spring security异常捕捉过滤器ExceptionTranslationFilter源码详解

最近在写后台过程中,需要用到springsecurity安全框架来完成登录及权限认证,因为是前后端分离所以需要捕捉异常,如未登录、登录失败、登录超时、缺少权限等信息,所以自定义返回数据。
这些异常在springsecurity中统一由ExceptionTranslationFilter过滤器进行捕捉,所以我们就来分析一下此过滤器源码达到我们要求。

源码解析

1.我们首先看他的构造方法 在全参构造中 我们看到 分别导入
AuthenticationEntryPoint,accessDeniedHandler,authenticationTrustResolver 我们主要看前两者个,这两者是异常捕捉的重点。
AuthenticationEntryPoint:顾名思义身份验证入口,主要用来判断你的身份,凡是在身份认证过程中发生的错误
accessDeniedHandler:访问拒绝处理 就是你要访问某个资源,但是当你没有访问权限时,就会抛出异常,在此类中进行处理。
authenticationTrustResolver :用以判断SecurityContextHolder中所存储信息 判断上下文中有无用户信息来抛出异常

private AccessDeniedHandler accessDeniedHandler;
	//AuthenticationEntryPoint 登录认证异常处理
    private AuthenticationEntryPoint authenticationEntryPoint;
    //用以判断SecurityContextHolder中所存储信息
    private AuthenticationTrustResolver authenticationTrustResolver;
    private ThrowableAnalyzer throwableAnalyzer;
    private RequestCache requestCache;
    private final MessageSourceAccessor messages;
	
    public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
        this(authenticationEntryPoint, new HttpSessionRequestCache());
    }

    public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint, RequestCache requestCache) {
        this.accessDeniedHandler = new AccessDeniedHandlerImpl();
        this.authenticationTrustResolver = new AuthenticationTrustResolverImpl();
        this.throwableAnalyzer = new ExceptionTranslationFilter.DefaultThrowableAnalyzer();
        this.requestCache = new HttpSessionRequestCache();
        this.messages = SpringSecurityMessageSource.getAccessor();
        Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
        Assert.notNull(requestCache, "requestCache cannot be null");
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.requestCache = requestCache;
    }
在过滤器中最主要是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);
            this.logger.debug("Chain processed normally");
        } catch (IOException var9) {
            throw var9;
        } catch (Exception var10) {
        //Throwable是exception的父类  获取异常
            Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
            //判断异常的类型 是否为AuthenticationException 
            RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
            //如果不是AuthenticationException异常 再去判断是否为AccessDeniedException
            if (ase == null) {
                ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
            }
			//如果两者都不是 判断是否为其他类型异常 并将其抛出 因为此过滤器只处理AuthenticationException和AccessDeniedException异常
            if (ase == null) {
                if (var10 instanceof ServletException) {
                    throw (ServletException)var10;
                }

                if (var10 instanceof RuntimeException) {
                    throw (RuntimeException)var10;
                }

                throw new RuntimeException(var10);
            }

            if (response.isCommitted()) {
                throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10);
            }
			//最后去处理这两种异常
            this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
        }

    }
在dofilter中调用handleSpringSecurityException方法 去处理异常

我们可以看到 此方法中对 这两种异常采用两种方式处理

如果抛出的异常是AuthenticationException,则执行方法sendStartAuthentication
如果抛出的异常是AccessDeniedException,且从SecurityContextHolder.getContext().getAuthentication()得到的是AnonymousAuthenticationToken和RememberMeAuthenticationToken,那么执行sendStartAuthentication
如果上面的第二点不满足,则执行accessDeniedHandler的handle方法

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
//异常属于AuthenticationException
        if (exception instanceof AuthenticationException) {
            this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
            this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
        } else if (exception instanceof AccessDeniedException) {
        //异常属于AccessDeniedException
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            //判断authentication 是不是AnonymousAuthenticationToken和RememberMeAuthenticationToken类型
            if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
            //如果不是 执行accessDeniedHandler.handle 是权限的问题
                this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
                //可以通过继承accessDeniedHandler 来实现handle方法 达到返回权限不足信息
                this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
            } else {
            //如果是则执行sendStartAuthentication 是登录认证的问题
                this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
                this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
            }
        }

    }
sendStartAuthentication方法 登录认证问题的解决
  protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
        SecurityContextHolder.getContext().setAuthentication((Authentication)null);
        this.requestCache.saveRequest(request, response);
        this.logger.debug("Calling Authentication entry point.");
        //调用authenticationEntryPoint子类的commence方法  我们自定义authenticationEntryPoint的子类
        this.authenticationEntryPoint.commence(request, response, reason);
    }
我们自定义authenticationEntryPoint
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        //返回json形式的错误信息
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json");
        Object principal = null;
        SecurityContext context = SecurityContextHolder.getContext();
        //获取认证信息
        Authentication authentication = context.getAuthentication();
        if (authentication == null) {
            httpServletResponse.getWriter().println("{\"state\":201,\"msg\":\"未登录\"}");
        } else {
            //Principal: 重要的 --- UserDetails(用户详情)
            principal = authentication.getPrincipal();
            if ("anonymousUser".equals(principal.toString())) {
                if (httpServletRequest.getCookies() != null) {
                    httpServletResponse.getWriter().println("{\"state\":202,\"msg\":\"登录超时\"}");
                }
                httpServletResponse.getWriter().println("{\"state\":201,\"msg\":\"未登录\"}");
            }
        }


        httpServletResponse.getWriter().flush();


    }

}
我们自定义AccessDeniedHandler 返回

用来返回权限不足的信息

public class MyAccessDeineHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("text/javascript;charset=utf-8");
        httpServletResponse.getWriter().print("{\"state\":203,\"msg\":\"没有权限访问\"}");
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Spring Security是一个用于保护Java应用程序的框架,它提供了一系列过滤器来处理安全相关的任务。在Spring Security过滤器被组织成一个过滤器链(Filter Chain),它根据配置的顺序依次执行。以下是一些常用的Spring Security过滤器及其功能: 1. SecurityContextPersistenceFilter:用于在请求处理期间存储和检索SecurityContext,即当前用户的安全上下文。 2. LogoutFilter:处理用户注销请求,并清除当前用户的认证信息。 3. UsernamePasswordAuthenticationFilter:用于处理基于用户名和密码的认证请求。 4. ConcurrentSessionFilter:用于处理并发会话控制,限制同一用户的同时登录数。 5. BasicAuthenticationFilter:用于处理基于HTTP基本身份验证的认证请求。 6. RequestCacheAwareFilter:用于缓存请求,以便在重定向后重新发送请求。 7. SecurityContextHolderAwareRequestFilter:用于包装请求,使其能够识别特定的安全上下文。 8. AnonymousAuthenticationFilter:用于在请求上下文添加匿名用户的认证信息。 9. SessionManagementFilter:用于处理会话管理,例如过期检查、并发控制等。 10. ExceptionTranslationFilter:用于捕获并处理异常,将其转换为合适的响应。 11. FilterSecurityInterceptor:用于实施访问控制,检查用户是否具有访问资源的权限。 这些过滤器可以根据具体的安全需求进行配置和组合,以提供所需的安全功能。通过指定不同的过滤器顺序、添加自定义过滤器或替换默认过滤器,您可以灵活地定制Spring Security过滤器链。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值