SpringSecurity基本校验使用总结

SpringSecurity基本校验使用总结

一、本地视图方式校验(Thymeleaf)

1、基本用户名校验

  1. 配置Pom依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //父工程
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.4</version>
    </parent>
    //依赖
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    </dependencies>
  2. 配置application.yaml

    1
    2
    3
    4
    5
    spring:
    application:
    name: springboot-security-test
    server:
    port: 8899
  3. 配置SpringBootApplication主启动

    1
    2
    3
    4
    5
    6
    @SpringBootApplication
    public class SpingBootApplicationMain {
    public static void main(String[] args) {
    SpringApplication.run(SpingBootApplicationMain.class, args);
    }
    }
  4. 编写Controller

    1
    2
    3
    4
    5
    6
    7
    8
    @RestController
    @RequestMapping("/sec")
    public class TestController {
    @GetMapping("hello")
    public String hello(){
    return "认证成功";
    }
    }

1.1、从配置文件中进行校验

修改配置文件中的application.yaml添加Security配置

1
2
3
4
security:
user:
name: byz
password: 123

启动工程进行验证,访问配置的映射器路径:

image-20220414095444309

进入登陆页面,输入在配置文件中的用户名密码进入

image-20220414095725437

1.2、从配置类中进行校验

1、修改配置文件,注释掉:

1
2
3
4
#  security:
# user:
# name: byz
# password: 123

2、新建一个配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description TODO
* @Date 2022-04-14 09:43:31
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//注入密码加密编码器
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = (BCryptPasswordEncoder) passwordEncoder();
String password = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("byz").password(password).roles("admin");
}
}

1.3、从数据库或者其他方式自定义校验UserDetailService

  1. 自定义USerDeatilService实现UserdetailService接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Service("userDetailsService") //名称必须叫这个
    public class MyUserDetailsServiceImpl implements UserDetailsService {
    //这里注入数据库查询逻辑,实际校验用户是否存在
    //注入mapper进行校验

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    /*
    * 1、使用Mapper查询数据库
    * 2、判断是否查询到用户
    * (这里假定查询结果)
    * */
    MyUser user = new MyUser();
    List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user,ROLE_admin");
    if (user != null) {
    /**
    * 从数据库查询到的用户
    * 实质上需要返回一个数据库查询到的用户
    * 并将其封装成为一个UserDetail对象进行返回,系统会自动校验其中的密码以及用户名信息
    */
    return new User("byz", new BCryptPasswordEncoder().encode("123"), grantedAuthorities); //这里USer为UserDetail的子类
    } else {
    //查询不到就报错
    throw new UsernameNotFoundException("用户名不存在!");
    }
    }
    }
  2. 设置配置类并且重写配置权限方法configure(AuthenticationManagerBuilder auth)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * @Author BaiYZ
    * @Program SpringSecurityDemo
    * @Description TODO
    * @Date 2022-04-14 09:43:31
    */
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注入密码编码器
    @Bean
    PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //设置自定义的USerDetailService逻辑,以及配置密码编码器
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    }
  3. 配置类中配置Httpl拦截方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
    * 重写http加密校验规则
    */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http
    //配置登陆也米娜
    .formLogin() //自定义登陆页面 ,这里会调用UserNamePasswordFilter
    .loginPage("/user/login")
    //登陆访问url地址 由控制器处理
    .loginProcessingUrl("/login")
    //登陆成功url地址 由控制器处理
    .defaultSuccessUrl("/user/success")

    //配置路径权限
    .and()
    .authorizeRequests() //表示配置地址权限访问
    //配置以路径匹配来怕配置权限
    .antMatchers("/", "/index", "/user/login").permitAll()

    //配置指定页面使用角色管理
    .antMatchers("/role/admin").hasRole("admin")
    .antMatchers("/role/service").hasAnyRole("user,test")
    //指定权限管理
    .antMatchers("/role/auth").hasAuthority("admin")
    .antMatchers("/role/test").hasAnyAuthority("test")

    //对请求进行配置 POST Get ....
    .anyRequest().authenticated() //所有请求都需要授权

    //csrf跨站请求配置
    .and()
    .csrf().disable()

    //配置403 自定义逻辑
    //403自定义错误url
    .exceptionHandling().accessDeniedPage("/unauth");

    }

2、记住我rememberMe

2.1、记住我(基于数据库)

  1. 注入数据源

  2. 安全配置类中配置(注入)PersistentTokenRepository

    1
    2
    3
    4
    5
    6
    7
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    //启动时创建表,创建一次,也可以手动创建(进入这个PersistentTokenRepository获取建表的sql)
    jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
    }
  3. 配置类中

    1
    2
    3
    4
    5
    6
    7
    8
    protected void configure(HttpSecurity http) throws Exception {
    http
    .and()
    .rememberMe().tokenRepository(persistentTokenRepository()) //开启RememberMe设置操作数据库对象
    .tokenValiditySeconds(60) //设置过期时间
    .userDetailsService(userDetailsService); //设置查询数据库的逻辑板 返回UserDetails
    //其他配置....
    }
  4. 页面配置复选框

    名称必须叫做remember-me 框架底层设置

    1
    <input type="checkbox" name="remember-me"/>

3、退出登陆(默认行为)

默认的退出登录URL为/logout

需要自定义就实现LogoutHandler接口

二、分布式Token校验

2.1、编写UserDetail对象(UserDetailService查询会使用到)

  1. 编写基础用户对象(存储数据库中)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * @Author BaiYZ
    * @Program SpringSecurityDemo
    * @Description 实际的实体类
    * @Date 2022-04-15 17:01:52
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable {
    @Serial
    private static final long serialVersionUID = 2211L;
    private String userName;
    private String password;
    private String age;
    private String birthday;
    }
  2. 编写SecurityUser用于SpringSecurity中权限用户封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    /**
    * @Author BaiYZ
    * @Program SpringSecurityDemo
    * @Description 用于鉴权的实体类
    * @Date 2022-04-15 17:02:31
    */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class SecurityUser implements UserDetails {
    /**
    * 当前用户
    */
    private transient User currentUserInfo;
    /**
    * 权限列表
    */
    private List<String> permissionValueList;

    public SecurityUser(User currentUserInfo) {
    if(currentUserInfo != null){
    this.currentUserInfo = currentUserInfo;
    }
    }

    /**
    * 获取授权列表 GrantedAuthority
    * @return Collection<? extends GrantedAuthority>
    */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    Assert.notNull(this.permissionValueList, "Cannot pass a null GrantedAuthority collection");
    //遍历设置权限
    Set<GrantedAuthority> setAuthorities = new HashSet<>();
    for (String grantedAuthority : this.permissionValueList) {
    //权限不为空
    if(StringUtils.hasText(grantedAuthority)){
    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(grantedAuthority);
    setAuthorities.add(simpleGrantedAuthority);
    }
    }
    return setAuthorities;
    }

    @Override
    public String getPassword() {
    return null;
    }

    @Override
    public String getUsername() {
    return null;
    }

    @Override
    public boolean isAccountNonExpired() {
    return false;
    }

    @Override
    public boolean isAccountNonLocked() {
    return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    return false;
    }

    @Override
    public boolean isEnabled() {
    return false;
    }
    }

2.2、编写Security

image-20220414095725437

2.1.1、编写自定义密码加密规则PassWordEncoder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description TODO
* @Date 2022-04-15 14:56:21
*/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;

public DefaultPasswordEncoder() {
}

public DefaultPasswordEncoder(int strength) {

}

/**
* 自定义加密方式 --->> 也可以使用md5加密,返回加密字符串即可
* @param rawPassword 原始密码由Securuty框架传入
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return bCryptPasswordEncoder.encode(rawPassword);
}

/**
* 比对密码比较的是输入后加密与存储的加密密码比对
* @param rawPassword 框架获取的用户提交的密码
* @param encodedPassword 存储的加密后的密码
* @return bool 真为通过
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
//输入密码加密
String encodeInput = bCryptPasswordEncoder.encode(rawPassword);
return encodedPassword.equals(encodeInput);
}
}

2.1.2 Token工具类

  • 生成Token(jwt生成—JsonWebToken)
  • 根据用户名获取Token
  • Token中获取用户名
  • 删除Token

Jwt介绍:

  1. JWT头

    1. alg:表示签名算法
    2. typ:令牌类型
    1
    2
    3
    4
    {
    "alg":"HS256",
    "typ":"JWT"
    }
  2. 有效载荷 PayLoad(Claims)

    1. iss:发行人
    2. exp:到期时间  Unix 的_时间_
    3. sub:主题
    4. aud:用户
    5. nbf:在此之前不可用
    6. iat:发布时间
    7. jti:jwt id 标识一个JWt
    8. 私有字段
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "iss":"xxx",
    "exp":"222",
    "sub":"loginedUSer",
    "nbf":"xxx",
    "aud":"user",
    "iat":"xxx",
    "jti":"xxx",
    "自定义字段":"value"
    }
  3. 签名哈希

    对上面两部分进行签名,使用指定算法生成前面哈希

    例子:

    1
    HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

完整JWt字符串:

image-20220415151923617

拼装结构:

1
JWTString=Base64(Header).Base64(Payload).签名哈希

2.1.2.1、导入JWt依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
</parent>

<!--JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

2.1.2.2、编写TokenManager工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description token 管理工具类
* @Date 2022-04-15 15:25:04
*/
@Component("tokenManager")
public class TokenManager {
/**
* token有效期 这里设置60min
*/
private long tokenExpiredTime = 60 * 60 * 1000;

/**
* token密钥
* <b>一般实际项目中需要实际生成</b>
*/
private String tokenSecrect = "111111";

/**
* 根据Token获取用户名信息
* setSubject(String sub); 设置jwt加密对象
* setExpiration(Date exp); 设置过期时间
* signWith(SignatureAlgorithm alg, String base64EncodedSecretKey); 设置签名算法,签名密钥
* CompressionCodecs.GZIP 进行压缩
*/
public String createToken(String userName){
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiredTime))
.signWith(SignatureAlgorithm.ES512,tokenSecrect)
.compressWith(CompressionCodecs.GZIP).compact();
return token;
}
//根据用户名获取Token

/**
* Jwts.parser():获取jwt转化器
* setSigningKey(String key):设置签名密钥
* parseClaimsJws(String claimsJws):转化jwt
* getBody():获取body
* @param token 传入token串
* @return 返回信息
*/
public String getUserInfoFromToken(String token){
String userInfo = Jwts.parser()
.setSigningKey(tokenSecrect)
.parseClaimsJws(token)
.getBody()
.getSubject();
return userInfo;
}
public void removeToken(String token){

}
}

2.1.3 退出登陆处理器(Token)

2.1.3.1、使用Redis存储Token

  1. 引入Pom依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. application.yaml中配置Redis
1
2
3
4
spring:
redis:
port: 6379
host: localhost

2.1.3.2、编写退出处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description Token退出登陆逻辑
* @Date 2022-04-15 16:11:20
*/
@Component("tokenLogOutHandler")
public class TokenLogOutHandler implements LogoutHandler {

/**
* 注入Token工具类
*/
private TokenManager tokenManager;
/**
* redis模板类
*/
private RedisTemplate redisTemplate;

public TokenLogOutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}

/**
* 退出登陆逻辑
* @param request 请求
* @param response 响应
* @param authentication 授权信息封装
*/
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//1、从header拿到Token
String token = request.getHeader("token");
//2、存在Token
//2.1、移出Token(从Redis删除)
if(token!=null){
tokenManager.removeToken(token);
String userInfoFromToken = tokenManager.getUserInfoFromToken(token);
redisTemplate.delete(userInfoFromToken);
}
//写响应相关操作,返回一个状态xxx
}
}

退出逻辑配置

1
2
3
4
5
6
7
8
9
@Override 
protected void configure(HttpSecurity http) throws Exception {
http
.and()
.logout()
.logoutUrl("/logout") //默认为logout
.logoutSuccessHandler(tokenLogOutHandler) //自定义退出成功处理器
.deleteCookies("JSESSIONID") //要删除的Cookies
}

2.1.4 未授权统一处理AuthenticationEntryPoint

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description 未授权统一处理
* @Date 2022-04-15 16:32:12
*/
@Component("unAuthenticationEntryPoint")
public class UnAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//处理未授权逻辑
}
}

未授权统一权限配置:

1
2
3
4
5
6
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//没有权限处理器
.exceptionHandling().authenticationEntryPoint(unAuthenticationEntryPoint)
}

2.3、编写过滤器

image-20220415191708836

2.3.1、认证过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description 认证过滤器
* @Date 2022-04-15 16:47:42
*/
@Component("tokenLoginFilter")
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
/**
* redis模板类
*/
private RedisTemplate redisTemplate;
/**
* token工具类
*/
private TokenManager tokenManager;
/**
* 权限管理工具类
*/
private AuthenticationManager authenticationManager;

public TokenLoginFilter(RedisTemplate redisTemplate, TokenManager tokenManager, AuthenticationManager authenticationManager) {
this.redisTemplate = redisTemplate;
this.tokenManager = tokenManager;
this.authenticationManager = authenticationManager;
//设置不止用post提交
this.setPostOnly(false);
}

/**
* 重写尝试认证的方法
* 父类会进行调用
*
* @param request 请求
* @param response 响应
* @return Authentication 返回认证封装
* @throws AuthenticationException 认证异常
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//获取表单提交数据
try {
//从请求中获取用户信息
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
//交给SpringSecurity管理
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword(), new ArrayList<>()));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}

/**
* AbstractAuthenticationProcessingFilter调用子类attemptAuthentication认证之后
* <b>成功调用</b>
*
* @param request 请求
* @param response 响应
* @param chain 过滤器链
* @param authResult 认证结果Authentication
* @throws IOException id异常
* @throws ServletException servlet异常
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//获取认证之后用户信息UserDetail
SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();
//封装token(这里使用用户名生成)
String token = tokenManager.createToken(securityUser.getCurrentUserInfo().getUserName());
//将用户名以及权限列表放入Redis
redisTemplate.opsForValue().set(token, securityUser.getPermissionValueList());

//将Token 返回前端!!!!前端会进行封装放到请求头中


//返回相关页面信息

}

/**
* AbstractAuthenticationProcessingFilter调用子类attemptAuthentication认证之后
* <b>成功调用</b>
*
* @param request 请求
* @param response 响应
* @param failed 失败的认证异常
* @throws IOException id异常
* @throws ServletException servlet异常
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
//认证失败的逻辑 比如返回页面error json
}
}

配置认证过滤器:

1
2
3
4
5
@Override
protected void configure(HttpSecurity http) throws Exception {
//增加自定义认证过滤器
http.addFilter(tokenLoginFilter)
}

2.3.2、编写授权过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description 授权过滤器
* @Date 2022-04-15 16:47:15
*/
@Component("tokenAuthenticationFilter")
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;

public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取当前认证成功用户权限信息
UsernamePasswordAuthenticationToken authResult = getAuthentication(request);
//判断存在权限信息,放到权限上下文
if (authResult != null) {
SecurityContextHolder.getContext().setAuthentication(authResult);
}
//放行过滤器
chain.doFilter(request, response);
}

/**
* 从请求中获取解析Token生成UsernamePasswordAuthenticationToken
*
* @param request 请求
* @return UsernamePasswordAuthenticationToken
*/
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
//拿到token
String token = request.getHeader("token");
if (token != null) {
//从token 解析用户名
String username = tokenManager.getUserInfoFromToken(token);
//从redis中获取权限列表
List<String> permissionList = (List<String>) redisTemplate.opsForValue().get("username");
List<GrantedAuthority> collect = null;
if (permissionList != null) {
collect = permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
return new UsernamePasswordAuthenticationToken(username, token, collect);
}
return null;
}

}

配置授权过滤器:

1
2
3
4
5
@Override
protected void configure(HttpSecurity http) throws Exception {
//增加自定义授权过滤器
http.addFilter(tokenAuthenticationFilter)
}

2.4、编写UserDetailService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description 实际查询数据库获取UserDetail
* @Date 2022-04-15 18:54:03
*/
@Service("userDetailService") //名称必须叫这个,否则无法注入
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询数据库,获取Security需要的对象

//返回UserDetail对象
return new SecurityUser();
}
}

配置自定义UserDetailService:

1
2
3
4
5
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//增加自定义授权过滤器
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

2.5、编写核心配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description token 核心配置类
* @Date 2022-04-15 18:22:00
*/
@Configuration
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 授权失败(为授权)统一处理器
*/
private final UnAuthenticationEntryPoint unAuthenticationEntryPoint;
/**
* 退出处理器
*/
private final TokenLogOutHandler tokenLogOutHandler;
/**
* 认证过滤器
*/
private final TokenLoginFilter tokenLoginFilter;
/**
* 授权过滤器
*/
private final TokenAuthenticationFilter tokenAuthenticationFilter;
/**
* 实际查询数据库获取UserDetail的逻辑
*/
private final UserDetailsService userDetailsService;

@Autowired
public TokenWebSecurityConfig(UnAuthenticationEntryPoint unAuthenticationEntryPoint, TokenLogOutHandler tokenLogOutHandler, TokenLoginFilter tokenLoginFilter,
TokenAuthenticationFilter tokenAuthenticationFilter,UserDetailsService userDetailsService) {
this.unAuthenticationEntryPoint = unAuthenticationEntryPoint;
this.tokenLogOutHandler = tokenLogOutHandler;
this.tokenLoginFilter = tokenLoginFilter;
this.tokenAuthenticationFilter = tokenAuthenticationFilter;
this.userDetailsService = userDetailsService;
}

/**
* 整体配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//没有权限处理器
.exceptionHandling().authenticationEntryPoint(unAuthenticationEntryPoint)

//自定义退出成功处理器
.and()
.logout()
.logoutUrl("/logout") //默认为logout
.addLogoutHandler(tokenLogOutHandler)
.deleteCookies("JSESSIONID") //要删除的Cookies


//配置所有的需要授
.and()
.authorizeRequests()
//这里增加需要hasAuth权限或者需要角色的地址认证
//配置以路径匹配来怕配置权限
// .antMatchers("/", "/index", "/user/login").permitAll()
//
// //配置指定页面使用角色管理
// .antMatchers("/role/admin").hasRole("admin")
// .antMatchers("/role/service").hasAnyRole("user,test")

.anyRequest().authenticated()

//关闭csrf跨域攻击
.and().csrf().disable()

//增加自定义认证过滤器
.addFilter(tokenLoginFilter)

//增加权限过滤器
.addFilter(tokenAuthenticationFilter);
}
/**
* 配置权限验证相关
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}

/**
* 设置访问路径相关的
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
//设置不进行权限校验的路径
web.ignoring().antMatchers("/");
}

}

三、Handler配置

3.1、授权成功处理器

1、编写处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description 登陆成功处理逻辑
* @Date 2022-04-15 15:52:27
*/
@Component("authenticationSuccessHandlerImpl")
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//登陆成功的逻辑
}
}

2、配置到Security配置文件中

1
2
3
4
@Override 
protected void configure(HttpSecurity http) throws Exception {
http.successHandler(authenticationSuccessHandlerImpl);
}

3.2、授权失败处理器

1、编写处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description 授权失败处理器
* @Date 2022-04-15 15:57:03
*/
@Component("authenticationFailureHandlerImpl")
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//授权失败的逻辑
}
}

2、配置到Security配置文件中

1
2
3
4
@Override 
protected void configure(HttpSecurity http) throws Exception {
http.failureHandler(authenticationFailureHandlerImpl);
}

3.3、退出登陆处理器

1、编写处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @Author BaiYZ
* @Program SpringSecurityDemo
* @Description 退出登陆处理器
* @Date 2022-04-15 16:02:26
*/
@Component("logoutSuccessHandlerImpl")
public class LogoutSuccessHandlerImpl implements LogoutHandler {

@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//退出登陆的逻辑
}
}

2、配置到Sevurity配置文件中

1
2
3
4
5
6
7
8
9
@Override 
protected void configure(HttpSecurity http) throws Exception {
http
.and()
.logout()
.logoutUrl("/logout") //默认为logout
.logoutSuccessHandler(logoutSuccessHandlerImpl) //自定义退出成功处理器
.deleteCookies("JSESSIONID") //要删除的Cookies
}

四、权限注解

4.1、开启权限注解

1
2
3
4
5
6
7
8
9
@Configuration
/**
* 开启全局方法权限注解
* securedEnabled:开启安全注解
* prePostEnabled:前置后置过滤注解
*/
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

4.2、常用注解使用

4.2.1、@Secured

适用于方法上,标识当前这个方法是什么样的角色可以访问

1
2
3
4
5
6
7
8
9
10
11
/**
* securedEnable测试 开启角色权限
*
* @return
*/
@ResponseBody
@GetMapping("/securedEnabled")
@Secured({"ROLE_test"}) //允许test角色访问
public String securedEnabledTest() {
return "允许访问,这里是 @EnableGlobalMethodSecurity(securedEnabled = true) 注解配置使用@Secure";
}

4.2.2、@PreAuthorize

适用于方法上,可以用权限或者角色,先校验权限,后执行方法 不通过403

1
2
3
4
5
6
7
8
9
10
11
/**
* prePostEnabled测试 可以只用权限或者角色,先校验权限,后执行方法 不通过403
*
* @return
*/
@ResponseBody
@GetMapping("/prePostEnabled")
@PreAuthorize("hasAnyAuthority('admin')")
public String prePostEnabled() {
return "允许访问,这里是 @EnableGlobalMethodSecurity(prePostEnabled = true) 注解配置使用 提前校验@PreAuthorize";
}

4.2.3、@PostAuthorize

适用于方法上,有任何权限,先执行方法 后校验权限,不通过403

1
2
3
4
5
6
7
8
9
10
11
/**
* prePostEnabled测试 有任何权限,先执行方法 后校验权限
*
* @return
*/
@ResponseBody
@GetMapping("/postEnabled")
@PostAuthorize("hasAnyAuthority('test')")
public String postAuthorize() {
return "允许访问,这里是 @EnableGlobalMethodSecurity(prePostEnabled = true) 注解配置使用 后置校验@PostAuthorize";
}

4.2.4、@PostFilter

适用于方法上,输出匹配的指定值(留下这个其他过滤)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 权限校验之后,输出匹配的指定值(留下这个其他过滤)
*
* @return
*/
@ResponseBody
@RequestMapping("/postFilter")
@PostFilter("filterObject == 'admin'")
public List<String> getAllListPostFilter() {
ArrayList<String> list = new ArrayList<>();
list.add("admin");
list.add("test");
return list;
}

会输出 admin , test被过滤掉

4.2.4、@PreFilter

适用于方法上,权限校验之后,输出匹配的指定值

1
2
3
4
5
6
7
8
9
10
11
/**
* 权限校验之后,输出匹配的指定值
*
* @return
*/
@ResponseBody
@RequestMapping("/preFilter")
@PreFilter("filterObject == 'test'")
public List<String> getAllListPreFilter(@RequestBody List<String> input) {
return input;
}

输入:

1
2
3
[
"test","admin","user","abcd"
]

输出:

1
2
3
[
"test"
]

五、坑

  1. 角色配置中不需要增加“ROLE_”前缀,但是注解中需要增加,数据库中不用增加

    1
    2
    3
    4
    5
    public ExpressionInterceptUrlRegistry hasRole(String role) {
    return access(ExpressionUrlAuthorizationConfigurer
    //这里会传入前缀
    .hasRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, role));
    }

    this.rolePrefix在构造方法中进行设置,如果没有覆盖默认的设置GrantedAuthorityDefaults.class会使用前缀

    image-20220415192937614