从源码和文档理解SpringSecurity
从宏观上来看
SpringSecurity是用了一系列Filter,来处理权限的问题
在请求,达到Servlet和Controller之前,就进行权限的校验
让权限和业务逻辑彻底解耦
以下是讲解,SpringSecurity是怎么用Filter的
1. 容器是如何使用Filter的
SpringSecurity是基于Servlet的Filter
机制来实现的
所以先了解一下Filter是怎么工作的
下图表示了客户端一个请求,是怎么被Filter拦截的
-
当客户端发送一个请求给应用的时候, 容器会创建一个
FilterChain
里面包含了多个Filters
和一个处理这个请求的Servlet
-
在SpringMVC中
Servlet
就是DispatcherServlet
-
这种模型就是当一个请求
HttpServletRequest
过来的时候,对应着一个Servlet
和多个Filter
-
由于一个Filter只会影响下游的Filter和Servlet,所以Filter的顺序非常重要
//标准写法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
chain.doFilter(request, response); // invoke the rest of the application
//例如Tomcat使用的ApplicationFilterChain
//保存了所有的Filter,和要被访问的Servlet的实例
//详见 org.apache.catalina.core.ApplicationFilterChain#internalDoFilter
}
2. SpringSecurity如何融入容器的拦截器Filter机制? (DelegatingFilterProxy)
DelegatingFilterProxy
是一个容器直接使用的Filter
,文中其他的Filter
对容器来说是透明的,不可见的- 在容器的标准里,一个Filter想要注册到容器上,必须在容器启动之前就注册好,同时也意味着需要在Spring环境启动之前,就注册到容器上
- 也就导致了一个问题,当我们在Spring环境中动态创造了一个实现了
Filter
接口的Bean
,实际上是不能直接注册到容器的过滤器链FilterChain
上的 - 为了解决这个问题,Spring使用了代理模式,
DelegatingFilterProxy
就是一个代理,提前注册到了容器的FilterChain
上 - 然后当Spring环境启动好之后,再用
DelegatingFilterProxy
来调用Spring初始化后生成的各种Bean
- 在Spring环境中的
Filter
并没有直接加入到拦截器链上, 而是被一个DelegatingFilterProxy
代理了,相当于那么多的Filter
在容器看来只被一个DelegatingFilterProxy
处理了
下图表示了 DelegatingFilterProxy
在容器的Filter
中的位置:
3. Spring管理的Filter是怎么执行doFilter的呢? (FilterChainProxy )
FilterChainProxy
是个特殊Filter
,同时也是一个SpringBean,可以获取到所有的在Spring环境上注册的Bean
- 实际上
DelegatingFilterProxy
就是包装了这个FilterChainProxy
- 而关键就在于
SpringSecurity
中所有被注册为Filter
的Bean
,都被这个FilterChainProxy
管理了,但不是直接管理的,是通过一组SecurityFilterChain
来管理的
4. 选择合适的安全策略(SecurityFilterChain)
- 每个请求根据访问路径的不同,SpringSecurity会选择不同的安全策略
- 不同的安全策略则需要一套不同功能的
Filter
来拦截处理 - 所以
FilterChainProxy
会先查,哪个SecurityFilterChain
能处理这个url - 看源码:
org.springframework.security.web.FilterChainProxy#getFilters(javax.servlet.http.HttpServletRequest)
- 如果某个
SecurityFilterChain
匹配到了这个请求,那么就用它保存的一组Filter
来处理 - 通过这个
SecurityFilterChain
来获取一组Filter
,然后依次调用这些Filter
- 这组
Filter
由org.springframework.security.web.FilterChainProxy.VirtualFilterChain
连接起来
那么SecurityFilterChain
是如何管理安全策略的呢?
-
FilterChainProxy
中有多个SecurityFilterChain
,每一个SecurityFilterChain
都代表了一种安全策略
-
SecurityFilterChain
实例里面包含了一个RequestMatcher
用来匹配某个Request
,同时这个SecurityFilterChain
实例也包含了一组Filter
-
FilterChainProxy
在拦截到一个Request
的时候,会去看,哪个SecurityFilterChain
能匹配上这个Request
-
当某个
SecurityFilterChain
的实例匹配到了这个Request
,FilterChainProxy
会生成一个VirtualFilterChain
,来把SecurityFilterChain中的Filters
被串起来,依次执行 -
可以看看源码:
org.springframework.security.web.FilterChainProxy.VirtualFilterChain
-
详见
org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter
5. 有用的Filter
6. Filter的顺序
SpringSecurity起到作用的Filter分三种,排成以下顺序
- 普通的各种SpringSecurity提供的
Filter
,用来进行权限校验等行为 ExceptionTranslationFilter
倒数第二道Filter,主要用来捕获处理权限异常FilterSecurityInterceptor
最后一道Filter
- 他们之间是如何配合的呢?
-
- 各种
Filter
会把权限校验的结果存在某个地方
- 各种
-
FilterSecurityIntercepto
r会检查这个权限校验的结果
-
- 如果权限校验通过,则直接调用Controller或者指定html页面
-
- 如果没有校验通过,则抛出权限相关的异常,把异常信息带出
-
ExceptionTranslationFilter
捕获到权限相关的异常, 则转化为相对应的报错信息,返回给前端
//ExceptionTranslationFilter 伪代码:
try {
filterChain.doFilter(request, response); //直接由FilterSecurityInterceptor来处理,等待抛出异常
} catch (AccessDeniedException | AuthenticationException e) {
if (!authenticated || e instanceof AuthenticationException) {
startAuthentication(); //如果是非登录状态,则跳转登录逻辑,比如重定向至登录页
} else {
accessDenied(); //如果是权限不够,则进行某些处理,比如返回401
}
}