Spring Security4.0.3原始碼分析之FilterChainProxy初始化
最近在學習安全框架Spring Security,想弄清楚其中實現的具體步驟,於是下定決心,研究一下Spring Security原始碼,這篇部落格的目的是想把學習過程記錄下來。學習過程中主要參考了http://dead-knight.iteye.com/blog/1511389大神的部落格,然後在其基礎上,進行更詳細的說明
1.Spring Security入口在是在web.xml配置瞭如下的過濾器,那麼這個過濾器到底是怎麼工作的呢
<!-- SpringSecurity需要的filter -->
<filter>
<filter-name>spring-security</filter-name >
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSecurityFilterChain</param-value>
</init-param>
</filter>
<filter-mapping >
<filter-name>spring-security</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
我們開啟DelegatingFilterProxy類,發現這個類位於org.springframework.web-3.0.1.RELEASE.jar下面,說明這個類本身是和springSecurity無關。
2.研究一下DelegatingFilterProxy類到底是幹嘛的
2.1 DelegatingFilterProxy首先是呼叫了initFilterBean方法,初始化過濾器
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// 如果沒配置targetBeanName,那麼預設使用過濾器的名字
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// 獲取Spring上下文,並且初始化Filter
// 如果可能的話,在Spring上下文初始化前初始化Filter,我們會使用延遲載入策略
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
// 初始化springSecurityFilterChain,詳解請移步2.3
this.delegate = initDelegate(wac);
}
}
}
}
2.2 然後呼叫了doFilter方法,執行過濾器方法
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
// 初始化springSecurityFilterChain,詳解請移步2.3
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
}
// 讓委託執行實際的doFilter操作
// 這裡的delegateToUse實際上就是springSecurityFilterChain例項,詳解請移步4
invokeDelegate(delegateToUse, request, response, filterChain);
}
2.3 接著來看看上面提到的initDelegate(wac)方法
// 初始化FilterChainProxy
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
// getTargetBeanName()返回的是Filter的name:springSecurityFilterChain
// 根據springSecurityFilterChain的bean name直接獲取FilterChainProxy的例項
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
這裡大家會產生疑問,springSecurityFilterChain這個bean在哪裡定義的呢?先把這個問題拋開,稍後會作出解答
3. 標籤配置的初始化
在spring-security-config-4.0.3.RELEASE.jar包的META-INF目錄下有2個檔案spring.handlers和spring.schemas。其中spring.schemas檔案主要是標籤的規範,約束;而spring.handlers這個檔案時真正解析自定義標籤的類,這個檔案的內容為:
http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
3.1 標籤解析
從上面可以看出來Spring Security的標籤解析由org.springframework.security.config.SecurityNamespaceHandler來處理。該類實現介面:NamespaceHandler,Spring中自定義標籤都要實現該介面,這個介面有三個方法init、parse、decorate,其中init用於自定義標籤的初始化,parse用於解析標籤,decorate用於裝飾。SecurityNamespaceHandler類的init方法完成了標籤解析類的註冊工作。開啟SecurityNamespaceHandler原始碼。
public void init() {
loadParsers();
}
private void loadParsers() {
// Parsers
parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE, new MethodSecurityMetadataSourceBeanDefinitionParser());
// Only load the web-namespace parsers if the web classes are available
if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass().getClassLoader())) {
parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
}
if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
parsers.put(Elements.WEBSOCKET_MESSAGE_BROKER, new WebSocketMessageBrokerSecurityBeanDefinitionParser());
}
}
上面可以看出SecurityNamespaceHandler類的init方法完成了標籤解析類的註冊工作,並且http的標籤解析類註冊程式碼為:
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
3.2 HttpSecurityBeanDefinitionParser是如何解析HTTP標籤的?
@SuppressWarnings({ "unchecked" })
public BeanDefinition parse(Element element, ParserContext pc) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
pc.pushContainingComponent(compositeDef);
// 這裡建立了listFactoryBean例項和springSecurityFilterChain例項,詳解移步3.2.1
registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
// 獲取listFactoryBean例項
BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS);
List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();
// 建立過濾器鏈程式碼,詳解移步3.2.2
filterChains.add(createFilterChain(element, pc));
pc.popAndRegisterContainingComponent();
return null;
}
3.2.1 registerFilterChainProxyIfNecessary分析
static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
// 判斷是否已經註冊了FILTER_CHAIN_PROXY,若已經註冊則直接返回
if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
return;
}
// 註冊ListFactoryBean
BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);
listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());
pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, BeanIds.FILTER_CHAINS));
// 註冊FilterChainProxy
BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
fcpBldr.getRawBeanDefinition().setSource(source);
fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);
fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(DefaultFilterChainValidator.class));
BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
// 此處為FILTER_CHAIN_PROXY取別名為springSecurityFilterChain
pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
}
拋開前面的程式碼不看,由最後一行可以看出這裡建立了springSecurityFilterChain例項,到這裡終於和前面的掛鉤起來了
3.2.2 createFilterChain分析
// 建立過濾器鏈的程式碼
private BeanReference createFilterChain(Element element, ParserContext pc) {
// 判斷是否需要SpringSecurity攔截
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) {
// 如果element有子節點並且instanceofElement 新增錯誤資訊
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建立過濾器鏈,詳情移步3.2.2.1
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper, portResolver, authenticationManager);
// AuthenticationConfigBuilder建立過濾器鏈,詳情移步3.2.2.2
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
unorderedFilterChain.addAll(httpBldr.getFilters());
unorderedFilterChain.addAll(authBldr.getFilters());
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
// 對過濾器鏈進行排序,詳細分析請移步3.2.2.3
Collections.sort(unorderedFilterChain, new OrderComparator());
// 對過濾器鏈正確性檢查
checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));
// The list of filter beans
List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();
for (OrderDecorator od : unorderedFilterChain) {
filterChain.add(od.bean);
}
// 建立過濾器鏈,詳情移步3.2.2.4
return createSecurityFilterChainBean(element, pc, filterChain);
}
3.2.2.1 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();
}
3.2.2 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();
}
這裡可以看到autoConfig屬性,如果該屬性為true,SpringSecurity就會自動配置好過濾器鏈。具體如何設定檢視AuthenticationConfigBuilder的create*Filter方法即可看到,具體Create分析下一篇再細說。
3.2.2.3 對過濾器鏈排序分析
排序主要是由Collections.sort(unorderedFilterChain, new OrderComparator());這行程式碼實現的
unorderedFilterChain是OrderDecorator的List集合,開啟OrderDecorator原始碼,檢視建構函式
public OrderDecorator(BeanMetadataElement bean, SecurityFilters filterOrder) {
this.bean = bean;
this.order = filterOrder.getOrder();
}
這裡我們發現它其實是根據SecurityFilters定義好的順序進行排序的
enum SecurityFilters {
FIRST(Integer.MIN_VALUE), CHANNEL_FILTER, SECURITY_CONTEXT_FILTER, CONCURRENT_SESSION_FILTER,
/** {@link WebAsyncManagerIntegrationFilter} */
WEB_ASYNC_MANAGER_FILTER, HEADERS_FILTER, CSRF_FILTER, LOGOUT_FILTER, X509_FILTER, PRE_AUTH_FILTER, CAS_FILTER, FORM_LOGIN_FILTER, OPENID_FILTER, LOGIN_PAGE_FILTER, DIGEST_AUTH_FILTER, BASIC_AUTH_FILTER, REQUEST_CACHE_FILTER, SERVLET_API_SUPPORT_FILTER, JAAS_API_SUPPORT_FILTER, REMEMBER_ME_FILTER, ANONYMOUS_FILTER, SESSION_MANAGEMENT_FILTER, EXCEPTION_TRANSLATION_FILTER, FILTER_SECURITY_INTERCEPTOR, SWITCH_USER_FILTER, LAST(Integer.MAX_VALUE);
private static final int INTERVAL = 100;
private final int order;
private SecurityFilters() {
order = ordinal() * INTERVAL;
}
private SecurityFilters(int order) {
this.order = order;
}
public int getOrder() {
return order;
}
}
3.2.2.4 建立過濾器鏈分析
private BeanReference createSecurityFilterChainBean(Element element, ParserContext pc, List<?> filterChain) {
BeanMetadataElement filterChainMatcher;
String requestMatcherRef = element.getAttribute(ATT_REQUEST_MATCHER_REF);
String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);
if (StringUtils.hasText(requestMatcherRef)) {
if (StringUtils.hasText(filterChainPattern)) {
pc.getReaderContext().error("You can't define a pattern and a request-matcher-ref for the " + "same filter chain", pc.extractSource(element));
}
filterChainMatcher = new RuntimeBeanReference(requestMatcherRef);
} else if (StringUtils.hasText(filterChainPattern)) {
filterChainMatcher = MatcherType.fromElement(element).createMatcher(filterChainPattern, null);
} else {
filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);
}
BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityFilterChain.class);
filterChainBldr.addConstructorArgValue(filterChainMatcher);
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);
}
}
pc.registerBeanComponent(new BeanComponentDefinition(filterChainBean, id));
return new RuntimeBeanReference(id);
}
4. 呼叫代理類的doFilter方法
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
通過以上分析,對FilterChainProxy如何產生的,以及Spring Security的標籤是如何解析有了大體的認識。具體標籤的解析,Filter鏈的執行,下次再更新……