Spring Security Servlet 概览

原文在 GitHub Pages 上,内容无差异spring

Spring Security 是 Spring 框架中用于实现 Security 相关需求的项目。咱们能够经过使用这个框架来实现项目中的安全需求。segmentfault

今天这篇文章将会讨论 Spring Security Servlet 是如何工做的。安全

之因此将内容限定到 Servlet,是由于如今 Spring Security 已经开始支持 Reactive Web Server,由于底层的技术不一样,固然须要分开讨论。架构

Spring Security 在哪里生效

咱们知道,在 Servlet 中,一次请求会通过这样的阶段: client -> servlet container -> filter -> servletmvc

而 Spring MVC 虽然引入了一些其余概念,但总体流程差异不大:框架

Spring Security 则是经过实现了 Filter 来实现的 Security 功能。这样一来,只要使用了 Servlet Container,就可使用 Spring Security,不须要关心有没有使用 Spring Web 或别的 Spring 项目。ide

DelegatingFilterProxy

这是 Spring Security 实现的一个 Servlet Filter。它被加入到 Servlet Filter Chain 中,将 filter 的任务桥接给 Spring Context 管理的 bean。svg

FilterChainProxy

这是被 DelegatingFilterProxy 封装的一个 Filter,其实也是一个代理。这个类维护了一个 List<SecurityFilterChain>,它会将请求代理给这个 list 进行 filter 的工做。spa

但这个代理不是遍历整个 list,而是经过 RequestMatcher 来判断是否要使用这一个 SecurityFilterChain。咱们配置时写的 mvcMatchers 之类的方法就会影响到这里的判断。线程

SecurityFilterChain

这个接口的实现维护了一个 Filter 列表,这些 Filter 是真正进行 filter 工做的类,好比 CorsFilterUsernamePasswordAuthenticationFilter 等。

上面提到的 RequestMatcher 是这个接口的默认实现使用的。

综上,咱们能够获得一个 big picture:

处理 Security Exception

这里说的 Security Exception,其实只有两种:AuthenticationExceptionAccessDeniedException。它们会在 ExceptionTranslationFilter 中被处理,而这个 Filter 每每被安排在 SecurityFilterChain 的最后。

AuthenticationException

这个异常表明身份认证失败。ExceptionTranslationFilter 会调用 startAuthentication 方法处理它,其流程是:

  1. 清理 SecurityContextHolder 中的身份信息(后面的身份认证内容会涉及)
  2. 将当前请求保存到 RequestCache 中,当用户经过身份验证后,会从其中取出当前请求,继续业务流程
  3. 调用 AuthenticationEntryPoint,要求用户提供身份信息。方式能够是重定向到登录页面,也能够是返回携带 WWW-Authenticate header 的 HTTP 响应

AccessDeniedException

这个异常表明受权失败,意味着当前用户的身份已确认,但被服务拒绝了请求。

ExceptionTranslationFilter 会将这个异常交给 AccessDeniedHanlder 处理。默认的实现会重定向到 /error,并获得一个 403 响应。


了解了 Spring Security 在哪里生效以后,咱们再来看看两个重要的问题:身份认证和受权。

身份认证

SecurityContextHolder

SecurityContextHolder 是保存身份信息的地方,默认经过 ThreadLocal 的方式保存 SecurityContext。能够经过静态方法 SecurityContextHolder.getSecurityContext() 获取当前线程的 SecurityContext

SecurityContextHolder.getSecurityContext() 方法虽然是静态的,能够在任何地方调用。但我的不建议这么作,而是应该做为参数传递给使用到的方法,避免当前的 SecurityContext 成为隐式输入。

SecurityContext 是一个接口,提供 getAuthentication 方法获取当前用户信息;setAuthentication 设置当前用户信息。

Authentication 也是一个接口,它的实现保存了当前用户的信息。在身份验证的流程中,老是在围绕着 Authentication 操做 —— 经过 PrincipalCredentials 判断用户身份、经过调用 setAuthenticated 方法保存身份认证是否经过的结果。

另外,在身份验证成功后,Authentication 中还保存了 GrantedAuthority 的集合,表示当前用户的角色和权限,用于后续的受权操做。

AuthenticationManager

AuthenticationManager 提供了 authenticate() 方法用于进行身份验证,但并非它本身完成,而是经过 AuthenticationProvider 完成。

AuthenticationProvider 提供 support(Authentication) 方法用于判断是否可以验证这种类型的 Authentication

AuthenticationManager 的实现 ProviderManager 中保存了 List<AuthenticationProvider>。它会按顺序调用支持当前 Authentication 类型的 AuthenticationProviderauthenticate 方法,直到身份验证成功(返回值 non-null)或所有失败。

在这个过程当中出现的 AuthenticationException 将会被上面提到的 ExceptionTranslationFilter 处理。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter.doFilter() 方法实现了身份验证的流程,包括成功和失败的处理。

它提供了一个抽象方法 attemptAuthentication() 用于身份验证。子类能够调用它的 authenticationManager 来实现 authenticate 的功能。

总体流程如图:

其中的 1 & 2 都在 attemptAuthentication() 方法中完成,须要子类实现。

3 经过 successfulAuthentication() 方法实现,能够被子类重写。

4 中除 SessionAuthenticationStrategy 外都交给 unsuccessfulAuthentication() 方法处理,一样能够被子类重写。

考虑到愈来愈多的应用都是基于无状态的 RESTful API,因此 SessionAuthenticationStrategy 不会在本文涉及

受权

在 Servlet 中受权

Spring Security 受权的入口有不少处,关注到 Servlet 上的话,那就是 FilterSecurityInterceptor 这个 Filter。他会被配置到全部的 AbstractAuthenticationProcessingFilter 子类以后,这样他就能从 SecurityContextHodler 中获得 Authentication,用以进行受权。

AccessDecisionManager

受权的过程,被交给 AccessDecisionManager 实现,他的 decide 方法接收三个参数:

  • Authentication:这就是从 SecurityContextHolder 中拿到的对象
  • secureObject:这是一个 Object 类型,对于 FilterSecurityIntercepter 来讲,会用 request、response 和 filterChain 建立一个 FilterInvocation 对象做为 secureObject
  • Collection<ConfigAttribute>FilterSecurityIntercepter 使用 ExpressionBasedFilterInvocationSecurityMetadataSource 保存这些 ConfigAttribute,这些值用来给 AccessDecisionManager 提供作判断的信息

AccessDecisionManager 天然也不是包含具体的判断逻辑的角色,真正根据上面三个参数来受权的类,实际上是 AccessDecisionVoter

AccessDecisionVoter

AccessDecisionVoter 提供一个 vote 方法,接收上面的 decide 方法同样的参数。

他的实现包括 RoleVoterAuthenticationVoter。顾名思义,分别是根据角色和权限信息来判断是否受权的实现。而_什么样的角色/权限能够访问这个对象_则是经过 ConfigAttribute 传入的。

无论具体的 Voter 实现如何,最终会返回一个 int,只有 -一、0、1 三个值,分别表示拒绝、弃权、赞成。

一个 AccessDecisionManager 会管理多个 AccessDecisionVoter,最终会根据全部 voter 的结果来判断是受权成功,仍是抛出 AccessDeniedException

具体判断的策略则是交给了 AccessDecisionManager 的三个实现来决定:

ConsensusBased
像通常的比赛投票同样,票多的结果就是最终决定。
能够配置票数相等(不是所有弃权)时,结果是否经过,默认值是容许经过。
也能够配置所有弃权时,结果是否经过,默认值是不容许。

AffirmativeBased
只要有一个 voter 赞成,就容许经过。
一样能够配置所有弃权时的决定,默认也是不容许。

UnanimousBased
要求全部 voter 一致赞成时才经过。
一样能够配置所有弃权时的决定,默认也是不容许。

AbstractSecurityInterceptor

到此,受权用到的核心类基本介绍完了,让咱们回过头来想一个问题:FilterSecurityInterceptor 明明是一个 Filter,为何要叫作 Interceptor

若是回顾上面介绍的这些类,你会发现只有 FilterSecurityInterceptor 经过实现 Filter 接口和 Servlet 绑定了起来,AccessDecisionManagerAccessDecisionVoter 都没有和 Servlet 绑定。

这么作的目的就是为了能支持 Method Security 和 AspectJ Security,这样就能复用真正作受权逻辑的代码。

咱们能够看到 FilterSecurityInterceptor 扩展了 AbstractSecurityInterceptor。而这个父类的另外两个实现 MethodSecurityInterceptorAspectJMethodSecurityInterceptor 都是非 Servlet 的实现。由此便作到了对不一样的受权方式的支持,而且复用了代码。


关于受权,还有一个很重要的 ACL 没有提到,它并无影响整个受权的架构,这里就不写了,之后有空再说吧。

总结

这篇文章梳理了 Spring Security 在 Servlet 中的代码架构,构建了一个 big picture。

经过这篇文章,咱们了解到,在请求到达真正处理业务的 Controller 以前,经历了:

  • 各类 AbstractAuthenticationProcessingFilter 过滤请求,交给 AuthenticationManager 管理的 AuthenticationProvider 尝试不一样的身份认证方式

    • 最终获得一个保存在 SecurityContextHolder 中的 Authentication 对象
    • 或者没法肯定身份的状况下抛出 AuthenticationException
  • FilterSecurityInterceptor 过滤,使用先前建立的 Authentication 对象交给 AccessDecisionManager 受权

    • 最终成功调用业务方法
    • 或者抛出 AccessDeniedException
  • 上面抛出的 AuthenticationExceptionAccessDeniedException 将会被 ExceptionTranslationFilter 处理,转化成 401 和 403 的响应。

有了这个 big picture,在接下来研究细节的时候,就不至于摸不着头脑了。

相关文章
相关标签/搜索