玖语巴黎 2022-05-09 09:09 采纳率: 75%
浏览 621
已结题

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

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

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

问题相关代码,请勿粘贴截图

自定义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();//禁用缓存
    }

运行结果及报错内容

img

  • 写回答

3条回答 默认 最新

  • 玖语巴黎 2022-05-10 09:36
    关注

    根据楼上提示,在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>
            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;
        }
    }
     
     
    
    

    修改后的认证代码

    /**
     * 权限控制
     * 判断用户角色
     * @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那么当前认证状态不应该是匿名的。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 5月10日
  • 已采纳回答 5月10日
  • 创建了问题 5月9日

悬赏问题

  • ¥15 电路原理(关键词-工作原理)
  • ¥15 yolov5s模型下载就卡住,没有运行结果
  • ¥15 请问代码技术们,云梦建站的这个坑你们踩过吗?
  • ¥20 androidstudio工具问题
  • ¥15 想问一些关于计量的问题
  • ¥15 关于c++外部库文件宏的问题,求解
  • ¥15 office打开卡退(新电脑重装office系统后)
  • ¥300 FLUENT 火箭发动机燃烧EDC仿真
  • ¥15 【Hadoop 问题】Hadoop编译所遇问题hadoop-common: make failed with error code 2
  • ¥15 vb6.0+webbrowser无法加载某个网页求解