Spring Security3是目前使用非常廣泛的java web安全架構,我經曆的項目中有很多在使用它。盡管有shiro等使用更友善、更容易了解、應用範圍更廣的安全架構開始流行,但Spring Security3在Java web領域無疑是更強大、更容易擴充的。
對Spring Security3配置、應用的blog很多,我就不再介紹這些了。此系列文章主要根據我學習Spring Security3的過程,沿着Filter鍊介紹Spring Security3的原理。
啟用Spring Security3需要做如下三件事:
1、在web.xml配置配置過濾器DelegatingFilterProxy
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filterclass>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
這裡filter的名字必須是springSecurityFilterChain,原因稍後解釋。
2、建立spring-sucirity.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/
spring-security-3.0.xsd">
<http auto-config="true">
<intercept-url pattern="/*" access="ROLE_USER"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user authorities="ROLE_USER" name="guest" password="guest"/>
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
3、添加spring-security.xml到web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring-security.xml</param-value>
</context-param>
下面開始從入DelegatingFilterProxy手:
從名字上可以看出,這個過濾器其實沒做什麼,隻是委派給了其他過濾器。它并不在spring-security包中,而是在spring-web包
看它的doFilter方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = null;
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
//初始化FilterChain代理
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
// Let the delegate perform the actual doFilter operation.
//調用doFilter方法
invokeDelegate(delegateToUse, request, response, filterChain);
}
分析initDelegate方法
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
getTargetBeanName()其實擷取到的是Filter名字springSecurityFilterChain:在initFilterBean()方法中對targetBeanName做了指派
protected void initFilterBean() throws ServletException {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
//targetBeanName被設定為filterName
this.targetBeanName = getFilterName();
}
......
}
initFilterBean方法在父類GenericFilterBean的init方法中被執行。 這裡根據我們在web.xml設定filter的名字去擷取Spring Seucurity3的代理過濾器。後面回看到這個名字是springSecurityFilterChain,是以在web.xml的filter名字也必須是這個。 再分析spring-scurity.xml的解析: org.springframework.security.config.SecurityNamespaceHandler負責解析namespace為security的xml文檔,即我們的spring-security.xml檔案,它在spring-security-config包:
SecurityNamespaceHandler繼承了NamespaceHandler接口,有三個方法:init初始化,parse解析,decorate裝飾,在SecurityNamespaceHandler的init方法進行了每個解析類的注冊
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());
// Only load the web-namespace parsers if the web classes are available
if (ClassUtils.isPresent("org.springframework.security.web.FilterChainProxy", getClass().getClassLoader())) {
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
parsers.put(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
}
}
HttpSecurityBeanDefinitionParser時解析http節點的解析類
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
看一下HttpSecurityBeanDefinitionParser是如何解析http節點的,上parse
public BeanDefinition parse(Element element, ParserContext pc) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
pc.pushContainingComponent(compositeDef);
final Object source = pc.extractSource(element);
final String portMapperName = createPortMapper(element, pc);
final UrlMatcher matcher = createUrlMatcher(element);
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcher, portMapperName);
httpBldr.parseInterceptUrlsForEmptyFilterChains();
httpBldr.createSecurityContextPersistenceFilter();
httpBldr.createSessionManagementFilters();
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null);
httpBldr.createServletApiFilter();
httpBldr.createChannelProcessingFilter();
httpBldr.createFilterSecurityInterceptor(authenticationManager);
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
httpBldr.isAllowSessionCreation(), portMapperName);
//http節點的預設的11個過濾器
authBldr.createAnonymousFilter();
authBldr.createRememberMeFilter(authenticationManager);
authBldr.createRequestCache();
authBldr.createBasicFilter(authenticationManager);
authBldr.createFormLoginFilter(httpBldr.getSessionStrategy(), authenticationManager);
authBldr.createOpenIDLoginFilter(httpBldr.getSessionStrategy(), authenticationManager);
authBldr.createX509Filter(authenticationManager);
authBldr.createLogoutFilter();
authBldr.createLoginPageFilterIfNeeded();
authBldr.createUserServiceInjector();
authBldr.createExceptionTranslationFilter();
List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
//将過濾器加入過濾器鍊
unorderedFilterChain.addAll(httpBldr.getFilters());
unorderedFilterChain.addAll(authBldr.getFilters());//這裡加入的過濾器和我們設定auto-config="true"有關
authenticationProviders.addAll(authBldr.getProviders());
BeanDefinition requestCacheAwareFilter = new RootBeanDefinition(RequestCacheAwareFilter.class);
requestCacheAwareFilter.getPropertyValues().addPropertyValue("requestCache", authBldr.getRequestCache());
unorderedFilterChain.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER));
unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
//對過濾器進行排序
Collections.sort(unorderedFilterChain, new OrderComparator());
checkFilterChainOrder(unorderedFilterChain, pc, source);
//排好序的過濾器鍊
List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();
for (OrderDecorator od : unorderedFilterChain) {
filterChain.add(od.bean);
}
ManagedMap<Object, List<BeanMetadataElement>> filterChainMap = httpBldr.getFilterChainMap();
filterChainMap.put(matcher.getUniversalMatchPattern(), filterChain);
//注冊過濾器鍊proxy
registerFilterChainProxy(pc, filterChainMap, matcher, source);
pc.popAndRegisterContainingComponent();
return null;
}
http節點的11個過濾器不是都要加入過濾器鍊的,這個和我們的auto-config有關。使用auto-config="true"會自動提供以下三個認證相關的功能: HTTP基本認證 Form登入認證 退出 這一點從源碼中可以找到:AuthenticationConfigBuilder類的createLogoutFilter、createBasicFilter和createFormLoginFilter三個方法
void createLogoutFilter() {
Element logoutElt = DomUtils.getChildElementByTagName(httpElt, Elements.LOGOUT);
if (logoutElt != null || autoConfig) {
logoutFilter = new LogoutBeanDefinitionParser(rememberMeServicesId).parse(logoutElt, pc);
}
}
void createBasicFilter(BeanReference authManager) {
......
if (basicAuthElt != null || autoConfig) {
......
}
......
}
void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
......
if (formLoginElt != null || autoConfig) {
......
}
......
}
可見隻有autoConfig=true 時才會加入這仨過濾器。 在對unorderedFilterChain進行排序時,這11個過濾器怎麼排序的呢?org.springframework.core.OrderComparator的排序邏輯是根據org.springframework.core.Ordered的getOrder方法的比較。在AuthenticationConfigBuilder的getFilters方法傳回的事List<OrderDecorator>集合,OrderDecorator的構造方法:
public OrderDecorator(BeanMetadataElement bean, SecurityFilters filterOrder) {
this.bean = bean;
this.order = filterOrder.getOrder();
}
繼續看SecurityFilters枚舉類
enum SecurityFilters {
FIRST (Integer.MIN_VALUE),
CHANNEL_FILTER,
CONCURRENT_SESSION_FILTER,
SECURITY_CONTEXT_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,
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;
}
}
可見是根據枚舉的定義進行排序的,在http節點的配置中,我們将自定一個的過濾器用before、after、position等關鍵字插入到這11個過濾器前後。 再看注冊過濾器鍊:
private void registerFilterChainProxy(ParserContext pc, Map<Object, List<BeanMetadataElement>> filterChainMap, UrlMatcher matcher, Object source) {
if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
pc.getReaderContext().error("Duplicate <http> element detected", source);
}
//定義一個FilterChainProxy
BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
fcpBldr.getRawBeanDefinition().setSource(source);
fcpBldr.addPropertyValue("matcher", matcher);
fcpBldr.addPropertyValue("stripQueryStringFromUrls", Boolean.valueOf(matcher instanceof AntUrlPathMatcher));
fcpBldr.addPropertyValue("filterChainMap", filterChainMap);//設定過濾器鍊集合
BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
//注冊bean
pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
//注冊的别名為springSecurityFilterChain,在web.xml配置的filter名也要是這個
pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
}
那麼在DelegatingFilterProxy的initDelegate方法就可以擷取到這個過濾器鍊了,然後就是doFilter了。 好了,囫囵吞棗的列出這麼多,也不是很連貫,但大概應該能說明白原理了。下一篇開始分析過濾器,是以再把過濾器列出來:
過濾器名稱 | 描述 |
o.s.s.web.context.SecurityContextPersistenceFilter | 負責從SecurityContextRepository擷取或存儲SecurityContext。SecurityContext代表了使用者安全和認證過的session。 |
o.s.s.web.authentication.logout.LogoutFilter | 監控一個實際為退出功能的URL(預設為/j_spring_security_logout),并且在比對的時候完成使用者的退出功能。 |
o.s.s.web.authentication.UsernamePasswordAuthenticationFilter | 監控一個使用使用者名和密碼基于form認證的URL(預設為/j_spring_security_check),并在URL比對的情況下嘗試認證該使用者。 |
o.s.s.web.authentication.ui.DefaultLoginPageGeneratingFilter | 監控一個要進行基于forn或OpenID認證的URL(預設為/spring_security_login),并生成展現登入form的HTML |
o.s.s.web.authentication.www.BasicAuthenticationFilter | 監控HTTP 基礎認證的頭資訊并進行處理 |
o.s.s.web.savedrequest. RequestCacheAwareFilter | 用于使用者登入成功後,重新恢複因為登入被打斷的請求。 |
o.s.s.web.servletapi. SecurityContextHolderAwareRequest Filter | 用一個擴充了HttpServletRequestWrapper的子類(o.s.s.web. servletapi.SecurityContextHolderAwareRequestWrapper)包裝HttpServletRequest。它為請求處理器提供了額外的上下文資訊。 |
o.s.s.web.authentication. AnonymousAuthenticationFilter | 如果使用者到這一步還沒有經過認證,将會為這個請求關聯一個認證的token,辨別此使用者是匿名的。 |
o.s.s.web.session. SessionManagementFilter | 根據認證的安全實體資訊跟蹤session,保證所有關聯一個安全實體的session都能被跟蹤到。 |
o.s.s.web.access. ExceptionTranslationFilter | 解決在處理一個請求時産生的指定異常 |
o.s.s.web.access.intercept. FilterSecurityInterceptor | 簡化授權和通路控制決定,委托一個AccessDecisionManager完成授權的判斷 |