使用SecurityContextHolder來偷窺登入帳號密碼,手段還真是不夠文雅。Spring-Security3是有提供取得登入資訊塞到Session的實踐,不過寫起來很煩,很煩也大概不易被破解^^。Google這方面的資訊,不是缺漏,就是講述古早的版本,還有中文網站,資訊雖新,卻常出現文章一大抄的謬誤,我目前是用3.0.2版,和3.0.1、3.0.0差異何在也不知,不過至少我這方面有踹通,實踐方式和Google內容還是有些關鍵性的差異:
第一個應該是security-context.xml的設定了:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"> <http use-expressions="true" auto-config="true"> <intercept-url pattern="/*.do" access="hasRole('ROLE_USER')"/> <intercept-url pattern="/login.jsp" access="isAnonymous()"/> <intercept-url pattern="/**" access="permitAll"/> <form-login login-processing-url="/j_spring_security_check" login-page="/login.jsp" default-target-url="/echo.do" authentication-failure-url="/login.jsp?error=1"/> <logout logout-url="/j_spring_security_logout" logout-success-url="/login.jsp"/> <custom-filter before="FORM_LOGIN_FILTER" ref="authenticationProcessingFilter"/> <session-management invalid-session-url="/timeout.html"> <concurrency-control max-sessions="1" /> </session-management> </http> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="securityManager"/> </authentication-manager> <beans:bean id="securityManager" class="com.foo.dao.impl.UserDetailsServiceImpl"/> <beans:bean id="authenticationProcessingFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <beans:property name="authenticationManager" ref="authenticationManager"/> <beans:property name="authenticationSuccessHandler"> <beans:bean class="com.foo.security.MyAuthenticationSuccessHandler"> <beans:property name="defaultTargetUrl" value="/echo.do"/> </beans:bean> </beans:property> <beans:property name="filterProcessesUrl" value="/j_spring_security_check"/> </beans:bean> </beans:beans> |
紅字是之前SecurityContextHolder飯粒多出來的設定,真正是靠腰的多。<http>標籤多出<custom-filter>指向authenticationProcessingFilter這個bean,而Google的before後所接的定字幾乎都錯,都是這麼寫:
<custom-filter before="AUTHENTICATION_PROCESSING_FILTER" ref="authenticationProcessingFilter"/> |
可是Filter的列舉(枚舉)型態裡,根本沒這有AUTHENTICATION_PROCESSING_FILTER這號人物,其它的參考下列表,我原本用CONCURRENT_SESSION_FILTER過不了關,後來改成FORM_LOGIN_FILTER才Pass,Spring-Seucrity 3這部份很像Struts2的Interceptor Stack,從下表來看,應該是有順序,LOGOUT_FILTER排在FORM_LOGIN_FILTER之前…嗯~~也蠻合理的。
Enumerated Values : - FIRST - CHANNEL_FILTER - CONCURRENT_SESSION_FILTER - SECURITY_CONTEXT_FILTER - LOGOUT_FILTER - X509_FILTER - PRE_AUTH_FILTER - CAS_FILTER - FORM_LOGIN_FILTER - OPENID_FILTER - BASIC_AUTH_FILTER - SERVLET_API_SUPPORT_FILTER - REMEMBER_ME_FILTER - ANONYMOUS_FILTER - EXCEPTION_TRANSLATION_FILTER - SESSION_MANAGEMENT_FILTER - FILTER_SECURITY_INTERCEPTOR - SWITCH_USER_FILTER - LAST |
<custom-filter>選擇FORM_LOGIN_FILTER時機委給Bean-authenticationProcessingFilter使用,這一動作出現頻道蓋台,<form-login>標籤應該失效,委給authenticationProcessingFilter處理。authenticationProcessingFilter顯然是指定org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter來處理的,此處在Google上又會得到錯誤類別名,正名是UsernamePasswordAuthenticationFilter,而Google的類別名是UsernamePasswordAuthenticationProcessingFilter,多出了Processing,所以我才猜是不是前版的遺留,至少在3.0.2版沒這個類別。
authenticationProcessingFilter要指定三類properties,第一個是指定Authentication Manager。若非這次的需求,SecurityContextHolder會找到<authentication-manager>這個標籤作為預設值。但既然委託給Spring Bean處理,所以要有的bean reference的name,所以後來才在<authentication-manager>加個alias屬性,這樣<authentication-manager>才能被authenticationProcessingFilter找到。
authenticationProcessingFilter要指定的第二類properties是本篇文章目的主程式所在,儲存username至session。最多有四種bean,最常用的是認證成功(AuthenticationSuccessHandler)和認證失敗(AuthenticationFailureHandler),尚有未認證的訪問及已認證訪問受保護的URL。而儲存至session的動作在第一種,是故AuthenticationSuccessHandler屬性指向MyAuthenticationSuccessHandler這個類別,其實作如下:
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { HttpSession session = request.getSession(); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); session.setAttribute("username", userDetails.getUsername()); super.onAuthenticationSuccess(request, response, authentication); } } |
和前一篇
SecurityContextHolder飯粒原理是一樣,只是比較文雅,而MyAuthenticationSuccessHandler下設了一個defaultTargetUrl屬性,其實也恰恰覆蓋<form-login>的default-target-url屬性。先前有言,<customer-filter>若設置為CONCURRENT_SESSION_FILTER過不了關,是session值在onAuthenticationSuccess的設定無法帶給defaultTargetUrl,所以改成FORM_LOGIN_FILTER就可以work。
第三類是filterProcessesUrl屬性,指authenticationProcessingFilter會對哪個URL Pattern產生作用,本例是指/j_spring_security_check,就因為custom-filter設置before="FORM_LOGIN_FILTER"搶在<form-login>攔截,致使<form-login>失效。