前言
最近在弄微信的OAuth认证,我们认证服务及作为客户端又作为认证服务端。因此需要对spring cloud oauth的oauth流程做出相应的变动改造。在研究spring cloud oauth的源码时候,发现对spring security 的认证流程不清不楚,改造起来比较吃力。所以对spring security 的实现原理进行了研究学习。
本文对spring security 的启动流程以及配置源码做了简单分析,希望对大家理解以及以后的使用spring security 有个很好的帮助。
一切从filter开始
spring security 的核心就是filter,通过一层层的filter后,才访问到我们的资源信息。感觉就像打魂斗罗,一个关卡一个关卡的打过去,打完最后一关,OK,结局是啥我不记得了。spring security 的filter做着一层一层的拦截,把相关的权限做一层一层的验证。成功?走下层。失败?认证失败。spring security的所有的权限校验都是这样做的,一切的认证都在filter中,业务代码完全不知情。
URI与Filter的匹配
每个请求的uri就是个路由地址。spring security中,配置的其中一个部分就是 uri与filter对应关系。换句话就是,每个uri在程序启动的时候,就已经确定了他们所对应的filter列表。这样,每个请求过来,都会经过他所有对应的filter列表,来做一层层的认证授权。如下图。指定了三个uri(支持EL表达式)。/login 的请求经过自己的filter列表,/api/* 以及 /userinfo 也是如此。通过这样的方式来达到控制具体uri权限的能力。
严谨点说不止是uri,可以自定义匹配头信息啊或者其他的,http请求中可以用来区分的都可以做为匹配条件。这里为了方便理解。
Spring Security filter配置与原理
我们再根据源码分析下spring security 是如何加载存储filter,以及如何处理uri与filter的映射的。
- 指定了@EnableWebSecurity,就会加载 WebSecurityConfiguration 的配置;
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class, ObjectPostProcessorConfiguration.class,
SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
- 在WebSecurityConfiguration 中,会实例化 WebSecurity;
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
- WebSecurity中可以通过addSecurityFilterChainBuilder() 方法,把各种filter的SecurityBuilder添加进来,而每个SecurityBuilder的泛型上界必须是SecurityFilterChain;
public WebSecurity addSecurityFilterChainBuilder(
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
- WebSecurity的performBuild() 方法实例化 FilterChainProxy,FilterChainProxy存储着SecurityFilterChain列表;
@Override
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
logger.warn("\n\n"
+ "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}
- WebSecurity的performBuild()方法中将securityFilterChainBuilders列表里面的每个SecurityBuilder对象构建SecurityFilterChain实例,将SecurityFilterChain列表作为参数构建FilterChainProxy。
SecurityFilterChain接口定义了两个方法,一个是存储filter,一个是uri规则匹配。
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
- 请求到达的时候,FilterChainProxy的dofilter()方法,会遍历所有的SecurityFilterChain,对匹配到的url,则一一调用SecurityFilterChain中的filter做认证授权。FilterChainProxy的dofilter()中调用了doFilterInternal()方法,如下:
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
// 获取请求对应的filter列表
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
// 执行每个filter
vfc.doFilter(fwRequest, fwResponse);
}
// 通过遍历filterChains,调用SecurityFilterChain的matches方法,判断当前的请求对应哪些filter,返回匹配的filter列表
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
下图是简单的类图,不过结合文字大体上spring security中是可以大体勾勒出uri与filter的对应关系。具体方法在何时调用,后面会讲到。通过这里我们就可以知道了几个关键类拥有哪些能力,最后在启动的时候把他们串起来,整个流程就清晰了。
HttpSecurity
httpSecurity终极作用,就是注册实例化filter
上一部分中,提到WebSecurity中可以通过addSecurityFilterChainBuilder() 方法,把各种filter的SecurityBuilder添加进来,而这里的SecurityBuilder就是HttpSecurity。
我们在使用spring security 的时候,经常的做法就是写个类,继承WebSecurityConfigurerAdapter,并且加上@Configuration注解。比如:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.permitAll();
}
}
在自定义认证的时候,对HttpSecurity 的操作是非常核心的动作。这里的http所调用的方法,最终的结果就产生了我们先去提到的uri-filter 的关系映射。
authorizeRequests(),formLogin()方法分别返回ExpressionUrlAuthorizationConfigurer和FormLoginConfigurer,他们都是SecurityConfigurer接口的实现类,分别代表的是不同类型的安全配置器。而这些安全配置器分别对应一个或多个filter。
- formLogin对应UsernamePasswordAuthenticationFilter
- authorizeRequests对应FilterSecurityInterceptor
authorizeRequests(),formLogin()方法源码
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity>{
...
public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
throws Exception {
return getOrApply(new ExpressionUrlAuthorizationConfigurer<HttpSecurity>())
.getRegistry();
}
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<HttpSecurity>());
}
...
}
都是调用了getOrApply()方法
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
C configurer) throws Exception {
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
return apply(configurer);
}
再进去 apply(),到了父类AbstractConfiguredSecurityBuilder中
// 存储了Class与configurer的Map
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
return configurer;
}
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (configurers) {
if (buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer
+ " to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<SecurityConfigurer<O, B>>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
所以最后是将configurer放入一个LinkedHashMap的属性configurers中。每个configurer又对应到filter。
所以我们平常使用 http.xxx 的操作,就是将configurer添加到这个LinkedHashMap中。
接口SecurityBuilder 与 接口SecurityConfigurer
以上提到的都是具体的类,而带入的参数又是接口名,然后搞得云里雾里。WebSecurity,HttpSecurity,UsernamePasswordAuthenticationFilter这些关系怎么感觉还是有点模糊不清。
现在,我们从更高的层面来说,从两个核心接口以及其实现类的类图来理解下。
这两个接口就是SecurityBuilder 与 SecurityConfigurer
public interface SecurityBuilder<O> {
O build() throws Exception;
}
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
这里描述下:
HttpSecurity是一个securityBuilder,HttpSecuirty内部维护了一个Filter的List集合,我们添加的各种安全配置器对应的Filter最终都会被加入到这个List集合中。
WebSecurity是一个securityBuilder,内部维护着securityBuilder的列表,存储securityBuilder,这里主要是存储HttpSecurity。
很多官方类是XXXConfigurer,这些都是SecurityConfigurer。这些SecurityConfigurer的configure()方法,都会把对应filter添加到HttpSecurity。
层级的抽样,再结合前面两块的内容,应该能对spring security的整体结构,会有个更好的理解。
WebSecurity
WebSecurity 是spring security 中非常重要的一个类!三个重要作用:
- 创建Spring Security 过滤链 的 FilterChainProxy
- 存储着SecurityBuilder的列表,这里主要是存储各个httpSecurity
- 对SecurityConfigurer进行构建
从这三个作用来看,WebSecurity 是spring security启动的核心。构建各种SecurityConfigurer,存在各种SecurityBuilder,创建好SS的过滤链。是不是把SS的核心功能都初始化完成了。。。
WebSecurityConfiguration这个配置会在使用@EnableWebSecurity后导入,WebSecurityConfiguration会初始化WebSecurity ,并且调用WebSecurity 的apply()方法。
WebSecurityConfiguration类中,构建springSecurityFilterChain()方法的时候,调用WebSecurity 的build()方法。
build()方法是在其父类AbstractSecurityBuilder中实现
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
我们再看看核心的doBuild()
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
这个方法非常的重要,SecurityConfigurer的初始化,构建;FilterChainProxy的初始化等等都在这个方法里面完成。这个方法以后有时间我再进一步的分析分析。
总结
上述中更多的描述是SS的启动的流程,以及filter的相关配置存储等等。因为我觉得,filter与uri映射关系成立之后,具体的拦截、认证授权等等,是到了每个filter中。我们把这块流程理通了,需要分析具体的认证原理时候,只需要找到对应的SecurityConfigurer做分析,就可以搞定的。
流程总结
再用文字总结下:
- spring security 启动过程中 WebSecurityConfiguration 实例化 WebSecurity 。
WebSecurity 中的 securityFilterChainBuilders 列表存储了各个 SecurityBuilder,这里是HttpSecurity 这个SecurityBuilder。
每个 httpSecurity.and()... 这样的操作就是把对应的 SecurityConfigurer 加入 HttpSecurity 的 configurers 中存储。
- 在构建WebSecurity 的时候,会调用WebSecurity 的doBuild(),这个方法是一个核心操作。
- 2-1. doBuild() 方法中调用init()方法,会将所有的configurers调用init()方法。
- 2-2. doBuild() 方法中调用 configure() 方法,调用每个 configurer 的 configure() 方法,把自己加入到 对应 httpSecurity 的 filter list中。
- 2-3. doBuild() 方法中调用performBuild() 方法,构建FilterChainProxy实例,把 uri的匹配与 httpSecurity 做关联,进入了spring security的过滤链
就形成了 WebSecurity 1:n HttpSecurity HttpSecurity 1:n configurers HttpSecurity 1:n filters
每个 HttpSecurity 实例都有个 RequestMatcher 用来匹配请求uri,有个 List<Filter> 记录需要经过的 filter,有个 FilterComparator 记录了 filter 的 order
每个请求到达,FilterChainProxy匹配该请求对应的List<Filter>,进行拦截。
以上
如果有存在理解错误,请及时指出。
我的spring cloud demo的git地址: spring cloud demo