目录
SpringSecurity的实现原理
Spring Security能完成各种认证和授权功能其实是依赖其底层的多个过滤器进行实现的,借用官方的一张原理图如下
但是实际上这些Filter并不是直接配置在tomcat中,其实他们都由一个叫做FilterChainProxy的类进行管理
这些Filter包括Spring Security默认生成的以及我们自己自定义的,通通都被封装成成一个个FilterChain并且保存在FilterChainProxy的一个成员变量中,一个FilterChain包含多个Filter,
FilterChainProxy包含多个FilterChain
原理图和代码如下:
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
private List<SecurityFilterChain> filterChains; //多个过滤器链
private FilterChainProxy.FilterChainValidator filterChainValidator;
private HttpFirewall firewall;
}
当一个请求过来的时候,Spring Security会根据规则匹配到第一个过滤器链,并使用它过滤该请求
这里又引申出一些问题,既然存在多过滤链的情况,那么用户怎么配置多个过滤链,并且SpringSecurity是怎么根据请求选择相应的过滤器链
如何配置多个过滤器链
其实配置多个过滤器链并不难,项目中每存在一个继承了WebSecurityConfigurerAdapter类的配置类,Spring Securiy就会生成一个过滤器链;
继承ResourceServerConfigurerAdapter类的配置类也会对应一个过滤器链,但是与继承了WebSecurityConfigurerAdapter类的配置类不同,
多个继承ResourceServerConfigurerAdapter类的配置类都共存于一个过滤器链中,也就是无论配置多少个ResourceServerConfigurerAdapter也仅仅存在一个对应的过滤链。
同时需要注意的是,存在多个WebSecurityConfigurerAdapter配置类的时候,需要用@Order进行区分,代表了它们之间的优先级
配置代码如下
@Configuration
@Order(200)
public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("DefaultWebSecurityConfig "+http);
}
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("WebSecurityConfig "+http);
}
}
@Configuration
public class ResourceSecurityConfig {
public static final String RESOURCE_ID = "resource-server";
@Resource
private ResourceServerTokenServices resourceServerTokenServices;
@Configuration
@EnableResourceServer
public class FirstResourceConfig extends ResourceServerConfigurerAdapter{
//配置token服务以及资源ID
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenServices(resourceServerTokenServices);
}
//配置访问控制
@Override
public void configure(HttpSecurity http) throws Exception {
System.out.println("FirstResourceConfig "+http);
http
.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('server')")
;
}
}
@Configuration
@EnableResourceServer
public class SecondResourceConfig extends ResourceServerConfigurerAdapter{
//配置token服务以及资源ID
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenServices(resourceServerTokenServices);
}
//配置访问控制
@Override
public void configure(HttpSecurity http) throws Exception {
System.out.println("SecondResourceConfig "+http);
http
.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('server')")
;
}
}
}
上面的代码就是配置了三个过滤器链,最后一个虽然好像配置了两个过滤器链,但是前面已经说过了ResourceServerConfigurerAdapter的配置类其实都会被合并在同一个过滤器链
那我们怎么知道的的确确存在三个过滤器链呢,过滤器链都存在FilterChainProxy的成员变量中,我们只要debug一下看看它的成员变量就知道了,如下图:
每一个FilterChain都包含一个RequestMatcher,如下图
实际上过滤器链的RequestMatcher是和我们的配置有关系,前面我们的配置都是使用了.authorizeRequests()方法,其实这个就是代表AnyRequestMatcher这个类
我们改变一下配置可以看看RequestMatcher会有什么变化
@Configuration
@Order(200)
public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("DefaultWebSecurityConfig "+http);
http.antMatcher("/user")
.authorizeRequests().anyRequest().permitAll()
.and()
.formLogin();
}
}
我们把第一个配置类的规则改变了,看看debug会发生什么变化
它变成了AntPathRequestMatcher,这里可以看出我们的配置会直接影响到SpringSecurity为我们选择合适的过滤器链,如果改成上面的配置,其实就代表
DefaultWebSecurityConfig对应的过滤器链只匹配/user的请求,而其他两个过滤器链就是匹配/**的请求,虽然两个过滤器链都是匹配相同的地址,
但是他们的优先级不同,一般ResourceServerConfigurerAdapter类默认的优先级是@Order(3),而WebSecurityConfigurerAdapter类默认是@Order(100)
优先级的值越低就越先被匹配到,所以ResourceServerConfigurerAdapter会被先匹配到
如何匹配多个过滤器链
要知道SpringSecurity是怎样匹配一个合适的过滤链,我们先看下面的代码:
private List<Filter> getFilters(HttpServletRequest request) {
Iterator var2 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var2.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var2.next();
} while(!chain.matches(request)); //这里就是匹配过滤器链的方法
return chain.getFilters();
}
chain.matches()就是匹配一个过滤器链的方法,我们继续进入该方法:
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);//实际上是调用了requestMatcher的matches方法
}
}
实际上是调用了RequestMatcher的matches方法,所以其实SpringSecurity在对一个请求选择一个过滤器链,就是通过调用每个过滤器链的RequestMatcher进行匹配
匹配到第一个成功的就是处理该请求的过滤器链
总结
所以我们可以得出SpringSecurity在处理一个请求的流程
1.获取该Request的URI地址
2.遍历所有FilterChain,并且用它的RequestMatcher匹配Request的URI地址
3.匹配到第一个合适的FilterChain,把该请求放到该过滤器链进行处理
4.过滤器链中的每一个过滤器获得请求,并对请求做相应的处理