• 0

  • 494

Spring Security(2)重要类探究

黑猫

我不是黑客

1星期前

SpringBoot MVC 拦截器中,曾讲过过滤器与拦截器的区别,Spring Security就是一个利用过滤器实现拦截的复杂框架。

客户端发送一个请求后,Spring Security会在进入DispatcherServlet类前添加很多的过滤器,获取用户信息、登录验证、权限验证等都可以在这些过滤器里完成。

获取过滤器:FilterChainProxy

FilterChainProxy->getFilters():
    for (SecurityFilterChain chain : filterChains) {
        if (chain.matches(request)) {
            return chain.getFilters();
        }
    }

    return null;
复制代码

filterChains值如下,都是在SecurityConfig类配置的,/hello/test没有过滤器,像是不需要登录的接口都可以这样配置。

获取凭证:SecurityContextPersistenceFilter

SecurityContextPersistenceFilter->doFilter():
    SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
HttpSessionSecurityContextRepository->loadContext():
    HttpServletRequest request = requestResponseHolder.getRequest();
    HttpServletResponse response = requestResponseHolder.getResponse();
    HttpSession httpSession = request.getSession(false);

    SecurityContext context = readSecurityContextFromSession(httpSession);
    ......
复制代码

凭证是根据session获取到的,这也就是为什么一段时间内,一次验证通过,后续就不需要再验证的原因了。

匿名凭证:AnonymousAuthenticationFilter

AnonymousAuthenticationFilter->doFilter():
    if (SecurityContextHolder.getContext().getAuthentication() == null) {
        SecurityContextHolder.getContext().setAuthentication(
                createAuthentication((HttpServletRequest) req));
复制代码

如果没有在session中获取到凭证,那么认为是匿名访问的。

授权、投票:FilterSecurityInterceptor

接下来执行SecurityFilter中自定义的逻辑,一切顺利的话,进入FilterSecurityInterceptor做安全检查

FilterSecurityInterceptor->invoke():
    InterceptorStatusToken token = super.beforeInvocation(fi);
AbstractSecurityInterceptor->beforeInvocation():
    this.accessDecisionManager.decide(authenticated, object, attributes);
复制代码

这里的accessDecisionManager即为授权器,默认是AffirmativeBased,不同授权器的规则不一样,AffirmativeBased的规则是:只要有一个投同意,就通过;如果有反对票,就拒绝授权;如果设置不允许弃权,也不通过。详细代码可以看它的decide()方法。

投票器默认是WebExpressionVoter,如果是匿名凭证的情况下,它会投反对票。

当这个filter通过后,后面就进入MVC流程了。

存储凭证:SecurityContextPersistenceFilter

还是在这个过滤器,在它的finally方法中,不论执行成功或失败,都会将这一次凭证的值存入到session中,这也就是为什么下一次操作可以从session中获取凭证的原因了

  SecurityContextPersistenceFilter->doFilter():
    finally {
        SecurityContext contextAfterChainExecution = SecurityContextHolder
                .getContext();
        // 当前线程清除凭证相关信息        
        SecurityContextHolder.clearContext();
        repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                holder.getResponse());
复制代码

异常处理:ExceptionTranslationFilter

在用户自定义过滤器中,如果不满足相关的设定(如权限不足、登录次数上限等),可以抛出异常,都会在这个过滤器捕捉到

ExceptionTranslationFilter->doFilter():
    catch (Exception ex) {
        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);
            }
            // 处理security异常
            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);
        }
    }
复制代码

根据抛出的异常调用不同的处理方法,通过SecurityConfig类配置完成,如果抛出的是AccessDeniedException相关的异常,调用accessDeniedHandler(一般返回403);如果抛出的是AuthenticationException相关的异常,调用authenticationEntryPoint方法(一般返回401)

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

信息安全

494

相关文章推荐

未登录头像

暂无评论