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
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值