4.SpringSecurity初始化的本质
创始人
2024-05-29 09:52:53
0

SpringSecurity初始化的本质

一、对SpringSecurity初始化的几个疑问

  通过前面第一次请求访问的分析我们明白了一个请求就来后的具体处理流程

image.png

  对于一个请求到来后会通过FilterChainProxy来匹配一个对应的过滤器链来处理该请求。那么这里我们就有几个疑惑。

  1. FilterChainProxy什么时候创建的?
  2. 过滤器链和对应的过滤器什么时候创建的?
  3. 怎么把自定义的过滤器添加到过滤器链中?
  4. 请求和过滤器的匹配规则是什么?

二、解析配置文件的过程

1.解析前的处理

  接下来我们来分析下Spring初始化的时候是如果解析SpringSecurity的配置文件的,并且存储在哪了?同时来解释我们上面的几个疑问。

首先系统启动的时候会触发在 web.xml中配置的ContextLoaderListener监听器

image.png

然后会执行对应的initWebApplicationContext方法

image.png

然后进入configureAndRefreshWebApplicationContext方法中。

image.png

然后进入refresh()方法

	@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing./*** 前戏,做容器刷新前的准备工作* 1、设置容器的启动时间* 2、设置活跃状态为true* 3、设置关闭状态为false* 4、获取Environment对象,并加载当前系统的属性值到Environment对象中* 5、准备监听器和事件的集合对象,默认为空的集合*/prepareRefresh();// Tell the subclass to refresh the internal bean factory.// 创建容器对象:DefaultListableBeanFactory// 加载xml配置文件的属性值到当前工厂中,最重要的就是BeanDefinitionConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.// beanFactory的准备工作,对各种属性进行填充prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 子类覆盖方法做额外的处理,此处我们自己一般不做任何扩展工作,但是可以查看web中的代码,是有具体实现的postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.// 调用各种beanFactory处理器invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.// 注册bean处理器,这里只是注册功能,真正调用的是getBean方法registerBeanPostProcessors(beanFactory);// Initialize message source for this context.// 为上下文初始化message源,即不同语言的消息体,国际化处理,在springmvc的时候通过国际化的代码重点讲initMessageSource();// Initialize event multicaster for this context.// 初始化事件监听多路广播器initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 留给子类来初始化其他的beanonRefresh();// Check for listener beans and register them.// 在所有注册的bean中查找listener bean,注册到消息广播器中registerListeners();// Instantiate all remaining (non-lazy-init) singletons.// 初始化剩下的单实例(非懒加载的)finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.// 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.// 为防止bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件beandestroyBeans();// Reset 'active' flag.// 重置active标志cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

我们要看配置文件的加载解析需要进入obtainFreshBeanFactory()方法中。

image.png

再继续进入

image.png

继续

image.png

这块会比较绕,直接截图进关键代码

image.png

一步

image.png

两步

image.png

三步

image.png

慢慢进入

image.png

image.png

image.png

image.png

image.png

开始具体的配置文件的加载解析

image.png

2.解析过程

  在上面的步骤基础上我们进入registerBeanDefinitions方法中来看看是如何具体实现配置文件的解析操作

image.png

然后进入registerBeanDefinitions方法中

image.png

继续

image.png

进入parseBeanDefinitions方法中,就会开始对应的节点解析。

image.png

parseDefaultElement方法会完成Spring中提供的默认方法解析,具体如下:

image.png

而SpringSecurity的解析是先进入import中,然后进入到parseCustomElement()方法来解析。

image.png

继续进入

image.png

3.解析器

我们在SpringSecurity的配置文件中使用了几个标签。

image.png

其实每个标签都有对应的解析器。

image.png

image.png

image.png

在SecurityNamespaceHandler中的 parsers中保存的就是 节点对应的解析器。

image.png

4.http解析

  继续跟踪代码肯定是会先解析security:http标签了。

image.png

下面的逻辑也很清晰

  • 先判断是否合法
  • 然后获取标签名称
  • 根据标签名称获取对应的解析器
  • 然后通过解析器来解析标签

image.png

进入HttpSecurityBeanDefinitionParser中看看解析http标签做了什么事情。

	@Overridepublic BeanDefinition parse(Element element, ParserContext pc) {// CompositeComponentDefinition  保存内嵌的BeanDefinitionCompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));// compositeDef定义保存在了 父容器中pc.pushContainingComponent(compositeDef);// 完成FilterChainProxy的注册registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));// Obtain the filter chains and add the new chain to itBeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS);List filterChains = (List) listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();// createFilterChain(element, pc) 创建对应的过滤器并添加到了filterChains这个过滤器链中filterChains.add(createFilterChain(element, pc));pc.popAndRegisterContainingComponent();return null;}

上面代码的几个关键带你

  • CompositeComponentDefinition保存配置文件中的嵌套的BeanDefinition信息
  • 完成了FilterChainProxy的注册
  • 完成了处理请求的过滤器和过滤器链的处理

5.FilterChainProxy的注册

  然后我们可以进入到registerFilterChainProxyIfNecessary()方法来查看FilterChainProxy的注册过程

image.png

SpringSecurity在BeanId中定义了相关的固定beanId值。

public abstract class BeanIds {private static final String PREFIX = "org.springframework.security.";/*** The "global" AuthenticationManager instance, registered by the*  element*/public static final String AUTHENTICATION_MANAGER = PREFIX + "authenticationManager";/** External alias for FilterChainProxy bean, for use in web.xml files */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中默认的过滤器是如何创建

image.png

  我们进入createFilterChainProxy方法中。

private BeanReference createFilterChain(Element element, ParserContext pc) {// 判断是否需要Security拦截boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED));if (!secured) {// 如果没配置pattern属性并且配置了request-matcher-ref为空 添加错误信息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  to define an unsecured pattern, " + "it cannot contain child elements.", pc.extractSource(element));}}// 创建过滤器链return createSecurityFilterChainBean(element, pc, Collections.emptyList());}// portMapper、portResolver主要提供给SSL相关类使用final BeanReference portMapper = createPortMapper(element, pc);final BeanReference portResolver = createPortResolver(portMapper, pc);// 新建一个空的authenticationProviders集合 ManagedList authenticationProviders = new ManagedList();// 通过空的authenticationProviders集合产生一个AuthenticationManager的bean定义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());// 配置logoutHandlershttpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());httpBldr.setEntryPoint(authBldr.getEntryPointBean());httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());// 向AuthenticationProviders中添加provider  authenticationProviders.addAll(authBldr.getProviders());List unorderedFilterChain = new ArrayList();// 向FilterChain链中添加filters  unorderedFilterChain.addAll(httpBldr.getFilters());unorderedFilterChain.addAll(authBldr.getFilters());// 添加自定义的Filter,也就是custom-filter标签定义的Filter  unorderedFilterChain.addAll(buildCustomFilterList(element, pc));// 对过滤器进行排序Collections.sort(unorderedFilterChain, new OrderComparator());// 校验过滤器是否有效checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));// The list of filter beansList filterChain = new ManagedList();for (OrderDecorator od : unorderedFilterChain) {filterChain.add(od.bean);}// 创建SecurityFilterChain 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);// 获取子标签intercept-urlinterceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);for (Element urlElt : interceptUrls) {// 判断子标签intercept-url是否配置了filters属性// 如果配置了filters属性添加错误消息,因为Security已经不再支持filters属性了if (StringUtils.hasText(urlElt.getAttribute(ATT_FILTERS))) {pc.getReaderContext().error("The use of \"filters='none'\" is no longer supported. Please define a" + " separate  element for the pattern you want to exclude and use the attribute" + " \"security='none'\".", pc.extractSource(urlElt));}}// 获取标签create-session属性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));// 是否允许sessionthis.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

image.png

到这http标签的解析就差不多了。到这儿也解释了我们前面的几个问题

7.解决的几个问题

问题1:web.xml配置的过滤器为什么必须是springSecurityFilterChain

image.png

问题2:FilterChainProxy什么时候创建的?

image.png

问题3:过滤器链和对应的过滤器什么时候创建的?

image.png

问题4:怎么把自定义的过滤器添加到过滤器链中?

image.png

问题5:请求和过滤器的匹配规则是什么?

image.png

相关内容

热门资讯

宫崎骏动画里的夏日稻田 | 图... 转自:湛江发布蝉鸣撕开安铺的夏天云朵在靛蓝天空慵懒游走翡翠色的稻田翻涌波浪白鹭掠过田埂惊起一片细碎银...
上海一男子为图方便将木材从11... 来源:新闻坊 一男子因违章被责令拆除 为图方便 竟直接将木材从11楼扔下近日,青浦警方破获一起高空抛...
2025中国福州国际招商月丨多... 为传承弘扬“中国福州国际招商月”思想精髓,持续扩大高水平对外开放,有福之州再一次向世界发出了写满诚意...
十五运会女足成年组正赛队伍全部... 来源:新华网 新华社西安5月9日电(记者郑昕)第十五届全国运动会足球项目女子成年组资格赛9日在各赛区...
影像速览!领航战略协作 共促世...   5月7日至10日,习近平主席对俄罗斯进行国事访问并出席在莫斯科举行的纪念苏联伟大卫国战争胜利80...
“最终还是由客户埋单”——德国... 转自:新华社新华财经法兰克福5月11日电(记者刘向 单玮怡 马悦然)位于德国巴登-符腾堡州小镇布雷滕...
一个轮胎引发的险情!高速避障必... 来源:广西交警微发布 近日,南宁市公安局交警支队十五大队民警在兰海高速武鸣服务区路段巡逻时,发现有一...
全国防灾减灾日丨防患未然,一图... 转自:新华网  2025年5月12日是第17个全国防灾减灾日,主题是“人人讲安全、个个会应急——排查...
事发杭州一网红景区!男孩被七八... 本文转自【新闻坊】;杭州的四岭村千岱坑坊友们听你说过吗?这里虽然早已废弃,洞口也已封闭但是在社交平台...
预告|5月12日起,《全民安全... 转自:中华人民共和国应急管理部由应急管理部、中央广播电视总台联合制作的《全民安全公开课》第三季系列节...
进度条刷新!15号线这几个站实... 近日重庆轨道交通15号线迎来了新进展快和小布丁来看看~重庆轨道交通15号线一期建设现场目前,轨道交通...
视频|玉渊谭天:中美会谈前都发...   #中美会谈前都发生了什么##美方为何坐不住了#中美接触进行中,近期美方接连对华滥施关税后,又反复...
相约中国大陆南极世界爱情角!5... 转自:湛江发布情侣朋友们今年520想好怎么过了吗?要不带上你最爱的人来奔赴一场大陆最南端的浪漫之约吧...
乌克兰要求无条件停火30天 最... 乌克兰和欧洲大国要求俄罗斯加入从周一开始的30天“无条件”停火协议,以便就结束战争进行谈判,并表示美...
不止是奔跑!被秦皇岛马拉松暖到... 【不止是奔跑!#被秦皇岛马拉松暖到了#】5月11日,2025秦皇岛马拉松鸣枪开跑,17000名选手在...
陈茂波:首季出口佳因买家赶下单... 格隆汇5月11日|香港政府早前估计第一季本地生产总值(GDP)同比升3.1%,高于市场预期的2.1%...
这个周末邂逅人艺之友 一起“再... 【这个周末#邂逅人艺之友# 一起“再造时光”】5月10日至11日,北京人民艺术剧院联合东城区委区政府...
它博会纳新采购节成交额破350... 转自:上观新闻如何通过创新项目为参展商开辟增量新渠道,是TOPS它博会始终关注的核心问题之一。去年T...
龙江交投:助力龙江旅游经济由“... 新华信用杭州5月11日电(王思凝)作为2025世界品牌莫干山大会的重要组成部分,以“促消费 树品牌 ...
女演员陷“辱华”争议,疑被新剧... 5月10日,女演员李凯馨相关话题连上热搜。日前,一名自称是李凯馨前助理的网友在微博爆料。在其曝光的录...