OUZHANBO

对于我这种菜鸡来说,毕业等于失业

0%

SpringSecurity 源码(一)——SpringSecurity如何被加载进来的?

SpringSecurity 源码(一)——SpringSecurity 如何被加载进来的?

前言

这篇文章还是参考了这个大佬的这篇文章,文笔不好,如果觉得难理解可以看大佬的文章

xml 的方式配置 SpringSecurity

在 web.xml 中添加 filter 的相关配置

1
2
3
4
5
6
7
8
9
10
<filter>
//这里的filter-name必须是springSecurityFilterChain,这里先记住后面源码分析的时候可以知道为什么一定要叫这个名字
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

上面在 web.xml 中声明了 DelegatingFilterProxy 这个 filter 并且在 url-pattern 中以 /*让它拦截所以的请求,那么在我们第一次请求的时候就调用 DelegatingFilterProxy 的 init 方法,阅读源码发现调用逻辑简单总结如下

图片丢失

可以看到在 DelegatingFilterProxy 的 initFilterBean()方法中调用了 AbstractApplicationContext 的 getBean(String name, Class< T > requiredType)方法

1
2
3
4
5
6
7
8
9
10
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
//这里的targetBeanName就是我们上面再web.xml中设置的springSecurityFilterChain
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}

这个 targetBeanName 的值是在上层的 DelegatingFilterProxy 的 initFilterBean()方法中设置的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
if (this.targetBeanName == null) {
//默认是null,所以在getFilterName()方法中获取
this.targetBeanName = getFilterName();
}
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}

getFilterName()方法的代码

1
2
3
4
protected String getFilterName() {
//这里通过this.filterConfig.getFilterName()获取到的就是我在web.xml的filter-name中设置的springSecurityFilterChain
return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
}

那这个名字叫 springSecurityFilterChain 的 bean 到底是哪个类的实例,从 getBean 方法一路深入,调用逻辑如下

图片丢失

在 AbstractBeanFactory 的 doGetBean(String name, @Nullable Class< T > requiredType, @Nullable Object[] args, boolean typeCheckOnly)方法中

1
2
3
4
5
6
7
8
9
10
11
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {

//在这里将名字springSecurityFilterChain转化为Spring认为的规范名字
String beanName = transformedBeanName(name);
Object beanInstance;
Object sharedInstance = getSingleton(beanName);
//省略一万行代码
......
}

打个断点调试会发现在 AbstractBeanFactory 的 canonicalName(String name)方法中获取到了这个所谓的规范名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String canonicalName(String name) {
String canonicalName = name;

String resolvedName;
do {
//在这个aliasMap里面存放着springSecurityFilterChain的规范名字
resolvedName = this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
}
while (resolvedName != null);
return canonicalName;
}

调试的截图如下

图片丢失

所以这个 springSecurityFilterChain 对应的规范名字就是 org.springframework.security.filterChainProxy

那这个 aliasMap 中的 springSecurityFilterChain 的键值对数据是在哪被保存进来的呢?经过断点调试发现是在 HttpSecurityBeanDefinitionParser 中的 registerFilterChainProxyIfNecessary(ParserContext pc, Object source) 方法将这个键值对保存进 aliasMap 中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
BeanDefinitionRegistry registry = pc.getRegistry();
if (registry.containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
return;
}

//创建一个ListFactoryBean的BeanDefinition(ListFactoryBean是FactoryBean的子类,在我们通过Spring容器获FactoryBean的实例并不是它本身而是它的getObject方法返回的对象,具体关于FactoryBean的知识可以自己了解一下,这里不扩展)
BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);

//在spring创建完ListFactoryBean这个bean后会将new ManagedList()创建的这个列表赋值给bean里面的sourceList这个属性赋
listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());

//在这段代码内部将ListFactoryBean这个BeanDefinition以BeanIds.FILTER_CHAINS的名字注册到spring中,之后在初始化bean的时候就会将ListFactoryBean这个实例创建并且放入spring中管理
pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, BeanIds.FILTER_CHAINS));

//在这里创建了FilterChainProxy的BeanDefinitionBuilder,在这个BeanDefinitionBuilder里面就包含了一个BeanDefinition
BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
fcpBldr.getRawBeanDefinition().setSource(source);

//这里说明在后面spring初始化FilterChainProxy这个实例时构造器需要一个名字叫BeanIds.FILTER_CHAINS的实例
fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);

fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(DefaultFilterChainValidator.class));
BeanDefinition fcpBean = fcpBldr.getBeanDefinition();

//在这段代码内部将FilterChainProxy这个BeanDefinition以BeanIds.FILTER_CHAIN_PROXY的名字注册到spring中,之后在初始化bean的时候就会将FilterChainProxy这个实例创建并且放入spring中管理
pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));

//这里的BeanIds.SPRING_SECURITY_FILTER_CHAIN和BeanIds.FILTER_CHAIN_PROXY分别就是我们上面的键springSecurityFilterChain和值org.springframework.security.filterChainProxy
registry.registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);

//此处省略一万行代码
......
}

BeaIds 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class BeanIds {
//此处省略一万行代码
......
private static final String PREFIX = "org.springframework.security.";

public static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain";

public static final String FILTER_CHAIN_PROXY = PREFIX + "filterChainProxy";

public static final String FILTER_CHAINS = PREFIX + "filterChains";

//此处省略一万行代码
......
}

经过上面的源码分析我们知道了这个 springSecurityFilterChain 名字对应的 bean 就是 FilterChainProxy 这个类的实例,springSecurityFilterChain 只不过是这个 bean 的别名,它的规范名字是 org.springframework.security.filterChainProxy

图片丢失

从调试结果上看,springSecurityFilterChain 这个 bean 确实是 org.springframework.security.FilterChainProxy 这个类的实例

再回来看看 DelegatingFilterProxy 的 doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

Filter delegateToUse = this.delegate;
//用了双if判断的方式保证delegate只被实例化一次(不了解双if判断的作用的话可以了解一下单例模式的双if判断),因为initDelegate方法中已经对delegate初始化过了,所以if里面的逻辑不会执行,只是多提一嘴
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
//这里才是需要关心的入口
invokeDelegate(delegateToUse, request, response, filterChain);
}

查看 DelegatingFilterProxy 的 invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)方法的代码

1
2
3
4
5
6
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//从这里看到真正调用的是FilterChainProxy的doFilter方法
delegate.doFilter(request, response, filterChain);
}

看下 FilterChainProxy 的 doFilter(ServletRequest request, ServletResponse response, FilterChain chain)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
doFilterInternal(request, response, chain);
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//在这里调用了真正的过滤逻辑
doFilterInternal(request, response, chain);
}
catch (RequestRejectedException ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}

FilterChainProxy 的 doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)方法的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(firewallRequest);
if (filters == null || filters.size() == 0) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
}
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
return;
}
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
}
//创建了一条虚拟的过滤器链,然后调用这条过滤器链里的过滤逻辑,filters就是这条过滤器链中的过滤器
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}

FilterChainProxy 的部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FilterChainProxy extends GenericFilterBean {

private List<SecurityFilterChain> filterChains;


private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
//获取到匹配的SecurityFilterChain实例,并且获取并且返回这个过滤器链中的过滤器
for (SecurityFilterChain chain : this.filterChains) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
this.filterChains.size()));
}
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}

}

默认情况下 filterChains 只有一个实例,并且这个实例有 10 个默认的过滤器

图片丢失

下面的代码解析会涉及到一些 spring 的加载原理,如果觉得有点晕的话可以先不要看,等简单学过 spring 源码再看也可以。

问题来了,这里的默认的过滤器链和里面的过滤器在哪定义的?我们先看下 HttpSecurityBeanDefinitionParser 的 parse(Element element, ParserContext pc)方法的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public BeanDefinition parse(Element element, ParserContext pc) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
pc.extractSource(element));
pc.pushContainingComponent(compositeDef);
//我们上面看过源码的方法
registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
// Obtain the filter chains and add the new chain to it
//上面的源码的注释已经说得很清楚了,下面的逻辑就是获取过滤器链列表并且向里面添加一个新的过滤器链,这个名叫BeanIds.FILTER_CHAINS的BeanDefinition不就是前面在registerFilterChainProxyIfNecessary方法里面我们注册到spring中的吗,当时还给它里面的sourceList指定了一个初始列表,不记得可以看会上面的registerFilterChainProxyIfNecessary方法
BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS);
//获取listFactoryBean中的sourceList,sourceList指向一个空的列表
List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean.getPropertyValues()
.getPropertyValue("sourceList").getValue();
//向这个列表里面添加一个过滤器链
filterChains.add(createFilterChain(element, pc));
pc.popAndRegisterContainingComponent();
return null;
}

看完上面的代码我们应该可以猜测到这些这个过滤器链里面的过滤器应该就在 createFilterChain 这个方法里面放进去的,那就继续深入 createFilterChain 这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private BeanReference createFilterChain(Element element, ParserContext pc) {
//此处省略一万行代码
......
//这些filter的定义就是在下面两段代码和buildCustomFilterList这个方法里面创建的(里面的逻辑很复杂,我自己都没看懂,所以先自己看下,以后有机会再分析)
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper,
portResolver, authenticationManager);
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc,
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
//此处省略一万行代码
......
//将这些filter的定义保存到unorderedFilterChain中
List<OrderDecorator> unorderedFilterChain = new ArrayList<>();
unorderedFilterChain.addAll(httpBldr.getFilters());
unorderedFilterChain.addAll(authBldr.getFilters());
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
unorderedFilterChain.sort(new OrderComparator());
checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));
// The list of filter beans
List<BeanMetadataElement> filterChain = new ManagedList<>();
//遍历unorderedFilterChain将这些filter的定义放入filterChain中,最终以参数传入createSecurityFilterChainBean这个方法中,并且再这个方法中创建这条过滤器链的bean引用
for (OrderDecorator od : unorderedFilterChain) {
filterChain.add(od.bean);
}
return createSecurityFilterChainBean(element, pc, filterChain);
}

看下 createSecurityFilterChainBean(Element element, ParserContext pc, List<?> filterChain)方法的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private BeanReference createSecurityFilterChainBean(Element element, ParserContext pc, List<?> filterChain) {
//此处省略一万行代码
......
//创建一个DefaultSecurityFilterChain的BeanDefinition
BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder
.rootBeanDefinition(DefaultSecurityFilterChain.class);
filterChainBldr.addConstructorArgValue(filterChainMatcher);
//为这个BeanDefinition加入之后创建时需要用到过滤器定义列表(就是我们上面创建那堆filter的定义)
filterChainBldr.addConstructorArgValue(filterChain);
BeanDefinition filterChainBean = filterChainBldr.getBeanDefinition();
String id = element.getAttribute("name");
if (!StringUtils.hasText(id)) {
id = element.getAttribute("id");
if (!StringUtils.hasText(id)) {
id = pc.getReaderContext().generateBeanName(filterChainBean);
}
}
//将这个DefaultSecurityFilterChain的BeanDefinition注册到spring容器中
pc.registerBeanComponent(new BeanComponentDefinition(filterChainBean, id));
return new RuntimeBeanReference(id);
}

上面的方法最终会返回一个 BeanReference,这个 BeanReference 里面就有这个过滤器链在 spring 容器中的名字,到时只需要 spring 只需要根据这个名字找到创建后的过滤器链实例即可

上面的分析是不是很晕,因为这说的过滤器和过滤器链都不是真正的实例,因为这个时候 spring 还没开始创建实例只是在处理 bean 之间的引用关系,所以才会有 BeanDefinition 和 BeanReference 和其他乱七八糟的东西,不过只要简单了解 spring 的启动原理其实是不难理解的(没办法啊,涉及到 spring 东西基本的加载都和 spring 的原理有关),所以如果看不懂的话可以先去了解一下 spring 原理(不需要很深入的了解,只需要知道为什么会有 BeanDefinition 和 BeanReference 和其他杂七杂八的东西就行,因为我也只是简单的看过一点,所以写得不好请不要见怪)

servlet3.0+ 环境下 java Config 配置 SpringSecurity

自定义一个 SecurityWebApplicationInitializer 并且让其继承自 AbstractSecurityWebApplicationInitializer

1
2
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}

在 Spring 相关的配置类上加上@EnableWebSecurity 这个注解,我这里的 Spring 相关的配置类是 MyConfig

1
2
3
4
5
@Configuration
@EnableWebSecurity
@ComponentScan(value = "top.ouzhanbo.webspring",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class})},useDefaultFilters = false)
public class MyConfig {
}

下面简单分析一下一个简单的继承和一个注解就怎么就将 SpringSecurity 加载进来

先看下 AbstractSecurityWebApplicationInitializer 的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {

private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";

public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";


@Override
public final void onStartup(ServletContext servletContext) {
beforeSpringSecurityFilterChain(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
if (enableHttpSessionEventPublisher()) {
servletContext.addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
}
servletContext.setSessionTrackingModes(getSessionTrackingModes());
insertSpringSecurityFilterChain(servletContext);
afterSpringSecurityFilterChain(servletContext);
}


private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME;
//在这里创建了DelegatingFilterProxy这个关键过滤器的实例,并且这个filter的名字就是springSecurityFilterChain
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
//并且在下面这个方法里将这个DelegatingFilterProxyfilter的实例注册到servletContext中
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}


private void registerFilter(ServletContext servletContext, boolean insertBeforeOtherFilters, String filterName,
Filter filter) {
//将这个DelegatingFilterProxyfilter的实例注册到servletContext中
Dynamic registration = servletContext.addFilter(filterName, filter);
Assert.state(registration != null, () -> "Duplicate Filter registration for '" + filterName
+ "'. Check to ensure the Filter is only configured once.");
registration.setAsyncSupported(isAsyncSecuritySupported());
EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters, "/*");
}
}

上面的这种使用 javaConfig 的方式相对于使用 xml 的方式跟容易理解,这代码逻辑和 web.xml 上面配置 filter 的效果是完全一样的,这种使用 SCI 的方法我在之前这篇文章里面讲过

前面讲过在 DelegatingFilterProxy 的这个 filter 的初始化中会调用到 initFilterBean()方法并在该方法中调用了 AbstractApplicationContext 的 getBean(String name, Class< T > requiredType)方法,并且这里的 targetBeanName 就是 springSecurityFilterChain,我之前分析过,这里也差不多,我就不再说一遍了,可以自己看下源码,相对于 xml 的方式,javaConfig 的配置方式理解起来已经很简单了

1
2
3
4
5
6
7
8
9
10
11
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {

String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
//这里的targetBeanName就是我们上面再web.xml中设置的springSecurityFilterChain
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}

问题来了上面的名字叫 springSecurityFilterChain 这个 bean 在哪被注册到 Spring 容器中的?其实也不难猜到是@EnableWebSecurity 这个注解在搞鬼,看下这个注解的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//这面主要看的是WebSecurityConfiguration这个类
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

boolean debug() default false;

}

WebSecurityConfiguration 的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {


//此处省略一万行代码
......
//这里这个Bean的名字就是springSecurityFilterChain(上面的代码提到过)
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
Assert.state(!(hasConfigurers && hasFilterChain),
"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
if (!hasConfigurers && !hasFilterChain) {
WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
this.webSecurity.apply(adapter);
}
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
for (Filter filter : securityFilterChain.getFilters()) {
if (filter instanceof FilterSecurityInterceptor) {
this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
break;
}
}
}
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
return this.webSecurity.build();
}
//此处省略一万行代码
......
}

要想知道 webSecurity.build()返回的是那个类的实例那就从这个 build 方法一路深入,这里就那么啰嗦一个个代码贴出来了,直接给个流程图

图片丢失

看下 WebSecurity 的 performBuild()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this is done by exposing a SecurityFilterChain bean "
+ "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
//创建FilterChainProxy这个实例,并且将过滤器链列表以构造参数的形式传入(上面securityFilterChains的创建我之后的文章再分析)
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}
if (this.requestRejectedHandler != null) {
filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
}
filterChainProxy.afterPropertiesSet();

Filter result = filterChainProxy;
if (this.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);
}
this.postBuildAction.run();
return result;
}

SpringBoot 配置 SpringSecurity

只需要在启动类上加上@EnableWebSecurity 这个注解就可以了(其实可以不加,在 WebSecurityEnablerConfiguration 这个类中引入了这个注解,而 WebSecurityEnablerConfiguration 这个类又被 SecurityAutoConfiguration 这个类通过@Import 注解引入,而 SecurityAutoConfiguration 这个类又会被 SpringBoot 自动装配进来)

1
2
3
4
5
6
7
8
9
10
11
@EnableWebSecurity
@SpringBootApplication
public class SpringbootSpringSecurityDemoApplication {

public static void main(String[] args) {

SpringApplication.run(SpringbootSpringSecurityDemoApplication.class, args);

}

}

那 SpringBoot 是如何将 DelegatingFilterProxy 这个 filter 加载到 servlet 容器中的?其实还是用到了 SpringBoot 的自动装配,看下 SecurityFilterAutoConfiguration 这个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}

private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
if (securityProperties.getFilter().getDispatcherTypes() == null) {
return null;
}
return securityProperties.getFilter().getDispatcherTypes().stream()
.map((type) -> DispatcherType.valueOf(type.name()))
.collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
}

}

看过这篇文章的话应该就能知道 SpringBoot 在启动的过程中会获取上面的 DelegatingFilterProxyRegistrationBean 这个 Bean 并且调用它的 onStartup 方法,在它的 onStartup 方法一路深入,调用逻辑如下

图片丢失

AbstractFilterRegistrationBean 的 addRegistration 代码

1
2
3
4
5
protected Dynamic addRegistration(String description, ServletContext servletContext) {
Filter filter = getFilter();
// 这里getOrDeduceName这个获取到的名字就是上面的DEFAULT_FILTER_NAME(springSecurityFilterChain)
return servletContext.addFilter(getOrDeduceName(filter), filter);
}

DelegatingFilterProxyRegistrationBean 的 getFilter 方法

1
2
3
4
5
6
7
8
9
10
11
public DelegatingFilterProxy getFilter() {
return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) {

// 为了延迟初始化DelegatingFilterProxy这个filter,在第一次调用的时候再初始化
@Override
protected void initFilterBean() throws ServletException {
// Don't initialize filter bean on init()
}

};
}

FilterChainProxy 的创建和初始化和上面说得都差不多,只是 DelegatingFilterProxy 是延迟初始化,看下 DelegatingFilterProxy 的 doFilter 方法就知道了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}

// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}

上面的英文注解以说的很清楚了,如果在 initFilterBean 方法中没有初始化就会在第一次调用 filter 的时候初始化

总结

总体来说除了 xml 的那种方式的源码阅读起来是比较难懂的,其实使用 java Config 和 SpringBoot 的方式配置 SpringSecurity 的逻辑还是很好理解的。这篇文章还,文笔不好,如果觉得难理解看这个大佬的这篇文章,讲得真的不错,推荐!!!