Spring Security中自定义含缺失角色信息的AccessDenied异常及定制器实现
解决方案:无需大量自定义代码即可返回缺失角色信息
可以实现该需求,无需重写整个AuthorizationManagerBeforeMethodInterceptor,通过结合Authorization事件监听和自定义AccessDeniedHandler即可完成,代码量极少。
核心思路
拦截器在授权失败时会发布AuthorizationEvent,我们可以监听该事件存储授权决策信息,然后在处理AccessDeniedException时从存储的决策中提取缺失的角色/权限,替换默认的"Access Denied"提示。
具体实现步骤
创建ThreadLocal存储授权决策
用于在事件监听和异常处理之间传递授权决策信息,注意使用后清理避免内存泄漏:public class AuthorizationDecisionHolder { private static final ThreadLocal<AuthorizationDecision> DECISION_HOLDER = new ThreadLocal<>(); public static void setDecision(AuthorizationDecision decision) { DECISION_HOLDER.set(decision); } public static AuthorizationDecision getDecision() { return DECISION_HOLDER.get(); } public static void clear() { DECISION_HOLDER.remove(); } }监听AuthorizationEvent存储决策
当授权失败时,将决策信息存入ThreadLocal:@Component public class AuthorizationEventListener implements ApplicationListener<AuthorizationEvent> { @Override public void onApplicationEvent(AuthorizationEvent event) { AuthorizationDecision decision = event.getAuthorizationDecision(); if (decision != null && !decision.isGranted()) { AuthorizationDecisionHolder.setDecision(decision); } else { AuthorizationDecisionHolder.clear(); } } }自定义AccessDeniedHandler返回带角色的提示
捕获AccessDeniedException后,从ThreadLocal中取出决策,提取所需权限信息并返回:public class CustomAccessDeniedHandler implements AccessDeniedHandler { private final AccessDeniedHandler defaultHandler = new DefaultAccessDeniedHandler(); @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException { AuthorizationDecision decision = AuthorizationDecisionHolder.get(); if (decision != null) { String errorMessage = "Access Denied. Required authorities: "; // 针对AuthorityAuthorizationManager的决策提取所需角色 if (decision instanceof AuthorityAuthorizationManager.AuthorityAuthorizationDecision authDecision) { errorMessage += authDecision.getRequiredAuthorities(); } else { // 其他类型AuthorizationManager可自行扩展逻辑 errorMessage += "unknown"; } response.sendError(HttpServletResponse.SC_FORBIDDEN, errorMessage); } else { defaultHandler.handle(request, response, ex); } AuthorizationDecisionHolder.clear(); } }配置Spring Security使用自定义AccessDeniedHandler
在安全配置类中替换默认的异常处理器:@Configuration @EnableMethodSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .exceptionHandling(ex -> ex.accessDeniedHandler(new CustomAccessDeniedHandler())); return http.build(); } }
扩展说明
- 如果使用
ExpressionAuthorizationManager(如@PreAuthorize("hasRole('ADMIN')")),可以通过解析表达式提取所需角色,或者自定义AuthorizationManager包装表达式管理器,让决策携带所需角色信息。 - 该方案完全复用Spring Security原有逻辑,仅通过事件监听和异常处理扩展,代码量极少,符合"尽可能少编写自定义代码"的需求。
内容的提问来源于stack exchange,提问作者Morten Haraldsen
相关产品推荐
相关产品推荐

