[12] ExceptionTranslationFilter

ExceptionTranslationFilter是Spring Security用于处理授权认证异常的关键组件。它在过滤器链中捕获并处理特定的Spring Security异常,如AuthenticationException和AccessDeniedException。用户可以自定义accessDeniedHandler和authenticationEntryPoint来定制异常处理。文章详细分析了代码执行步骤,尤其是在资源认证服务配置中如何注入自定义异常处理器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ExceptionTranslationFilter

介绍

Spring Security授权认证服务的异常处理不能像常规Spring MVC或者Spring Boot那样进行统一异常处理,而是在过滤器上进行了层层拦截,代码阅读起来也有些费劲。而该Filter是用于处理Spring Security部分异常的,开发者可以自定义accessDeniedHandler和authenticationEntryPoint进行配置,根据自己的需求进行异常处理。

代码分析

步骤1

上篇文章已经提及过Spring Security认证服务配置分ResourceServerConfigurerAdapter(资源认证服务配置)和AuthorizationServerSecurityConfigurer(授权认证服务配置),对于授权认证服务,作者还没找到办法自定义注入accessDeniedHandler和authenticationEntryPoint,我们可以看一下WebSecurityConfigurerAdapter的部分代码:

protected final HttpSecurity getHttp() throws Exception {
    if (http != null) {
        return http;
    }

    DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
            .postProcess(new DefaultAuthenticationEventPublisher());
    localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

    AuthenticationManager authenticationManager = authenticationManager();
    authenticationBuilder.parentAuthenticationManager(authenticationManager);
    authenticationBuilder.authenticationEventPublisher(eventPublisher);
    Map<Class<?>, Object> sharedObjects = createSharedObjects();

    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
            sharedObjects);
    if (!disableDefaults) {
        // @formatter:off
        http
            .csrf().and()
            .addFilter(new WebAsyncManagerIntegrationFilter())
            //添加一个ExceptionHandlingConfigurer,configurer是new出来的,未注入accessDeniedHandler和authenticationEntryPoint
            .exceptionHandling().and()
            .headers().and()
            .sessionManagement().and()
            .securityContext().and()
            .requestCache().and()
            .anonymous().and()
            .servletApi().and()
            .apply(new DefaultLoginPageConfigurer<>()).and()
            .logout();
        // @formatter:on
        ClassLoader classLoader = this.context.getClassLoader();
        List<AbstractHttpConfigurer> defaultHttpConfigurers =
                SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

        for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
            http.apply(configurer);
        }
    }
    configure(http);
    return http;
}

configure(http)这行代码将执行AuthorizationServerSecurityConfiguration#configure()方法

protected void configure(HttpSecurity http) throws Exception {
    //这里的configurer是new出来的
    AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
    FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
    http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
    configure(configurer);
    //这里也为改变ExceptionHandlingConfigurer的accessDeniedHandler和authenticationEntryPoint
    //只是调用的自定义的AuthorizationServerConfigurerAdapter
    http.apply(configurer);
    String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
    String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
    String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
    if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
        UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
        endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
    }
    // @formatter:off
    http
        .authorizeRequests()
            .antMatchers(tokenEndpointPath).fullyAuthenticated()
            .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
            .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
    .and()
        .requestMatchers()
            .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
    .and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
    // @formatter:on
    http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}

http.apply(configurer)这行代码会执行到我们自定义的AuthorizationServerConfigurerAdapter#configure(AuthorizationServerSecurityConfigurer security)方法,如下:

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security
        .tokenKeyAccess(TokenAccess.PERMIT_ALL)
        .checkTokenAccess(TokenAccess.PERMIT_ALL)
        .allowFormAuthenticationForClients()
        //这里的配置之会改变AuthorizationServerSecurityConfiguration中对应的对象
        .accessDeniedHandler(new CarpAccessDeniedHandler(objectMapper))
        .authenticationEntryPoint(new AuthorizeAuthExceptionEntryPoint(objectMapper))
        //使用ObjectPostProcessor也不行
        .addObjectPostProcessor(new ObjectPostProcessor<ExceptionHandlingConfigurer>() {
            @Override
            public <O extends ExceptionHandlingConfigurer> O postProcess(O object) {
                object.accessDeniedHandler(new CarpAccessDeniedHandler(objectMapper));
                return (O) object.authenticationEntryPoint(new AuthorizeAuthExceptionEntryPoint(objectMapper));
            }
        });
}

笔者在授权认证服务上注入自定义accessDeniedHandler和authenticationEntryPoint失败,但是比如我们的业务系统引入的是资源认证配置,是可以通过如下的方法注入的,配置如下:

public void configure(HttpSecurity http) throws Exception {
    http.exceptionHandling().authenticationEntryPoint(new AuthorizeAuthExceptionEntryPoint(objectMapper));
    http.exceptionHandling().accessDeniedHandler(new CarpAccessDeniedHandler(objectMapper));
}

步骤2

ExceptionTranslationFilter过滤执行时对异常进行捕获,并从异常堆栈中提取了SpringSecurityException,针对AuthenticationException以及AccessDeniedException过滤器做了特殊化处理。有一点需要注意的是,Spring Security在很多地方都做异常的捕获,并对异常做了转换,意味着你的业务代码抛出的异常很可能被Spring Security转换,doFilter()核心代码如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    try {
        chain.doFilter(request, response);
    }
    catch (IOException ex) {
        throw ex;
    }
    catch (Exception ex) {
        // Try to extract a SpringSecurityException from the stacktrace
        //从异常堆栈中提取SpringSecurityException
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
        //找到第一个AuthenticationException异常
        RuntimeException ase = (AuthenticationException) throwableAnalyzer
                .getFirstThrowableOfType(AuthenticationException.class, causeChain);

        if (ase == null) {
            //找到第一个AccessDeniedException异常
            ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                    AccessDeniedException.class, causeChain);
        }

        if (ase != null) {
            if (response.isCommitted()) {
                throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
            }
            //处理异常
            handleSpringSecurityException(request, response, chain, ase);
        }
        else {
            if (ex instanceof ServletException) {
                throw (ServletException) ex;
            }
            else if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            }
            throw new RuntimeException(ex);
        }
    }
}

private void handleSpringSecurityException(HttpServletRequest request,
        HttpServletResponse response, FilterChain chain, RuntimeException exception)
        throws IOException, ServletException {
    if (exception instanceof AuthenticationException) {
        //身份认证异常
        sendStartAuthentication(request, response, chain,
                (AuthenticationException) exception);
    }
    else if (exception instanceof AccessDeniedException) {
        //访问受限异常
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //匿名身份认证信息或者记住我身份认证信息
        if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
            sendStartAuthentication(
                    request,
                    response,
                    chain,
                    new InsufficientAuthenticationException(
                        messages.getMessage(
                            "ExceptionTranslationFilter.insufficientAuthentication",
                            "Full authentication is required to access this resource")));
        }
        else {
            //访问受限异常处理
            accessDeniedHandler.handle(request, response,
                    (AccessDeniedException) exception);
        }
    }
}

protected void sendStartAuthentication(HttpServletRequest request,
        HttpServletResponse response, FilterChain chain,
        AuthenticationException reason) throws ServletException, IOException {
    // SEC-112: Clear the SecurityContextHolder's Authentication, as the
    // existing Authentication is no longer considered valid
    //清空SecurityContextHolder的Authentication
    SecurityContextHolder.getContext().setAuthentication(null);
    //缓存请求
    requestCache.saveRequest(request, response);
    logger.debug("Calling Authentication entry point.");
    //类似切点,处理请求以及响应
    authenticationEntryPoint.commence(request, response, reason);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值