1.网页
创建项目
-
启动项目
-
访问页面会被拦截,需要输入用户名和密码
-
用户名:user
-
密码:项目启动的时候,控制台生成的密码
-
登录成功之后,会自动跳转到最后一次访问的url地址
介绍
认证:登录,让不让你登录
授权:登录之后,能访问什么
防止网络攻击
SpringSecurity的认证和授权是分开的,并且二者不会互相影响
自定义登录页面
页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="/java_login" method="post">
<p>
<input type="text" placeholder="用户名" name="uname">
</p>
<p>
<input type="password" placeholder="密码" name="pwd">
</p>
<p>
<button>登录</button>
</p>
</form>
</body>
</html>
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//http未登录不能访问
//所有的请求,都必须是认证之后才能访问
http.authorizeRequests()
.anyRequest()
.authenticated();
//配置 表单请求
http.formLogin()
.loginPage("/loginpage.html")//配置登录的html页面
.usernameParameter("uname")//页面表单提交的时候,用户名的name值
.passwordParameter("pwd")//密码的name值
.loginProcessingUrl("/java_login")//页面表单提交的地址,action的值
.defaultSuccessUrl("/success.html",true)
.permitAll();//以上配置 都允许 不登录访问
//csrf防跨域伪造 关闭
//http.cors().disable();
http.csrf().disable();
}
//放过静态资源
@Override
public void configure(WebSecurity web) throws Exception {
//绕开 security框架,能访问的地址
web.ignoring().antMatchers("/css/**","/js/**","*.html");
}
}
使用数据库的用户名登录
配置完成mybatisplus的代码
生成代码
写一个类,实现UserDetails 用来 存储 数据库查询出来的 用户名密码等数据
@NoArgsConstructor
public class LoginUser implements UserDetails {
RgAdminUser adminUser;
public LoginUser(RgAdminUser adminUser) {
this.adminUser = adminUser;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//授权相关的,返回的这个集合,就是用户的角色集合或者权限标识集合
//Security中 认证 和 授权 是完全独立分开的
return null;
}
@Override
public String getPassword() {
//获取 数据库的密码
return adminUser.getPassword();
}
@Override
public String getUsername() {
//获取用户名
return adminUser.getUsername();
}
//一下 4个 方法,都必须 同时 返回true 才能认证成功
@Override
public boolean isAccountNonExpired() {
//账号是否过期
return true;
}
@Override
public boolean isAccountNonLocked() {
//账号是否锁定
return true;
}
@Override
public boolean isCredentialsNonExpired() {
//凭证是否过期
return true;
}
@Override
public boolean isEnabled() {
//是否启用
return adminUser.getStatus() == 0;
}
}
UserDetailService的实现类
@Service("unameUserDetailsService")
public class UnameUserDetailsServiceImpl implements UserDetailsService {
@Resource
RgAdminUserService adminUserService;
//认证的方法中,会执行当前的方法,传入 用户名 需要我们返回一个UserDetails
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据username 查询 用户信息
RgAdminUser adminUser = adminUserService.queryByUname(username);
if (adminUser!=null){
return new LoginUser(adminUser);
}
return null;
}
}
{noop}123 不加密的密码.真实的密码前面,必须额外加上{noop}
密码加密
//密码加密
@Bean
public PasswordEncoder passwordEncoder(){
//指明算法,代替系统原有的算法
//加入这行代码之后,密码,必须加密
return new BCryptPasswordEncoder();
}
加密之后,之前的{noop}123 已经不能登录了
必须使用加密的字符串 才能登录
修改密码接口
@Override
public void resetPassword(Integer uid, String password) {
//重置密码
//新密码----加密之后的
String newpassword = passwordEncoder.encode(password);
//更新代码
LambdaUpdateWrapper<RgAdminUser> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(RgAdminUser::getPassword,newpassword);
updateWrapper.eq(RgAdminUser::getUid,uid);
update(updateWrapper);
}
注销登录
//http.logout().logoutUrl("/log_out");
//http.logout().logoutUrl("/log_out").logoutSuccessUrl("/logout.html").permitAll();
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/log_out","GET"))
.logoutSuccessUrl("/logout.html").permitAll();
静态资源
以后是前后端分离的,服务端不存静态资源
2.JSON
Security--JSON
放弃页面
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.51</version>
</dependency>
返回JSON数据
http.formLogin()
.usernameParameter("uname")//页面表单提交的时候,用户名的name值
.passwordParameter("pwd")//密码的name值
.loginProcessingUrl("/javasm_login")//页面表单提交的地址,action的值
.successHandler((request, response, authentication) -> createJSON(response,authentication))
.failureHandler((request, response, e) -> createJSON(response,e.getMessage()))
.permitAll();//以上配置 都允许 不登录访问
未登录 返回json
http.exceptionHandling()
.authenticationEntryPoint((request, response, e) -> createJSON(response,e.getMessage()));
退出登录返回json
//退出登录
http.logout()
.logoutUrl("/logout")
.logoutSuccessHandler((request, response, authentication) ->
createJSON(response,authentication));
获取用户信息
查询当前登录的用户信息
@Override
public RgAdminUser getMyUser() {
//获取上下文对象
SecurityContext context = SecurityContextHolder.getContext();
//从上下文对象中,获取用户信息
//Authentication 实际上的对象是 UsernamePasswordAuthenticationToken
Authentication authentication = context.getAuthentication();
//个人信息,是在principal属性中
//getPrincipal UserDetail类型, 实际的对象 LoginUser
LoginUser loginUser = (LoginUser)authentication.getPrincipal();
//获得属性
RgAdminUser adminUser = loginUser.getAdminUser();
return adminUser;
}
修改个人信息
@Override
public void updateMyUser(RgAdminUser adminUser) {
//获取已经登录的用户信息,从Security中获取用户信息,没有进过数据库
RgAdminUser myUser = getMyUser();
//判断 用户 是不是想修改密码,是否加了密码参数
if (!StringUtils.isEmpty(adminUser.getPassword())){
String newpassword = passwordEncoder.encode(adminUser.getPassword());
//对象中的密码,就是加密之后的密码了
adminUser.setPassword(newpassword);
//
myUser.setPassword(newpassword);
}
//数据库更新,
adminUser.setUid(myUser.getUid());
updateById(adminUser);
new Thread(()->{
//更新 Security中的用户信息
SecurityContext context = SecurityContextHolder.getContext();
//因为 用户 传入的参数 adminuser 可能属性不全,
//新的值一个一个的拿进来
if (!StringUtils.isEmpty(adminUser.getUsername())){
myUser.setUsername(adminUser.getUsername());
}
if (adminUser.getStatus()!=null){
myUser.setStatus(adminUser.getStatus());
}
UserDetails userDetails = new LoginUser(myUser);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails,
userDetails.getPassword(),
userDetails.getAuthorities());
context.setAuthentication(authentication);
}).start();
}
JSON登录
请求接口的时候,传入json格式数据,登录
1创建一个JsonLoginFilter,接收json格式数据
2修改配置类,添加新的过滤器
public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {
Logger logger = LogManager.getLogger(JsonLoginFilter.class);
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
//限定只能post方式登录
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("请求方式不对: " + request.getMethod());
}
//判断 获取的参数 是不是 json数据
if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){
throw new AuthenticationServiceException("请求类型不是JSON: " + request.getMethod());
}
try {
//如何 从request对象中 获取JSON字符串
/*
{
"username":"xiaoming",
"password":"456"
}
*/
String json = RequestBodyUtil.read(request);
//json转成map
Map<String,String> map = JSONObject.parseObject(json, Map.class);
//从map中获取参数
String username = map.get("username");
String password = map.get("password");
//如果参数是null,给空值
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//用户名密码存入token 临时存储
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//添加ip信息
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (IOException e) {
throw new AuthenticationServiceException("参数接收错误: " + e.getMessage());
}
}
}
public class RequestBodyUtil {
//处理 request中的参数,转成 json字符串返回
public static String read(HttpServletRequest request) throws IOException {
//request对象的流信息
ServletInputStream inputStream = request.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
//把字节流转成了 字符串流
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//StringBuilder 存储参数
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine())!=null){
sb.append(line).append("\n");
}
//把StringBuilder转成String字符串
String json = sb.toString().trim();
return json;
}
}
修改配置文件
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//密码加密
@Bean
public PasswordEncoder passwordEncoder() {
//指明算法,代替系统原有的算法
//加入这行代码之后,密码,必须加密
return new BCryptPasswordEncoder();
}
/************************JSON格式登录,配置,开始*************************************/
@Bean
public JsonLoginFilter jsonLoginFilter() throws Exception {
//想在new对象的时候,添加一些配置,所以没有在类的内部加@Companet
JsonLoginFilter filter = new JsonLoginFilter();
//调用父类的认证方法,必不可少
filter.setAuthenticationManager(authenticationManagerBean());
//配置登录成功之后,返回什么json
filter.setAuthenticationSuccessHandler((request, response, authentication) -> createJSON(response, authentication));
//失败之后的显示
filter.setAuthenticationFailureHandler((request, response, e) -> createJSON(response, e.getMessage()));
//设置 访问的路径
filter.setFilterProcessesUrl("/json_login");
return filter;
}
//获取用户名密码之后,如何校验
//这样从Spring容器中获取对象,如果有很多同类型的实现类,因为加了name属性,也不会报错
@Resource(name = "unameUserDetailsService")
UserDetailsService userDetailsService;
@Bean
public DaoAuthenticationProvider jsonProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
//设置 查询用户信息 使用 哪个service
authenticationProvider.setUserDetailsService(userDetailsService);
//配置 加密的算法
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
/************************JSON格式登录,配置,结束*************************************/
@Override
protected void configure(HttpSecurity http) throws Exception {
//http未登录不能访问
//所有的请求,都必须是认证之后才能访问
http.authorizeRequests()
.antMatchers("/admin/user/**").permitAll()
.anyRequest()
.authenticated();
//配置JSON格式登录 加过滤器,告诉Security 是哪个类型的过滤器
http.addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(jsonProvider());//是用哪个认证方法
//配置 表单请求
http.formLogin()
.usernameParameter("uname")//页面表单提交的时候,用户名的name值
.passwordParameter("pwd")//密码的name值
.loginProcessingUrl("/javasm_login")//页面表单提交的地址,action的值
.successHandler((request, response, authentication) -> createJSON(response, authentication))
.failureHandler((request, response, e) -> createJSON(response, e.getMessage()))
.permitAll();//以上配置 都允许 不登录访问
//未登录 返回json
http.exceptionHandling()
.authenticationEntryPoint((request, response, e) ->
createJSON(response, e.getMessage()));
//退出登录
http.logout()
.logoutUrl("/logout")
.logoutSuccessHandler((request, response, authentication) ->
createJSON(response, "退出登录成功"));
//csrf防跨域伪造 关闭
http.csrf().disable();
}
//放过静态资源
@Override
public void configure(WebSecurity web) throws Exception {
//绕开 security框架,能访问的地址
web.ignoring().antMatchers("/css/**", "/js/**", "*.html");
}
private void createJSON(HttpServletResponse response, Object o) throws IOException {
//想返回数据,向Response对象中 设置内容
response.setContentType("application/json;charset=utf-8");
//向浏览器 写出数据的对象
PrintWriter writer = response.getWriter();
//写什么json,内容是什么
R r = R.ok(o);
String json = JSON.toJSONString(r);
writer.write(json);
writer.flush();
writer.close();
}
}
图片验证码登录
引入依赖
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
生成图片验证码
@RestController
@RequestMapping("/sec")
public class SecurityController {
@Resource
HttpServletResponse response;
@Resource
HttpSession session;
//显示验证码
@GetMapping("/img/code")
public void getImageCode() throws IOException {
Properties properties = new Properties();
//配置图片宽高
properties.setProperty("kaptcha.image.with","150");
properties.setProperty("kaptcha.image.height","50");
//图片上面 显示的 数字,随机数
properties.setProperty("kaptcha.textproducer.char.string","0123456789");
//验证码长度
properties.setProperty("kaptcha.textproducer.char.length","4");
//创建配置对象
Config config = new Config(properties);
DefaultKaptcha kaptcha = new DefaultKaptcha();
//创建图形对象
kaptcha.setConfig(config);
//生成图片
String code = kaptcha.createText();
BufferedImage image = kaptcha.createImage(code);
//code存入session
session.setAttribute("json_code",code);
//设置response头信息,为图片
response.setContentType("image/jpeg");
//获取流信息
ServletOutputStream outputStream = response.getOutputStream();
ImageIO.write(image,"jpg",outputStream);
}
}
public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
.....
String json = RequestBodyUtil.read(request);
//json转成map
Map<String,String> map = JSONObject.parseObject(json, Map.class);
//从map中获取参数
String username = map.get("username");
String password = map.get("password");
//参数中 拿到的
String code = map.get("code");
//校验code
if (code == null){
throw new AuthenticationServiceException("验证码为空");
}else {
HttpSession session = request.getSession();
Object o = session.getAttribute("json_code");
if (o == null){
throw new AuthenticationServiceException("验证码未生成,请生成验证码");
}else {
String sessionCode = o.toString();
if (!code.equals(sessionCode)){
throw new AuthenticationServiceException("验证码错误");
}
}
}
......
}
}
手机号验证码登录
发送验证码
@Resource
RedisTemplate<String,Object> redisTemplate;
@Resource
SmsUtil smsUtil;
@Override
public void sendCode(String phone) {
//获取随机的字符串
String code = RandomUtil.getCode(4);
//存入redis
String key = String.format(RedisKeys.UserCodePhone,phone);
redisTemplate.opsForValue().set(key,code,10, TimeUnit.HOURS);
//发短信
smsUtil.sendSms(phone,code);
}
手机号验证码登录
PhoneLoginFilter
public class PhoneLoginFilter extends AbstractAuthenticationProcessingFilter {
public PhoneLoginFilter() {
//当前的登录方式,url地址和请求类型
super(new AntPathRequestMatcher("/phone_login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
//如果不是post方法请求,报错
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
//判断 获取的参数 是不是 json数据
if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
throw new AuthenticationServiceException("请求类型不是JSON: " + request.getContentType());
}
//把用户传入的json字符串 获取到
String json = RequestBodyUtil.read(request);
Map<String, String> map = JSONObject.parseObject(json, Map.class);
//接收参数
String phone = map.get("phone");
String code = map.get("code");
phone = phone == null ? "" : phone.trim();
code = code == null ? "" : code.trim();
//手机号验证码 封到一个token对象中
PhoneAuthenticationToken token = new PhoneAuthenticationToken(phone,code);
//补充详情
setDetails(request,token);
return this.getAuthenticationManager().authenticate(token);
}
protected void setDetails(HttpServletRequest request, PhoneAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
}
PhoneAuthenticationToken
public class PhoneAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;//手机号 & 用户详情
private Object credentials;//验证码
public PhoneAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public PhoneAuthenticationToken( Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {
//权限列表
super(authorities);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}
PhoneAuthenticationProvider
@Component
public class PhoneAuthenticationProvider implements AuthenticationProvider {
@Resource
RedisTemplate<String,Object> redisTemplate;
@Resource(name = "phoneDetailService")
UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//PhoneLoginFilter中 存入的信息
PhoneAuthenticationToken token = (PhoneAuthenticationToken) authentication;
String phone = (String) token.getPrincipal();//手机号
String code = (String) token.getCredentials();//验证码
//校验 验证码对不对
String key = String.format(RedisKeys.UserCodePhone,phone);
//redis中获取正确的验证码
Object o = redisTemplate.opsForValue().get(key);
if (o == null){
throw new BadCredentialsException("验证码过期");
}
String relCode = (String) o;
if (!relCode.equals(code)){
throw new BadCredentialsException("验证码错误");
}
//代码运行到这里,说明 验证码 校验通过
//根据 手机号,查询 用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
//组装成新的 PhoneAuthenticationToken
//(用户信息,随意,权限列表)
PhoneAuthenticationToken authenticationToken = new PhoneAuthenticationToken(userDetails,phone,userDetails.getAuthorities());
//详情 ip等等
authenticationToken.setDetails(token.getDetails());
return authenticationToken;
}
@Override
public boolean supports(Class<?> aClass) {
return PhoneAuthenticationToken.class.isAssignableFrom(aClass);
}
}
@Service("phoneDetailService")
public class PhoneUserDetailsServiceImpl implements UserDetailsService {
@Resource
RgAdminUserService adminUserService;
@Override
public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
RgAdminUser adminUser = adminUserService.queryByPhone(phone);
if (adminUser!=null){
return new LoginUser(adminUser);
}
return null;
}
}
SecurityConfig
@Bean
public PhoneLoginFilter phoneLoginFilter() throws Exception {
PhoneLoginFilter filter = new PhoneLoginFilter();
filter.setAuthenticationManager(authenticationManagerBean());
//登录成功页面
filter.setAuthenticationSuccessHandler((request, response, authentication) -> createJSON(response, authentication));
//登录失败页面
filter.setAuthenticationFailureHandler((request, response, e) -> createJSON(response, e.getMessage()));
//因为在PhoneLoginFilter中 已经设置过登录的路径,不再设置
return filter;
}
@Resource
PhoneAuthenticationProvider phoneAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
......
http.addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(jsonProvider());//是用哪个认证方法
//配置 手机号验证码登录
http.addFilterAt(phoneLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(phoneAuthenticationProvider);
......
}
Token登录
TokenLoginFilter
public class TokenLoginFilter extends AbstractAuthenticationProcessingFilter {
public TokenLoginFilter(){
super(new AntPathRequestMatcher("/token_login","POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
//如果不是post方法请求,报错
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
//token字符串,是在header中 传递过来的
String tokenstr = request.getHeader(JWTUtil.Admin_Token);
if (StringUtils.isEmpty(tokenstr)){
throw new AuthenticationServiceException("Token_Null");
}
//如果有token 下一步
TokenAuthenticationToken authenticationToken = new TokenAuthenticationToken(tokenstr,"");
//PhoneAuthenticationToken t1 = new PhoneAuthenticationToken(tokenstr,"");
setDetails(request,authenticationToken);
return this.getAuthenticationManager().authenticate(authenticationToken);
}
protected void setDetails(HttpServletRequest request, TokenAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
}
TokenAuthenticationToken
public class TokenAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;//手机号 & 用户详情
private Object credentials;//验证码
public TokenAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public TokenAuthenticationToken( Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {
//权限列表
super(authorities);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}
TokenAutheticationProvider
@Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
@Resource(name = "uidDetailsService")
UserDetailsService userDetailsService;
@Resource
HttpServletResponse response;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
TokenAuthenticationToken token = (TokenAuthenticationToken) authentication;
//token字符串
String tokenStr = (String) token.getPrincipal();
//获取里面的uid
String uid = JWTUtil.parse(tokenStr);
//根据uid 查询 用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(uid);
//token登录成功
//生成新的token,把新的token 存入 header对象
String newTokenStr = JWTUtil.generate(uid);
//token字符串 放到header对象中
response.setHeader(JWTUtil.Admin_Token,newTokenStr);
//允许js处理
response.setHeader("Access-Control-Expose-Headers",JWTUtil.Admin_Token);
//把用户详情,和权限列表,再次封到TokenAuthenticationToken
TokenAuthenticationToken authenticationToken = new TokenAuthenticationToken(userDetails, uid,userDetails.getAuthorities());
//详情 ip等等
authenticationToken.setDetails(token.getDetails());
return authenticationToken;
}
@Override
public boolean supports(Class<?> aClass) {
return TokenAuthenticationToken.class.isAssignableFrom(aClass);
}
}
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/************************Token登录,配置,开始*************************************/
@Resource
TokenAuthenticationProvider tokenAuthenticationProvider;
@Bean
public TokenLoginFilter tokenLoginFilter() throws Exception {
TokenLoginFilter filter = new TokenLoginFilter();
filter.setAuthenticationManager(authenticationManagerBean());
//登录成功页面
filter.setAuthenticationSuccessHandler((request, response, authentication) -> createJSON(response, authentication));
//登录失败页面
filter.setAuthenticationFailureHandler((request, response, e) -> {
e.printStackTrace();
createJSON(response, e.getMessage());
});
//因为在TokenLoginFilter中 已经设置过登录的路径,不再设置
return filter;
}
/************************Token登录,配置,结束*************************************/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置 token登录
http.addFilterAt(tokenLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(tokenAuthenticationProvider);
}
跨域
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//配置类
CorsConfiguration corsConfiguration = new CorsConfiguration();
//支持所有的域名
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedHeader("*");
//允许 token
corsConfiguration.addAllowedHeader(JWTUtil.Admin_Token);
//允许cookies
corsConfiguration.setAllowCredentials(true);
//配置类 注册到 跨域类
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsFilter(source);
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//跨域
@Resource
CorsFilter corsFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
//跨域 ,必须在过滤器链之前 配置
//在UsernamePasswordAuthenticationFilter过滤器之前,添加一个过滤器corsFilter
http.addFilterBefore(corsFilter,UsernamePasswordAuthenticationFilter.class);
//配置JSON格式登录 加过滤器,告诉Security 是哪个类型的过滤器
http.addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(jsonProvider());//是用哪个认证方法
//配置 手机号验证码登录
http.addFilterAt(phoneLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(phoneAuthenticationProvider);
//配置 token登录
http.addFilterAt(tokenLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(tokenAuthenticationProvider);
}
}
3.授权
授权
认证和授权是分开的
设计一下现在的权限表结构
没有配置过权限的url地址,认证成功的用户都能访问,不做限制
角色
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//初始化 权限标识集合
List<GrantedAuthority> list = new ArrayList<>();
//角色
RgAdminRole role = adminUser.getRole();
if (role!=null){
//创建 权限对象
//ROLE_super_admin
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+role.getCode());
//添加到集合中
list.add(authority);
}
//如果多角色,循环 添加到列表中
return list;
}
配置文件
http.authorizeRequests()
.antMatchers("/dynamic/test1","/dynamic/test2").hasRole("super_admin")//只有 super_admin的角色,才能访问当前的链接
//.antMatchers("/dynamic/test2").hasRole("admin")//后设置的权限 覆盖了前面的权限
.antMatchers("/dynamic/test3").hasAnyRole("test","super_admin")
.antMatchers("/admin/user/**", "/sec/**").permitAll()
.anyRequest()
.authenticated();
角色继承
@Bean
public RoleHierarchy roleHierarchy(){
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
//设置 admin 的权限 大于 test
hierarchy.setHierarchy("ROLE_admin > ROLE_test");
return hierarchy;
}
权限标识
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//初始化 权限标识集合
List<GrantedAuthority> list = new ArrayList<>();
//权限标识
List<RgAdminAuthority> authorityList = adminUser.getAuthorityList();
//循环,把用户的权限标识,添加到权限集合中
authorityList.forEach(auth ->{
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(auth.getCode());
list.add(authority);
});
return list;
}
http.authorizeRequests()
.antMatchers("/dynamic/test1").hasAuthority("buss")
.antMatchers("/dynamic/test2").hasAuthority("test")
.antMatchers("/dynamic/test3").hasAnyAuthority("buss","test")
.antMatchers("/admin/user/**", "/sec/**").permitAll()
.anyRequest()
.authenticated();
自定义403页面
//自定义 403
http.exceptionHandling().accessDeniedHandler((request,response,e)->{
createJSON(response,"没有权限");
});
注解
使用Security的注解,控制授权
@Configuration
//开启的是Security授权注解
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
@Secured
拥有什么角色的人可以访问
不支持授权码
//当前方法,允许 拥有super_admin和test角色的人访问,角色的继承在这里失效
@Secured({"ROLE_super_admin","ROLE_test"})
//未生效
@Secured({"buss"})
@PreAuthorize
在方法 执行之前 ,进行控制
//当前的方法,允许 拥有 buss,test 权限标识的人访问
@PreAuthorize("hasAnyAuthority('buss','test')")
//只允许 拥有 buss权限的用户访问
@PreAuthorize("hasAuthority('buss')")
//只允许 拥有 super_admin 角色的人访问
@PreAuthorize("hasRole('ROLE_super_admin 角色的人访问')")
//只允许 拥有 test 角色的人访问, 经过测试,角色的继承 生效了,admin也可以访问
@PreAuthorize("hasRole('ROLE_test')")
@PostAuthorize
在方法执行之后,返回之前,进行控制
使用这个注解,方法一定会被执行,但是 不一定能返回
//允许访问当前方法,但是只有super_admin角色 可以返回数据
@PostAuthorize("hasAnyRole('ROLE_super_admin')")
@PreAuthorize@PostAuthorize 都可以使用 角色 和 授权码进行控制
@PreFilter
用来过滤参数的,把不符合条件的参数过滤掉
//只能过滤集合类型的参数
@PreFilter("filterObject.likeNum > 40")
@GetMapping("/save/list")
public R test7(@RequestBody List<RgDynamic> list){
logger.info(list);
return R.ok(list);
}
@PostFilter
//过滤 返回值, 只能过滤 集合类型的返回值
@PostFilter("filterObject.likeNum < 40")
@GetMapping("/list")
public List<RgDynamic> test8(){
List<RgDynamic> list = dynamicService.list();
return list;
}
自定义权限认证
1:使用Security的权限列表,做判断
@Component("javasmAuthorize")
public class JavasmExpressAuthorize {
Logger logger = LogManager.getLogger(JavasmExpressAuthorize.class);
/**
* //确保当前的类中,只有1个方法,返回值类型是布尔
* @param code 权限标识
* @return 布尔,如果返回true 允许用户继续访问方法,如果返回false,不允许用户访问,拦截了
*/
public boolean f23(String code){
//传入权限标识,方法内部,判断,当前的用户,是否拥有这个权限
//获取当前登录的信息
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal!=null){
LoginUser loginUser = (LoginUser) principal;
//获取框架里的 权限列表
Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();
//校验,权限列表中,是否包含 code
//判断 集合中的 每个元素,是否包含code
/*for (GrantedAuthority grantedAuthority : authorities){
//ROLE_super_admin buss
String hasCOde = grantedAuthority.getAuthority();
if (hasCOde.equals(code)){
return true;
}
}
return false;*/
return authorities.stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(code));
}
return false;
}
}
//@PreAuthorize("@处理的类在Spring容器中的id")通过id 找到对应的bean
@PreAuthorize("@javasmAuthorize.f23('buss')")
//过滤 返回值, 只能过滤 集合类型的返回值
@PostFilter("filterObject.likeNum < 40")
@GetMapping("/list")
public List<RgDynamic> test8(){
List<RgDynamic> list = dynamicService.list();
return list;
}
2:自己写判断的流程
@Component("javaPreAuthorize")
public class ZhangPreAuthorize {
public boolean f1(String code){
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal!=null){
//用户信息
LoginUser loginUser = (LoginUser) principal;
//获取 登录的用户信息,我们自己的用户对象
RgAdminUser adminUser = loginUser.getAdminUser();
//获取权限列表
List<RgAdminAuthority> authorityList = adminUser.getAuthorityList();
List<String> codeList = authorityList.stream().map(RgAdminAuthority::getCode).collect(Collectors.toList());
/* List<String> codeList1 = new ArrayList<>();
authorityList.forEach(authority ->{
codeList1.add(authority.getCode());
});*/
//判断 传入的 code 在不在集合中
return codeList.contains(code);
}
return false;
}
}
@PreAuthorize("@javaPreAuthorize.f1('buss')")
@GetMapping("/test9")
public R test9(){
List<RgDynamic> list = dynamicService.list();
return R.ok(list);
}
3:使用menu
@Component
public class JavasmMenuAuthorize {
public boolean check(String url){
//查询 用户的信息
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal==null){
return false;
}
LoginUser loginUser = (LoginUser) principal;
//判断 菜单列表 是否包含当前的地址
return loginUser.checkMenu(url);
}
}
//检查 菜单 是否包含url地址
public boolean checkMenu(String code){
//获取 菜单列表
List<RgAdminMenu> menuList = this.adminUser.getRole().getMenuList();
//把 菜单的url地址 集中到一起
//使用流的方式
Stream<String> childUrlStream = menuList.stream()
.map(RgAdminMenu::getChildList)
.flatMap(Collection::stream)
.map(RgAdminMenu::getUrl);
Stream<String> firstUrlStream = menuList.stream().map(RgAdminMenu::getUrl);
List<String> urlList = Stream.concat(childUrlStream, firstUrlStream).collect(Collectors.toList());
return urlList.contains(code);
}
@PreAuthorize("@javasmMenuAuthorize.check('/user/list')")
@GetMapping("/test10")
public R test10(){
List<RgDynamic> list = dynamicService.list();
return R.ok(list);
}