springboot版本:3.2.0
jdk版本:21
security,跟随boot版本。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Security6相比Security5及之前的版本,在配置上做出了断层式的改变。和之前继承WebSecurityConfigurerAdapter相比,现在种种配置都是以bean的形式呈现的。
@Slf4j
@EnableWebSecurity
@Configuration
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
// 白名单
@Value("#{'${config.security.white-list}'.split(',')}")
private String[] whiteList;
@Resource
private AuthenticationTokenFilter authenticationTokenFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(req -> req.requestMatchers(whiteList).permitAll()
.anyRequest().authenticated())
.exceptionHandling(http -> http.authenticationEntryPoint(failureHandling()))
// token过滤器
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
// 无状态
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* 强散列哈希加密实现
*/
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 验证失败处理器
*
*/
public AuthenticationEntryPoint failureHandling() {
return (request, response, exception) -> {
log.error(exception.getMessage());
RespEnum resultEnum = RespEnum.UNAUTHORIZED;
exception.printStackTrace();
Resp<Void> resp = Resp.error(resultEnum);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().write(JSON.toJSONString(resp));
};
}
}
这里用过滤器的方式验证用户,用户信息放到请求上下文中
@Component
public class AuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private TokenService tokenService;
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = SecurityUtils.getToken(request);
// 无token不加载用户信息
if (Func.isEmpty(token) || token.startsWith("Basic")) {
chain.doFilter(request, response);
return;
}
// 解析token,并从redis中取出loginUser
LoginUser loginUser = tokenService.verifyToken(token);
// security的认证实际上最终操作的就是这个authenticationToken
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
}
tokenService不变(主要是对token、用户的处理)。
@Component
public class TokenService {
@Resource
private RedisTemplate<String, String> redisTemplate;
// 令牌默认存储时长
protected static final long EXPIRE_SECOND = 3600;
// token在redis中的key
private final static String ACCESS_TOKEN = "login_tokens:";
/**
* 创建令牌
*/
public String createToken(LoginUser loginUser) {
String token = IdUtil.nanoId();
// 存储
refreshToken(loginUser, token);
return token;
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(String token) {
if (StringUtils.isNotEmpty(token)) {
String tokenKey = getTokenKey(token);
String userStr = redisTemplate.opsForValue().get(tokenKey);
LoginUser loginUser = JSON.parseObject(userStr, LoginUser.class);
return loginUser;
}
return null;
}
/**
* 删除用户缓存信息
*/
public void removeToken(String token) {
if (token == null) {
return;
}
String tokenKey = getTokenKey(token);
redisTemplate.delete(tokenKey);
}
/**
* 验证令牌有效期,相差不足120分钟,自动刷新缓存
*
* @param token
*/
public LoginUser verifyToken(String token) {
LoginUser loginUser = getLoginUser(token);
// 刷新token
refreshToken(loginUser, token);
return loginUser;
}
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser, String token) {
// 将loginUser缓存
String userKey = getTokenKey(token);
ValueOperations<String, String> forValue = redisTemplate.opsForValue();
forValue.set(userKey, JSON.toJSONString(loginUser) , Duration.ofSeconds(EXPIRE_SECOND));
}
private String getTokenKey(String token) {
return ACCESS_TOKEN + token;
}
}
登录不变,还是原先的,核心代码:
@Resource
private AuthenticationManager authenticationManager;
public Resp<String> login(@Valid @RequestBody LoginReqDTO loginBody) {
String username = loginBody.getUsername();
String password = loginBody.getPassword();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String token = tokenService.createToken(loginUser);
return Resp.success(token);
}