上周碰到问题,通过 persionService 进行权限判断的时候,抛出的 AccesDeniedExcepthion 无法被 AccessDeniedHandler 处理到。他是直接被 globalException 拦截到。
1.Spring Security 中的异常处理
我们一般都会在 Spring Security 的 自定义配置类( WebSecurityConfigurerAdapter )中使用 HttpSecurity 提供的 exceptionHandling() 方法用来提供异常处理。该方法构造出 ExceptionHandlingConfigurer 异常处理配置类。该配置类提供了两个实用接口:
AuthenticationEntryPoint 该类用来统一处理 AuthenticationException 异常
AccessDeniedHandler 该类用来统一处理 AccessDeniedException 异常
我们只要实现并配置这两个异常处理类即可实现对 Spring Security 认证授权相关的异常进行统一的自定义处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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) 方法中配置即可。相关的配置片段如下:
1
|
http.exceptionHandling().accessDeniedHandler(new SimpleAccessDeniedHandler()).authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
|
2.验证权限失败抛出 AccessDeniedException 不允许访问
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
|
@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。
1
2
3
4
5
6
|
@ExceptionHandler(value =Exception.class)
@ResponseBody
public ResultJson exceptionHandler(HttpServletRequest req, Exception e){
log.error("未知异常!原因是:",e);
return ResultJson.error(CommonEnum.INTERNAL_SERVER_ERROR);
}
|
是什么造成这个原因了:
问题原因
出现这种问题的原因一般都是因为项目中还配置了 GlobalExceptionHandler 。
由于 GlobalExceptionHandler 全局异常处理器会比 AccessDeniedHandler 先捕获 AccessDeniedException 异常,因此当配置了 GlobalExceptionHandler 后,会发现 AccessDeniedHandler 失效了。
3.解决方案
然后我就直接在全局异常处理 GlobalExceptionHandler 里添加
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 处理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);
}
|
经过过了很久的学习,已经没有单独使用 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
1
2
3
4
5
6
7
8
9
10
11
|
<!-- 注意是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 实现权限异常处理
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
|
@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 资源服务器
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
|
@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();//配置所有访问控制,必须认证过后才可以访问
}
}
|