Spring Security自定义AccessDeniedHandler配置后不生效

问题遇到的现象和发生背景

我在Spring Security中配置了两个异常处理,一个是自定AuthenticationEntryPoint,一个是自定义AccessDeniedHandler。但发现无论抛什么异常都进入了AuthenticationEntryPoint。怎么样才能进入自定义AccessDeniedHandler呢?往下看

问题相关代码

认证代码

/**
 * 权限控制
 * 判断用户角色
 * @author 刘昌兴
 * 
 */
@Component
public class RoleOfAdminFilter implements AccessDecisionManager {
    /** 
     * @author 刘昌兴
     * @param authentication 调用方法的调用者(非空)
     * @param o 被调用的受保护对象
     * @param collection 与被调用的受保护对象关联的配置属性
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        //collection即是在UrlOfMenuJudgeRoleFilter中getAttributes返回的由角色组成的List<ConfigAttribute>
        for(ConfigAttribute configAttribute:collection){
            //当前url所需要的角色
            String urlNeedRole=configAttribute.getAttribute();
            //如果匿名可访问就不用匹配角色
            if("ROLE_anonymous".equals(urlNeedRole)){
                //如果未登录,提示登陆
                if(authentication instanceof AnonymousAuthenticationToken){
                    throw new AccessDeniedException("尚未登陆,请登录");
                }else{
                    return;//终止继续匹配角色
                }
            }
            //获得用户所授予的角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            //判断用户的角色是否满足访问该url的角色
            for(GrantedAuthority grantedAuthority:authorities){
                if(grantedAuthority.getAuthority().equals(urlNeedRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足!");
    }
 
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }
 
    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}
 
 
 

自定义AuthenticationEntryPoint

/**
 * 用户未登录或token失效时的返回结果
 * @author 刘昌兴
 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
 
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter=response.getWriter();
        ResultBean resultBean=ResultBean.error(authException.getMessage(), null);
        resultBean.setCode(401);
        printWriter.write(new ObjectMapper().writeValueAsString(resultBean));
        printWriter.flush();
        printWriter.close();
    }
    
}
 

自定义AccessDeniedHandler

/**
 * 没有权限访问时返回的结果
 * @author 刘昌兴
 * 
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
 
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter=response.getWriter();
        ResultBean resultBean=ResultBean.error("权限不足,请联系管理员!", null);
        resultBean.setCode(403);
        printWriter.write(new ObjectMapper().writeValueAsString(resultBean));
        printWriter.flush();
        printWriter.close();
        
    }
 
}
 
 

Spring Security配置

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
/*            .antMatchers("/login","/doc.html","/swagger-resources/**",
                    "/v2/api-docs/**","/webjars/**","/capture","/test/**","/ws/**","/logOut",
                    "/admins/userFaces","/index.html","/css/**","/js/**","/fonts/**").permitAll()//放行相关请求和资源*/
            .anyRequest().authenticated()//除了上面的其他都需要认证
            .withObjectPostProcessor(getObjectPostProcessor())//动态权限配置
            .and()
            .addFilterBefore(getJwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)//添加登陆过滤器
            .exceptionHandling()//添加异常处理过滤器
            .accessDeniedHandler(restfulAccessDeniedHandler)//访问拒绝处理器
            .authenticationEntryPoint(restAuthenticationEntryPoint)//权限异常过滤器
            .and()
            .csrf().disable()//使用jwt,不需要使用csrf拦截器
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//不需要使用session
            .and()
            .headers().cacheControl();//禁用缓存
    }
 
原因分析

在ExceptionTranslationFilter源码中有如下代码

    private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
            FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
        if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
            if (logger.isTraceEnabled()) {
                logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
                        authentication), exception);
            }
            sendStartAuthentication(request, response, chain,
                    new InsufficientAuthenticationException(
                            this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
                                    "Full authentication is required to access this resource")));
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
                        exception);
            }
            this.accessDeniedHandler.handle(request, response, exception);
        }
    }
 

如果程序抛出了AccessDeniedException但是当前认证状态是匿名的(未认证),那么会ExceptionTranslationFilter会抛出InsufficientAuthenticationException。而所有的AuthenticationException会被配置的AuthenticationEntryPoint实现类(RestAuthenticationEntryPoint)捕获。
所以通过抛出AccessDeniedException进入自定义AccessDeniedHandler(RestfulAccessDeniedHandler)的前提是当前已完成身份认证。
修改后的认证代码

/**
 * 权限控制
 * 判断用户角色
 * @author 刘昌兴
 * 
 */
@Component
public class RoleOfAdminFilter implements AccessDecisionManager {
    /** 
     * @author 刘昌兴
     * @param authentication 调用方法的调用者(非空)
     * @param o 被调用的受保护对象
     * @param collection 与被调用的受保护对象关联的配置属性
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        //collection即是在UrlOfMenuJudgeRoleFilter中getAttributes返回的由角色组成的List<ConfigAttribute>
        //如果未登录,提示登陆
        if(authentication instanceof AnonymousAuthenticationToken){
            throw new BadCredentialsException("尚未登陆");
        }
        for(ConfigAttribute configAttribute:collection){
            //当前url所需要的角色
            String urlNeedRole=configAttribute.getAttribute();
            //如果URL登录即可访问就不用匹配角色
            if("ROLE_login".equals(urlNeedRole)){
                return;
            }
            //获得用户所授予的角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            //判断用户的角色是否满足访问该url的角色
            for(GrantedAuthority grantedAuthority:authorities){
                if(grantedAuthority.getAuthority().equals(urlNeedRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足");
    }
 
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }
 
    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}
 

总结:如果想通过抛出AccessDeniedException异常进入自定义AccessDeniedHandler那么当前认证状态不应该是匿名的。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玖语巴黎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值