Spring 是一個非常流行和成功的 Java 應用開發框架。Spring Security 基於 Spring 框架,提供了一套 Web 應用安全性的完整解決方案。一般來說,Web 應用的安全性包括用戶認證(Authentication)和用戶授權(Authorization)兩個部分。用戶認證指的是驗證某個用戶是否為系統中的合法主體,也就是說用戶能否訪問該系統。用戶認證一般要求用戶提供用戶名和密碼。系統通過校驗用戶名和密碼來完成認證過程。用戶授權指的是驗證某個用戶是否有權限執行某個操作。在一個系統中,不同用戶所具有的權限是不同的。比如對一個文件來說,有的用戶只能進行讀取,而有的用戶可以進行修改。一般來說,系統會為不同的用戶分配不同的角色,而每個角色則對應一系列的權限。
對於上面提到的兩種應用情景,Spring Security 框架都有很好的支持。在用戶認證方面,Spring Security 框架支持主流的認證方式,包括 HTTP 基本認證、HTTP 表單驗證、HTTP 摘要認證、OpenID 和 LDAP 等。在用戶授權方面,Spring Security 提供了基於角色的訪問控制和訪問控制列表(Access Control List,ACL),可以對應用中的領域對象進行細粒度的控制。
本文先從web包來分析spring-security提供的安全保護。
1. access模塊:
其中,ExceptionTranslationFilter:處理過濾器鏈拋出的所有AccessDeniedException和AuthenticationException異常.
WebInvocationPrivilegeEvaluator:允許用戶來決定他們是否有訪問特定web url的權限。
1.1 channel包:確保從指定傳輸通道接收web請求。
其中,
ChannelProcessingFilter: 確保一個web請求通過要求的channel。在內部使用FilterInvocation來表示request請求,允許使用FilterInvoctionSecurityMetaDataSource來查詢應用之上的屬性。
代理ChannelDecisionManager來處理真實的通道安全Decision和必須的action。若響應由ChannelDecisionManager來提交,ChannelProcessingFilter將不會處理。
下面的示例強制將登陸表單和對/secure路徑下的訪問都通過https來訪問。
<bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter"> <property name="channelDecisionManager" ref="channelDecisionManager"/> <property name="securityMetadataSource"> <security:filter-security-metadata-source path-type="regex"> <security:intercept-url pattern="\A/secure/.*\Z" access="REQUIRES_SECURE_CHANNEL"/> <security:intercept-url pattern="\A/login.jsp.*\Z" access="REQUIRES_SECURE_CHANNEL"/> <security:intercept-url pattern="\A/.*\Z" access="ANY_CHANNEL"/> </security:filter-security-metadata-source> </property> </bean> <bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl"> <property name="channelProcessors"> <list> <ref bean="secureChannelProcessor"/> <ref bean="insecureChannelProcessor"/> </list> </property> </bean> <bean id="secureChannelProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor"/> <bean id="insecureChannelProcessor" class="org.springframework.security.web.access.channel.InsecureChannelProcessor"/>
channelDecisionManager:確定一個web channel是否提供了足夠的安全性。
ChannelProcessor:確定一個web channel是否滿足特定安全條件。
ChannelEntryPoint:由ChannelProcessor使用來啟動一個web channel。
1.2 expression包:
SecurityExpressionHandler 是一個門面,它將內在的表達式對象實現和spring security對安全表達式的需求分離開來。
DefaultWebSecurityExpressionHandler是SecurityExpressionHandler的默認實現。
protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) { WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(trustResolver); root.setRoleHierarchy(getRoleHierarchy()); return root; }
1.3 intercept包:增強http請求的安全性,特別是url請求。
其中,FilterSecurityInterceptor 通過實現了filter來增加http資源的安全性。這個安全攔截器需要FilterInvocationSecurityMedataSource。
2. authentication模塊
認證處理機制,支持多種協議如BASIC,CAS,form login等提交認證信息。
2.1 logout和rememberme 包
其中
LogoutFilter記錄用戶的退出,它包含了一組Logouthandler。這些hangler應用按照順序排序,順序是你想調用TockenBasedRememberMeServices和securityContxtLogoutHander的順序。退出后,將由LogoutSucessHandler或者LogoutSuccesUrl來決定跳轉到哪里。到底由誰確定依賴於創建LogoutFilter使用的構造方法。
RememberMeAuthenticationFilter檢查SecurityContext中是否有Authentication對象,並且當有RememberMeServices實現了該請求時將一個remember-me authentication token設置到SecurityContext中。這個過濾器會調用RememberMeServices實現的autoLogin方法,如果上述方法返回一個非空的Authentication對象,會被傳遞到AuthenticationManager,這樣任何特定authentication將可以完成。若Authentication結果返回成功,它將會被設置到SecurityContext中。
如果認證成功,一個InteractiveAuthenticationSuccessEvent時間將會發布到application context中,若認證不成功,則不會有事件發布,因為不成功的話會記錄成特定AuthenticationManager的應用事件。 正常情況下,不管Authentication是成功還是失敗,都會允許處理請求request。如果需要控制特定認證用戶的訪問目的,可以將AuthenticationSuccessHandler注入其中。
2.2 peauth包
支持已認證的場景--spring 假定請求request已經被外部配置系統認證通過的場景。
AuthenticationDetailsSource:給特定的web請求request提供一個Authentication接口提供一個getDetails()方法
J2eePreAuthenticatedProcessingFilter:基於j2ee容器認證機制的過濾器,它將使用j2ee 用戶principal名稱作為預先完成認證的principal。
WebSpherePreAuthenticatedProcessingFilter:基於Websphere認證的過濾器,它將使用Websphere RunAs 用戶principal名稱作為先完成認證的principal。
X509AuthenticationFilter:負責處理要求未認證用戶提供客戶端證書的請求。如果這個請求包含了合法的證書,它將會使SubjectDnX509PrincipalExtractor抽取出安全實體.
RequestHeaderAuthenticationFilter:一個簡單的預先認證完成過濾器,它從用戶的請求頭獲取用戶名,使用在諸如CA siteminder系統等。
2.3 session和switchuser
session包:提供一個新認證用戶處理session相關行為的策略接口和實現類
switchuser包:提供一個基於http的具有切換用戶能力的包。類似於linux中的su命令。
SessionAuthenticationStrategy:在一次認證過程中對httpSession相關的行為允許可插拔支持。典型應用場景是確認session是否存在或者改變session id來保證基於session攻擊的安全。
SwitchUserFilter:高權限用戶向低權限用戶切換 。
2. 4 ui包
DefaultLoginPageGeneratingFilter:當一個用戶沒有配置login頁面時使用。僅當跳轉到login頁面時用到。
2.5 www包
BasicAuthenticationFilter:處理一個http請求的basic認證頭,將結果放入SecurityContextHolder。
總之,該過濾器負責處理具有basic認證scheme和base64編碼的username:password token的http請求頭消息.例如,認證一個名為"Aladdin"的用戶,其密碼為“open sesame” ,其頭部如下:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
該過濾器不但可以用來為遠程協議客戶端(如hessian和soap)提供basic認證服務,還可以為標准的用戶代理(如ie和netscape)提供basic認證服務。如果認證成功,認證結果Authentication對象將會放入SecurityContextHolder之中。如果認證失敗並且<ignoreFailure>設置為false(默認),將會調用AuthenticationEntryPoint實現類(除非<ignoreFailure>屬性被設置為true)。通常情況下是BasicAuthenticationEntryPoint,它提醒用戶通過Basic認證方式重新認證。因basic認證協議的簡單和廣泛部署,它是一個非常有吸引力的協議。然而,由於該協議通過明確的text傳遞密碼,因此它不適用於很多應用場景。spring security提供的Digest認證可以在這些場景中替換Basic認證。
注意:若設置了RememberMeService,該filter將自動給用戶返回remember-me細節。
DigestAuthenticationFilter:參照BasicAuthenticationFilter,注意:digest認證的缺點,盡管digest認證方式比basic認證方式更全面、更安全,但Rfc2617第四部分詳細討論了digest認證方式比basic認證方式的好處,也論述了digest的缺陷。
3.bind模塊
AuthenticationPrincipal注解:綁定一個方法的參數或者方法到Authentication的getPrincipal()方法。必須指明,參數應該解析到當前用戶而不是可以在表單編輯的用戶。示例如下:
@Controller public class MyController { @RequestMapping("/user/current/show") public String show(@AuthenticationPrincipal CustomUser customUser) { // do something with CustomUser return "view"; }
AuthenticationPrincipalArgumentResolver:解析AuthenticationPrincipal注解。上述例子也可以這樣做:
@Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @AuthenticationPrincipal public @interface CurrentUser { } @Controller public class MyController { @RequestMapping("/user/current/show") public String show(@CurrentUser CustomUser customUser) { // do something with CustomUser return "view"; }
4.上下文context模塊
同步SecurityContextPersistenceFilter:從配置的SecurityContextRepository而不是request中獲取信息存到SecurityContextHolder,並且當請求結束清理contextHolder時將值存回repository中(默認使用HttpSessionSecurityContextRepository).在該過濾器中每一個請求僅執行一次,該filter需在任何認證處理機制其作用之前執行。認證處理機制如basic,cas等期望在執行時從SecurityContextHolder中獲取SecurityContext。
異步WebAsyncManagerIntegrationFilter:提供了對securityContext和WebAsyncManager的集成。方式是通過SecurityContextCallableProcessingInterceptor的beforeConcurrentHandling(NativeWebRequest, Callable)方法來講SecurityContext設置到Callable上。
SecurityContextCallableProcessingInterceptor:支持spring mvc Callable集成。當SecurityContextCallableProcessingInterceptor執行preProcess(NativeWebRequest, Callable)方法時將注入的SecurityContext傳遞給SecurityContextHolder。
@Override public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception { SecurityContextHolder.setContext(securityContext); }
同樣清楚的是,SecurityContextCallableProcessingInterceptor在執行postProcess(NativeWebRequest, Callable, Object)方法時調用SecurityContextHolder的clearContext()。
@Override public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception { SecurityContextHolder.clearContext(); }
5 csrf模塊
Cross-site request forgery跨站請求偽造,也被稱為“one click attack”或者session riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。
CsrfException:當一個HttpServletRequest沒有有效的CsrfToken或者沒有CsrfToken時拋出的異常,有兩個子類:InvalidCsrfTokenException和MissingCsrfTokenException。
CsrfToken提供了一個期望的csrf token信息。默認實現為DefaultCsrfToken,還有一個內部實現類SaveOnAccessCsrfToken。
CsrfTokenRepository:將crsfToken與HttpServletRequest關聯起來,使之能關聯CsrfToken的api。例如,可以存儲到HttpSesssion中。默認實現為HttpSessionCsrfTokenRepository。
CsrfFilter:通過使用同步token模式來進行csrf防護。
CsrfLogoutHandler 負責在退出時移除csrfToken。調用時在LogoutFilter中的logout方法中:
for (LogoutHandler handler : handlers) { handler.logout(request, response, auth); }
6. Debug模塊
DebugFilter:spring security的調試過濾器。為幫助用戶理解請求request是如何被spring security處理的,使用日志記錄諸如session創建等消息。同時也會記錄一些別的相關消息。
Logger封裝了apache commons-logging。
7.firewall模塊
HttpFirewall接口時為了阻止潛在威脅的請求而將請求進行封裝來控制這些請求的行為的接口。
DefaultHttpFirewall是默認實現。主要是檢查一個路徑是否合法:
/** * Checks whether a path is normalized (doesn't contain path traversal sequences like "./", "/../" or "/.") * * @param path the path to test * @return true if the path doesn't contain any path-traversal character sequences. */ private boolean isNormalized(String path) { if (path == null) { return true; } for (int j = path.length(); j > 0;) { int i = path.lastIndexOf('/', j - 1); int gap = j - i; if (gap == 2 && path.charAt(i+1) == '.') { // ".", "/./" or "/." return false; } else if (gap == 3 && path.charAt(i+1) == '.'&& path.charAt(i+2) == '.') { return false; } j = i; } return true; }
FirewalledRequest是個抽象類,是請求request的封裝,返回一個HttpFirewall接口。不同之處在於reset方法,該方法允許當請求離開security filter chain時重置部分或者全部狀態。默認實現為RequestWrapper。
8. header模塊
HeaderWriter是一個向HttpServletResponse寫入http請求頭的約定。
HeaderWriterFilter向當前請求中增加http請求頭的過濾器,為瀏覽器保護增加特定的http請求頭。如X-FRAME-options(X-Frame-Options response header 可用於指示是否應該允許瀏覽器呈現在一個頁面<FRAME> 或 <IFRAME>中. 以確保網站內容是不是嵌入到其它網站. )、x-xss-protection(在正常情況下,通過下面的HTTP header,就可以完美的關閉發送這個header的頁面XSS保護特性了。X-XSS-Protection: 0)和x-content-type-options(這個header主要用來防止在IE9、chrome和safari中的MIME類型混淆攻擊。firefox目前對此還存在爭議。通常瀏覽器可以通過嗅探內容本身的方法來決定它是什么類型,而不是看響應中的content-type值。通過設置 X-Content-Type-Options:如果content-type和期望的類型匹配,則不需要嗅探,只能從外部加載確定類型的資源。)。
9.session模塊
ConcurrentSessionFilter:這個filter有兩個功能。第一:它調用SessionRegistry的refreshLastRequest(String)方法來保證注冊的session通常擁有正確的最后一次更新時間。
第二:它從每個請求的SessionRegistry中檢索SessionInformation,並檢查session是否已經標示為過期。若標示為過期,則調用配置的所有logout handler(在LogoutFilter中調用),通常的場景是使session過期。此時會跳轉到指定的expiredURL,session過期會通過注冊在<web.xml>的HttpSessionEventPublisher產生一個HttpSessionDestroyedEvent事件。
10 小結
Spring Security對Web安全性的支持大量地依賴於Servlet過濾器。這些過濾器攔截進入請求,並且在應用程序處理該請求之前進行某些安全處理。 Spring Security提供有若干個過濾器,它們能夠攔截Servlet請求,並將這些請求轉給認證和訪問決策管理器處理,從而增強安全性。
參考文獻:
1. http://www.ibm.com/developerworks/cn/java/j-lo-springsecurity/
2. http://baike.baidu.com/link?url=mk6ZedYayBqvXehW094XdImcU9g2SGLgt-gmdKYAF17db97_mEloPPm3K-1eqqEymefNNO30b2CTGw2Ryf6amq