使用
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的類別名是UsernamePasswordAuthentication
ProcessingFilter,多出了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>失效。
Jemmy 發表在 痞客邦 留言(0) 人氣(979)
再回頭閱讀Spring-Security3的文件,它提供一個獨立於其它Context之外(包括HTTP的Session)的運作環境:SecurityContextHolder,所以透過j_spring_security_check登入的資訊需從此處取得,可以在Action、Custom Tag或一般Java Class取得,如下:
| Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { logger.debug("username=" + ((UserDetails)principal).getUsername()); logger.debug("pwd=" + ((UserDetails)principal).getPassword()); } else { logger.debug("username=" + ((UserDetails)principal).getUsername()); } |
偷窺登入者ID、Password過程有點麻煩,這也可知UserDetails是怎麼來的。
Jemmy 發表在 痞客邦 留言(0) 人氣(553)
使用Spring-Security3的session control,要先在web.xml做以下的設定:
| <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <listener> <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher </listener-class> </listener> <session-config> <session-timeout>2</session-timeout> <!-- Unit: minutes --> </session-config> |
很奇怪若把<listener-class>寫到同一個<listen>下會錯。OK,再來是設定Security-context.xml的內容:
| <http use-expressions="true" auto-config="true"> ... <session-management invalid-session-url="/timeout.html"> <concurrency-control max-sessions="1" /> </session-management> </http> |
通常只需設定invalid-session-url屬性導向某個頁面就好,若希望同時間對同一個user帳號只允許一個session來連結,可以加入<concurrency-control max-sessions="1"/>的設定。 有件事還解不出,就是invalid-session-url這屬性會蓋掉<logout>標籤的logout-success-url的屬性值,也就是當Logout成功,會被導到invalid-session-url屬性所指的URL。
Jemmy 發表在 痞客邦 留言(0) 人氣(447)
越成功的產品,越有細微的Bug,Spring3也不例外,在整合Spring-Security時(請參考
Spring-Security 3初體驗),<context:include-filter>的type不管怎麼指定,都會被Spring-Security使用AspectJ語法屏蔽,下例applicationContext.xml內容紅字部份即是明明指定regex type,卻可以用aspectJ語法,而指定的regex語法不能用。
| <context:component-scan base-package="com.foo" use-default-filters="false"> <context:include-filter type="regex" expression="com.foo.bar..*Config"/> <context:include-filter type="regex" expression="com.foo.util.aop.*"/> </context:component-scan> <aop:aspectj-autoproxy proxy-target-class="true"/> |
雖然我在web.xml對這兩context.xml做檔名改變,讓applicationContext.xml先載入,再載入Security的context.xml,有成功一次解掉屏蔽,之後第二天還是不行。不知為何?
Jemmy 發表在 痞客邦 留言(0) 人氣(78)
使用Spring Security 3來保護頁面。這篇Blog是寫得非常好的Spring-Security 3的入門飯粒。已於2010/2/19發行了3.0.2版了。 Spring-Security已把認證授權該實作的細節隱藏得相當好,開發者可只須注重業務層次。其官方文件強調Spring-Security是以Layer(層)作為Security控管的單位。業務層次的程式是以繼承UserDetails和UserDetailsService兩大介面為主,範例裡講得蠻清楚的。這裡是對Configruation file加以補述。 第一步是設定web.xml。必要設定如下:
| <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/security-config.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
主要設定有二:一個是設定Filter(紅字部份),可以使Spring-Security在Web起作用,作用範圍就是url-pattern所指定的。第二個是畫有底線的security-config.xml,由Spring Context載入,為Spring Security設定所在。 第二步是Spring config的設定(security-config.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="/secure/protected.jsp" 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="/index.jsp" authentication-failure-url="/login.jsp?error=1"/> <logout logout-url="/j_spring_security_logout"/> </http> <authentication-manager> <authentication-provider user-service-ref="securityManager"/> </authentication-manager> <beans:bean id="securityManager" class="com.blogspot.lawpronotes.security.SecurityManager"/> </beans:beans> |
<http>標籤目的在於透過AOP方式指定URL Pattern的使用權限,其auto-config="true"的設定,可以在url pattern不用明確指定,比如pattern="/**"即是指其它的URL Pattern皆適用;而use-expression="true"的設定,可以在access屬性使用Spring EL(Spring Expression Language),是故:
hasRole('ROLE_USER'):只允許ROLE_USER存取。而像hasAnyRole('Role1', 'Role2', …)則可設定多角色存取。 isAnonymous():只允許尚未Login的存取。 permitAll:表示url pattern不限制任何角色皆可存取。 而Spring EL尚有以下的common built-in expression:principal、authentication、denyAll、isRememberMe()、isAuthenticated()、isFullyAuthenticated()等,以及Web Security Expression如:hasIpAddress。是故可以如下用and方式P設定:
| <intercept-url pattern="/admin*" access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/> |
除了Common built-in和Web Security的Expression之外,也提供annotation expression,如Method Security Expression,使用它的話事先要在context.xml宣告如下
| <global-method-security pre-post-annotations="enabled"/> |
如此一來才能在Bean Class的method做這樣的設定:
| @PreAuthorize("hasRole('ROLE_USER')") public void create(Contact contact); // OR @PreAuthorize("hasPermission(#contact, 'admin')") public void deletePermission(Contact contact, Sid recipient, Permission permission); // OR @PreAuthorize("#contact.name == principal.name)") public void doSomething(Contact contact); |
上述的security-context.xml尚未講到<form-login>和<logout>兩個標籤,各自對映到/j_spring_security_check和/j_spring_security_logout,這是Spring Security預設的Filter URL,當然可以改寫,但超出自身修維之外了。 整個流程是:
<http>標籤對url pattern過濾後轉給<authentication-manager>指定的bean處理,該bean即繼承UserDetailsService的class。 藉由該bean的loadUserByUsername這個method去存取DB或LDAP驗證,驗證成功再return繼承UserDetails的instance。 return後由Spring-Security去驗證密碼和授權,來決定是否允許access。 在上面步驟2和3之間,繼承UserDetails的instance在被New出時,就要設好其interface的屬性,重要如username、password和Authorizes等,這些來源應該在執行loadUserByUsername結束之前就要完成設置。 Jemmy 發表在 痞客邦 留言(0) 人氣(818)