我们一般都会在Spring Security 的 自定义配置类( WebSecurityConfigurerAdapter )中使用HttpSecurity 提供的 exceptionHandling() 方法用来提供异常处理。该方法构造出 ExceptionHandlingConfigurer 异常处理配置类。该配置类提供了两个实用接口:
AuthenticationEntryPoint 该类用来统一处理 AuthenticationException 异常
AccessDeniedHandler 该类用来统一处理 AccessDeniedException 异常
我们只要实现并配置这两个异常处理类即可实现对 Spring Security 认证授权相关的异常进行统一的自定义处理。
public class SimpleAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.out(response, ResultJson.error(CommonEnum.NOT_FIND_LOGIN_INFORMATION));
}
}
public class SimpleAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseUtil.out(response, ResultJson.error(CommonEnum.NO_PERMISSION));
}
}
配置
实现了上述两个接口后,我们只需要在 WebSecurityConfigurerAdapter 的 configure(HttpSecurity http) 方法中配置即可。相关的配置片段如下:
http.exceptionHandling().accessDeniedHandler(new SimpleAccessDeniedHandler()).authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
@Slf4j
@Component("el")
public class ElPermissionConfig {
/**
* 判断接口是否有xxx:xxx权限
*
* @param permission 权限
* @return {boolean}
*/
public boolean check(String permission) {
log.info("需要权限:{}",permission);
if (StrUtil.isBlank(permission)) {
return false;
}
SecurityUser user= (SecurityUser) SecurityUtils.getCurrentUser();
if (user == null) {
return false;
}
return user
.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.filter(StringUtils::hasText)
.anyMatch(x -> PatternMatchUtils.simpleMatch(permission, x));
}
}
当验证权限失败时抛出AccessDeniedException异常 不允许访问,而我明明配置了SimpleAccessDeniedHandler 来处理异常并返回提示信息。我仔细检查发现拦截AccessDeniedException异常的是全局异常处理GlobalExceptionHandler。
@ExceptionHandler(value =Exception.class)
@ResponseBody
public ResultJson exceptionHandler(HttpServletRequest req, Exception e){
log.error("未知异常!原因是:",e);
return ResultJson.error(CommonEnum.INTERNAL_SERVER_ERROR);
}
然后我就直接在全局异常处理GlobalExceptionHandler里添加
/**
* 处理AccessDeineHandler无权限异常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = AccessDeniedException.class)
@ResponseBody
public ResultJson exceptionHandler(HttpServletRequest req, AccessDeniedException e){
log.error("不允许访问!原因是:",e.getMessage());
return ResultJson.error(CommonEnum.NO_PERMISSION);
}
==============={2023/4/13} ==========================
经过过了很久的学习,已经没有单独使用security,现在是使用security-oauth2,但是很多 配置是类似的。
现在没有使用全局异常也能处理 Security 中的异常处理, 直接在CustomAuthExceptionHandler 捕获打印
2023-04-13 16:53:10.935 ERROR 10932 — [nio-8111-exec-2] c.d.s.h.CustomAuthExceptionHandler : NoAuthentication :UNAUTHORIZED
2023-04-13 16:53:11.863 ERROR 10932 — [nio-8111-exec-3] c.d.s.h.CustomAuthExceptionHandler : NoAuthentication :UNAUTHORIZED
<!-- 注意是starter,自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 不是starter,手动配置 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
CustomAuthExceptionHandler 实现权限异常处理
@Component
@Slf4j
public class CustomAuthExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
Throwable cause = authException.getCause();
if (cause instanceof InvalidTokenException) {
log.error("InvalidTokenException : {}",cause.getMessage());
//Token无效
ResponseUtil.out(response,ResultJson.error(CommonEnum.ACCESS_TOKEN_INVALID));
//response.getWriter().write(JSON.toJSONString(ResultJson.error(CommonEnum.ACCESS_TOKEN_INVALID)));
} else {
log.error("NoAuthentication :{} ",CommonEnum.UNAUTHORIZED);
//资源未授权
ResponseUtil.out(response,ResultJson.error(CommonEnum.UNAUTHORIZED));
//response.getWriter().write(JSON.toJSONString(ResultJson.error(CommonEnum.UNAUTHORIZED)));
}
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//访问资源的用户权限不足
log.error("AccessDeniedException : {}",accessDeniedException.getMessage());
ResponseUtil.out(response,ResultJson.error(CommonEnum.INSUFFICIENT_PERMISSIONS));
//response.getWriter().write(JSON.toJSONString(ResultJson.error(CommonEnum.INSUFFICIENT_PERMISSIONS)));
}
}
oauth2资源服务器
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_IDS = "order";
@Autowired
private CustomAuthExceptionHandler customAuthExceptionHandler;
@Autowired
UserLogoutSuccessHandler userLogoutSuccessHandler;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_IDS)
.stateless(false)
.accessDeniedHandler(customAuthExceptionHandler)
.authenticationEntryPoint(customAuthExceptionHandler);
}
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
//解决springSecurty使用X-Frame-Options防止网页被Frame
httpSecurity.headers().frameOptions().disable()
.and()
.logout()
.logoutSuccessHandler(userLogoutSuccessHandler);
httpSecurity
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().anyRequest()
.and()
.anonymous()
.and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()//将PreflightRequest不做拦截。
.and()
.authorizeRequests()
.antMatchers(
"/webjars/**",
"/swagger/**",
"/v2/api-docs",
"/doc.html",
"/swagger-ui.html",
"/swagger-resources/**",
"/druid/**",
"/open/**").permitAll()
.and()
.authorizeRequests()
.antMatchers("/**").authenticated();//配置所有访问控制,必须认证过后才可以访问
}
}