前面篇幅已经将spring Security的认证、授权流程、跨域问题讲得差不多了。这一篇主要补充一下剩余的一些知识点,主要包括CSRF、认证失败、授权失败处理器相关知识点。
说明: 本系列的文章使用的是 springBoot 2.7.11 (springboot 2x 最后一个稳定版本),对应的 Spring Security 是 5.7.8
CSRF
简单说就是为了安全起见,浏览器和服务端每次通讯时,服务端会随机给浏览器发一个随机token值,放到请求头/响应头里 csrf_token = xxxx,然后每次都进行验证。
具体详见 CSRF攻击与防御
实质上这个csrf_token 跟我们使用的token鉴权方案是非常像的。也就是使用token鉴权天然解决了CSRF问题,因此不再需要Spring Security再来做这个检查了。
而且前后端分离项目,前端每次请求都是无状态的,本身也是没用csrf_token 这个请求头的,因此需要主动关闭。
@Bean
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
//使用由httpSecurityConfiguration 装配的 HttpSecurity 则会紧密的保持原有的过滤器
http.csrf().disable()//前后端分离项目要关闭 csrf
...
}
认证失败处理器
- 自定义 AuthenticationEntryPoint
/**
* 认证失败出现的异常处理
* @author
* @date 2023/6/7 21:59
**/
@Component
public class AuthenticationEntryImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
Result result = Result.error("用户认证失败请查询登录");
WebUtils.renderString(response, JSON.toJSONString(result));
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
- 添加 Security Config
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
// 配置异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
授权失败处理器
- 自定义AccessDeniedHandler
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
Result result = Result.error("您无权访问该接口");
WebUtils.renderString(response, JSON.toJSONString(result));
response.setStatus(HttpStatus.FORBIDDEN.value());
}
}
- 添加 Security Config
@Autowired
private AccessDeniedHandler accessDeniedHandler;
// 配置异常处理器
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
可能按照上面流程配置了认证失败、授权失败的处理器,但是没有执行到处理器中,这时候要看下是不是开启了全局异常捕获,需要手动抛出这2个异常。
/**
* 全局异常处理
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
...
/**
* 排除掉security 2个异常
* @param e
*/
@ExceptionHandler(AuthenticationException.class)
public void AuthenticationExceptionResolve(AuthenticationException e) {
throw e;
}
@ExceptionHandler(AccessDeniedException.class)
public void AccessDeniedExceptionResolve(AccessDeniedException e) {
throw e;
}
}