SpringSecurity初始化的本质
一、对SpringSecurity初始化的几个疑问
??通过前面第一次请求访问的分析我们明白了一个请求就来后的具体处理流程
??对于一个请求到来后会通过FilterChainProxy来匹配一个对应的过滤器链来处理该请求。那么这里我们就有几个疑惑。
- FilterChainProxy什么时候创建的?
- 过滤器链和对应的过滤器什么时候创建的?
- 怎么把自定义的过滤器添加到过滤器链中?
- 请求和过滤器的匹配规则是什么?
二、解析配置文件的过程
1.解析前的处理
??接下来我们来分析下Spring初始化的时候是如果解析SpringSecurity的配置文件的,并且存储在哪了?同时来解释我们上面的几个疑问。
首先系统启动的时候会触发在 web.xml 中配置的ContextLoaderListener监听器
然后会执行对应的initWebApplicationContext方法
然后进入configureAndRefreshWebApplicationContext方法中。
然后进入refresh()方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
我们要看配置文件的加载解析需要进入obtainFreshBeanFactory()方法中。
再继续进入
继续
这块会比较绕,直接截图进关键代码
一步
两步
三步
慢慢进入
开始具体的配置文件的加载解析
2.解析过程
??在上面的步骤基础上我们进入registerBeanDefinitions方法中来看看是如何具体实现配置文件的解析操作
然后进入registerBeanDefinitions方法中
继续
进入parseBeanDefinitions方法中,就会开始对应的节点解析。
parseDefaultElement方法会完成Spring中提供的默认方法解析,具体如下:
而SpringSecurity的解析是先进入import中,然后进入到parseCustomElement()方法来解析。
继续进入
3.解析器
我们在SpringSecurity的配置文件中使用了几个标签。
其实每个标签都有对应的解析器。
在SecurityNamespaceHandler中的 parsers中保存的就是 节点对应的解析器。
4.http解析
??继续跟踪代码肯定是会先解析security:http标签了。
下面的逻辑也很清晰
- 先判断是否合法
- 然后获取标签名称
- 根据标签名称获取对应的解析器
- 然后通过解析器来解析标签
进入HttpSecurityBeanDefinitionParser中看看解析http标签做了什么事情。
@Override
public BeanDefinition parse(Element element, ParserContext pc) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(
element.getTagName(), pc.extractSource(element));
pc.pushContainingComponent(compositeDef);
registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(
BeanIds.FILTER_CHAINS);
List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean
.getPropertyValues().getPropertyValue("sourceList").getValue();
filterChains.add(createFilterChain(element, pc));
pc.popAndRegisterContainingComponent();
return null;
}
上面代码的几个关键带你
- CompositeComponentDefinition保存配置文件中的嵌套的BeanDefinition信息
- 完成了FilterChainProxy的注册
- 完成了处理请求的过滤器和过滤器链的处理
5.FilterChainProxy的注册
??然后我们可以进入到registerFilterChainProxyIfNecessary()方法来查看FilterChainProxy的注册过程
SpringSecurity在BeanId中定义了相关的固定beanId值。
public abstract class BeanIds {
private static final String PREFIX = "org.springframework.security.";
public static final String AUTHENTICATION_MANAGER = PREFIX + "authenticationManager";
public static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain";
public static final String CONTEXT_SOURCE_SETTING_POST_PROCESSOR = PREFIX
+ "contextSettingPostProcessor";
public static final String USER_DETAILS_SERVICE = PREFIX + "userDetailsService";
public static final String USER_DETAILS_SERVICE_FACTORY = PREFIX
+ "userDetailsServiceFactory";
public static final String METHOD_ACCESS_MANAGER = PREFIX
+ "defaultMethodAccessManager";
public static final String FILTER_CHAIN_PROXY = PREFIX + "filterChainProxy";
public static final String FILTER_CHAINS = PREFIX + "filterChains";
public static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX
+ "methodSecurityMetadataSourceAdvisor";
public static final String EMBEDDED_APACHE_DS = PREFIX
+ "apacheDirectoryServerContainer";
public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource";
public static final String DEBUG_FILTER = PREFIX + "debugFilter";
}
6.创建过滤器
??接下来看看SpringSecurity中默认的过滤器是如何创建
??我们进入createFilterChainProxy方法中。
private BeanReference createFilterChain(Element element, ParserContext pc) {
boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED));
if (!secured) {
if (!StringUtils.hasText(element.getAttribute(ATT_PATH_PATTERN)) && !StringUtils.hasText(ATT_REQUEST_MATCHER_REF)) {
pc.getReaderContext().error("The '" + ATT_SECURED + "' attribute must be used in combination with" + " the '" + ATT_PATH_PATTERN + "' or '" + ATT_REQUEST_MATCHER_REF + "' attributes.", pc.extractSource(element));
}
for (int n = 0; n < element.getChildNodes().getLength(); n++) {
if (element.getChildNodes().item(n) instanceof Element) {
pc.getReaderContext().error("If you are using <http> to define an unsecured pattern, " + "it cannot contain child elements.", pc.extractSource(element));
}
}
return createSecurityFilterChainBean(element, pc, Collections.emptyList());
}
final BeanReference portMapper = createPortMapper(element, pc);
final BeanReference portResolver = createPortResolver(portMapper, pc);
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders);
boolean forceAutoConfig = isDefaultHttpConfig(element);
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());
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());
authenticationProviders.addAll(authBldr.getProviders());
List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
unorderedFilterChain.addAll(httpBldr.getFilters());
unorderedFilterChain.addAll(authBldr.getFilters());
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
Collections.sort(unorderedFilterChain, new OrderComparator());
checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));
List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();
for (OrderDecorator od : unorderedFilterChain) {
filterChain.add(od.bean);
}
return createSecurityFilterChainBean(element, pc, filterChain);
}
先看HttpConfigurationBuilder的构造方法
public HttpConfigurationBuilder(Element element, boolean addAllAuth, ParserContext pc, BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
this.httpElt = element;
this.addAllAuth = addAllAuth;
this.pc = pc;
this.portMapper = portMapper;
this.portResolver = portResolver;
this.matcherType = MatcherType.fromElement(element);
interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
for (Element urlElt : interceptUrls) {
if (StringUtils.hasText(urlElt.getAttribute(ATT_FILTERS))) {
pc.getReaderContext().error("The use of \"filters='none'\" is no longer supported. Please define a" + " separate <http> element for the pattern you want to exclude and use the attribute" + " \"security='none'\".", pc.extractSource(urlElt));
}
}
String createSession = element.getAttribute(ATT_CREATE_SESSION);
if (StringUtils.hasText(createSession)) {
sessionPolicy = createPolicy(createSession);
} else {
sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
}
createCsrfFilter();
createSecurityContextPersistenceFilter();
createSessionManagementFilters();
createWebAsyncManagerFilter();
createRequestCacheFilter();
createServletApiFilter(authenticationManager);
createJaasApiFilter();
createChannelProcessingFilter();
createFilterSecurityInterceptor(authenticationManager);
createAddHeadersFilter();
}
然后进入AuthenticationConfigBuilder中来查看,发向其实也创建了很多的过滤器
public AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc, SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
this.httpElt = element;
this.pc = pc;
this.requestCache = requestCache;
autoConfig = forceAutoConfig | "true".equals(element.getAttribute(ATT_AUTO_CONFIG));
this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.NEVER && sessionPolicy != SessionCreationPolicy.STATELESS;
this.portMapper = portMapper;
this.portResolver = portResolver;
this.csrfLogoutHandler = csrfLogoutHandler;
createAnonymousFilter();
createRememberMeFilter(authenticationManager);
createBasicFilter(authenticationManager);
createFormLoginFilter(sessionStrategy, authenticationManager);
createOpenIDLoginFilter(sessionStrategy, authenticationManager);
createX509Filter(authenticationManager);
createJeeFilter(authenticationManager);
createLogoutFilter();
createLoginPageFilterIfNeeded();
createUserDetailsServiceFactory();
createExceptionTranslationFilter();
}
创建SecurityFilterChain
到这http标签的解析就差不多了。到这儿也解释了我们前面的几个问题
7.解决的几个问题
问题1:web.xml配置的过滤器为什么必须是springSecurityFilterChain
问题2:FilterChainProxy什么时候创建的?
问题3:过滤器链和对应的过滤器什么时候创建的?
问题4:怎么把自定义的过滤器添加到过滤器链中?
问题5:请求和过滤器的匹配规则是什么?
|