javascript
Spring安全–幕后
安全任務(wù)(例如,用戶身份驗(yàn)證和用戶查看應(yīng)用程序資源的授權(quán))通常由應(yīng)用程序服務(wù)器處理。 可以將這些任務(wù)委托給Spring安全性流程,以減輕應(yīng)用程序服務(wù)器處理這些任務(wù)的負(fù)擔(dān)。 Spring安全性基本上通過實(shí)現(xiàn)標(biāo)準(zhǔn)javax.servlet.Filter來處理這些任務(wù)。 為了在應(yīng)用程序中初始化Spring安全性,您需要在web.xml中聲明以下過濾器:
現(xiàn)在,這個(gè)過濾器(springSecurityFilterChain)僅將請求委托給Spring安全框架,在此框架中定義的安全任務(wù)將由在應(yīng)用程序上下文中定義的安全過濾器處理。 那么這是怎么發(fā)生的呢?
在DelegatingFilterProxy(javax.servlet.Filter的實(shí)現(xiàn))的doFilter方法內(nèi),將檢查spring應(yīng)用程序上下文中是否有名為“ springSecurityFilterChain”的bean。 這個(gè)'springSecurityFilterChain'bean實(shí)際上是為spring過濾器鏈定義的別名。
<alias name="filterChainProxy" alias="springSecurityFilterChain"/>因此,當(dāng)在應(yīng)用程序上下文中完成檢查時(shí),它將返回filterChainProxy bean。 此過濾器鏈與javax.servlet.FilterChain的鏈不同,web.xml中定義的Java過濾器使用javax.servlet.FilterChain來調(diào)用下一個(gè)可能的過濾器(如果存在的話)或?qū)⒄埱髠鬟f給servlet / jsp。 bean filterChainProxy包含在Spring應(yīng)用程序上下文中定義的安全過濾器的有序列表。 所以這是下一組問題:
1.誰初始化/定義這個(gè)filterChainProxy?
2.在Spring應(yīng)用程序上下文中定義了哪些安全過濾器?
3.這些安全過濾器與web.xml中定義的普通過濾器有何不同?
現(xiàn)在是第一個(gè)問題,當(dāng)在應(yīng)用程序上下文中定義了安全名稱空間的?http?元素時(shí),將初始化filterChainProxy。 這是?http?元素的基本結(jié)構(gòu):
<sec:http auto-config="true"><sec:intercept-url pattern="/**" access="ROLE_USER" /> </sec:http><sec:authentication-manager id="authenticationManager"><sec:authentication-provider><sec:user-service><sec:user name="admin" password="password" authorities="ROLE_USER, ROLE_ADMIN" /><sec:user name="user" password="password" authorities="ROLE_USER" /></sec:user-service></sec:authentication-provider> </sec:authentication-manager>現(xiàn)在,來自Spring框架的HttpSecurityBeanDefinitionParser讀取了這個(gè)?http?元素,以在應(yīng)用程序上下文中注冊filterChainProxy。 將auto-config設(shè)置為true的http元素實(shí)際上是以下內(nèi)容的簡寫形式:
<sec:http><sec:form-login /><sec:http-basic /><sec:logout /> </sec:http>稍后我們將討論?http?的子元素。 因此,現(xiàn)在提到第二個(gè)問題,默認(rèn)情況下,所有過濾器都在過濾器鏈中注冊了什么? 這是Spring文檔的答案:
?http?名稱空間塊始終創(chuàng)建一個(gè)SecurityContextPersistenceFilter , ExceptionTranslationFilter和FilterSecurityInterceptor 。 這些是固定的,不能用替代方法替代。
因此,默認(rèn)情況下,當(dāng)我們添加?http?元素時(shí),將添加以上三個(gè)過濾器。 并且由于將auto-config設(shè)置為true,BasicAuthenticationFilter,LogoutFilter和UsernamePasswordAuthenticationFilter也被添加到過濾器鏈中。 現(xiàn)在,如果您查看任何這些過濾器的源代碼,它們也是標(biāo)準(zhǔn)的javax.servlet.Filter實(shí)現(xiàn)。 但是,通過在應(yīng)用程序上下文而不是在web.xml中定義這些過濾器,應(yīng)用程序服務(wù)器會(huì)將控件轉(zhuǎn)移到Spring以處理與安全性相關(guān)的任務(wù)。 Spring的filterChainProxy將負(fù)責(zé)鏈接將應(yīng)用于請求的安全過濾器。 這回答了第三個(gè)問題。
為了更好地控制要應(yīng)用于請求的安全篩選器,我們可以定義自己的FilterChainProxy實(shí)現(xiàn)。
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"><sec:filter-chain-map path-type="ant"><sec:filter-chain pattern="/images/*" filters="none"/><sec:filter-chain pattern="/**" filters="securityContextFilter, logoutFilter, formLoginFilter, servletApiFilter, anonFilter, exceptionTranslator, filterSecurityInterceptor, customFilter1, customeFilter2" /></sec:filter-chain-map> </bean>從上面的xml中,我們看到我們不希望對圖像應(yīng)用任何過濾器,而對于其余的請求,則指定了必須應(yīng)用的一系列過濾器。 因此,通常,我們按照從最小約束到最大約束的順序指定過濾器鏈。 但是通常不需要這種注冊我們自己的過濾器鏈的方法。 Spring通過?http?元素提供了多個(gè)掛鉤,通過這些掛鉤我們可以更好地控制安全性的應(yīng)用方式。 因此,我們將詳細(xì)介紹所有可以通過?http?元素配置的內(nèi)容。
1.身份驗(yàn)證:HttpBasicAuthentication和基于表單登錄的身份驗(yàn)證
2.通過ACL(訪問控制列表)的授權(quán)支持
3.注銷支持 4.匿名登錄支持 5.記住我身份驗(yàn)證 6.并發(fā)會(huì)話管理
(1)身份驗(yàn)證:身份驗(yàn)證可以通過兩種方式處理-HttpBasicAuthentication和基于表單登錄的身份驗(yàn)證。 我們將在短期內(nèi)簡要討論這兩個(gè)。 在理解這些內(nèi)容之前,最好對AuthenticationManager有基本的了解,它是通過Spring安全性實(shí)現(xiàn)身份驗(yàn)證的核心。 在身份驗(yàn)證管理器元素內(nèi),我們定義了可用于該應(yīng)用程序的所有身份驗(yàn)證提供程序。 身份驗(yàn)證提供程序包含UserDetailsS??ervice的實(shí)現(xiàn)。 Spring將用戶信息加載到UserDetailsS??ervice中,并將用戶名/密碼組合與登錄時(shí)提供的憑據(jù)進(jìn)行比較。 這是UserDetailsS??ervice接口:
package org.springframework.security.core.userdetails;import org.springframework.dao.DataAccessException;/*** Core interface which loads user-specific data.* It is used throughout the framework as a user DAO and is the strategy used by the* {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider DaoAuthenticationProvider}.* The interface requires only one read-only method, which simplifies support for new data-access strategies.* @see org.springframework.security.authentication.dao.DaoAuthenticationProvider* @see UserDetails* @author Ben Alex*/ public interface UserDetailsService {/*** Locates the user based on the username. In the actual implementation, the search may possibly be case* insensitive, or case insensitive depending on how the implementation instance is configured. In this case, the* <code>UserDetails</code> object that comes back may have a username that is of a different case than what was* actually requested..** @param username the username identifying the user whose data is required.** @return a fully populated user record (never <code>null</code>)** @throws UsernameNotFoundException if the user could not be found or the user has no GrantedAuthority* @throws DataAccessException if user could not be found for a repository-specific reason*/UserDetails loadUserByUsername(String username)throws UsernameNotFoundException, DataAccessException; }Spring提供了此服務(wù)的兩個(gè)內(nèi)置實(shí)現(xiàn):
(a)在應(yīng)用程序上下文中存儲(chǔ)用戶登錄名/密碼詳細(xì)信息:
當(dāng)應(yīng)用程序的用戶很少時(shí),這非常適合。 可以如下初始化:
<sec:authentication-manager id="authenticationManager"><sec:authentication-provider><sec:user-service><sec:user name="admin" password="password" authorities="ROLE_ADMIN,ROLE_USER"/><sec:user name="user" password="password" authorities="ROLE_USER"/></sec:user-service></sec:authentication-provider> </sec:authentication-manager>?authentication-provider?標(biāo)記對應(yīng)于DaoAuthenticationProvider,它實(shí)際上調(diào)用提供的UserDetailsS??ervice的實(shí)現(xiàn)。 在這種情況下,我們將直接以XML提供用戶名和密碼。 當(dāng)應(yīng)用程序的用戶群很大時(shí),我們希望將信息存儲(chǔ)在數(shù)據(jù)庫中。
為?user-service?初始化的對應(yīng)的Bean是org.springframework.security.core.userdetails.memory.InMemoryDaoImpl
(b)在數(shù)據(jù)庫中存儲(chǔ)用戶詳細(xì)信息:這是必須初始化的方式。
<sec:authentication-manager id="authenticationManager"><sec:authentication-provider><sec:jdbc-user-service data-source-ref="dataSource" /></sec:authentication-provider> </sec:authentication-manager>Spring中的相應(yīng)類是org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl。 如果查看此類,則可以發(fā)現(xiàn)用戶名和密碼存儲(chǔ)在users表中,可以分配給用戶的角色存儲(chǔ)在Authoritys表中。 稍后我們將討論角色。 這些是此類從數(shù)據(jù)庫中獲取用戶憑據(jù)和權(quán)限的查詢:
-- Fetch user credentials: select username,password,enabled from users where username = ? -- Fetch user authorities: select username,authority from authorities where username = ?現(xiàn)在假設(shè)您有一個(gè)舊數(shù)據(jù)庫,您的用戶詳細(xì)信息存儲(chǔ)在其他表中,然后我們可以配置Spring為獲取用戶憑據(jù)和權(quán)限而執(zhí)行的獲取查詢。 假設(shè)我有一個(gè)成員表,其中包含ID,用戶名,密碼字段,以及角色表,其中包含用戶名,角色字段。 這是我們必須配置的方式:
<sec:authentication-manager id="authenticationManager"><sec:authentication-provider><!-- TBD <password-encoder hash="md5"/> --><sec:jdbc-user-service id="userDetailsService" data-source-ref="dataSource" users-by-username-query="SELECT username, password, true as enabledFROM MEMBERWHERE username=?"authorities-by-username-query="SELECT member.username, role.role as authoritiesFROM ROLE role, MEMBER memberWHERE role.member_id=member.id and member.username=?"/></sec:authentication-provider> </sec:authentication-manager>現(xiàn)在介紹執(zhí)行身份驗(yàn)證的方法:
HttpBasicAuthentication:可以如下配置:
<sec:http auto-config="true"><sec:http-basic /> </sec:http>默認(rèn)情況下,啟用此選項(xiàng)后,瀏覽器通常會(huì)顯示一個(gè)登錄對話框供用戶登錄。 代替登錄對話框,我們可以配置它以顯示特定的登錄頁面。 這種身份驗(yàn)證是在超文本傳輸??協(xié)議標(biāo)準(zhǔn)中正式定義的。 登錄憑據(jù)(以base 64編碼)已通過Authentication http標(biāo)頭發(fā)送到服務(wù)器。 但是它有它自己的缺點(diǎn)。 最大的問題與注銷服務(wù)器有關(guān)。 大多數(shù)瀏覽器傾向于緩存會(huì)話,不同的用戶無法通過刷新瀏覽器重新登錄。 定義?http-basic?實(shí)際上在幕后定義了BasicAuthenticationFilter過濾器。 身份驗(yàn)證成功后,身份驗(yàn)證對象將放入Spring securityContext中。 可以通過類SecurityContextHolder訪問安全上下文。 這是BasicAuthenticationFilter bean聲明的樣子:
<sec:custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthenticationFilter" /><bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"><property name="authenticationManager" ref="authenticationManager"/><property name="authenticationEntryPoint" ref="authenticationEntryPoint"/> </bean><bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"><property name="loginFormUrl" value="/login.jsp"/> </bean>有關(guān)過濾器位置的更多詳細(xì)信息,請參閱枚舉org.springframework.security.config.http.SecurityFilters
基于表單登錄的身份驗(yàn)證:這是我們啟用它的方式:
<sec:form-login login-page="/login.jsp"/>但是Spring提供了多個(gè)鉤子。 屬性default-target-url指定登錄頁面在用戶進(jìn)行身份驗(yàn)證后應(yīng)該進(jìn)入的位置,而authentication-failure-url定義在身份驗(yàn)證失敗時(shí)用戶應(yīng)該進(jìn)入的頁面。
<sec:form-login login-page="/login.jsp" default-target-url="/app/messagePost" authentication-failure-url="/login.jsp?error=true"/>下一組屬性是: 始終使用默認(rèn)目標(biāo),身份驗(yàn)證成功處理程序引用和身份驗(yàn)證失敗處理程序引用 。 身份驗(yàn)證成功時(shí),將調(diào)用authentication-success-handler-ref , 身份驗(yàn)證失敗時(shí),將調(diào)用authentication-failure-handler-ref 。 這是AuthenticationSuccessHandler和AuthenticationFailureHandler的接口。
/*** Strategy used to handle a successful user authentication.* <p>* Implementations can do whatever they want but typical behaviour would be to control the navigation to the* subsequent destination (using a redirect or a forward). For example, after a user has logged in by submitting a* login form, the application needs to decide where they should be redirected to afterwards* (see {@link AbstractAuthenticationProcessingFilter} and subclasses). Other logic may also be included if required.** @author Luke Taylor* @since 3.0*/ public interface AuthenticationSuccessHandler {/*** Called when a user has been successfully authenticated.** @param request the request which caused the successful authentication* @param response the response* @param authentication the <tt>Authentication</tt> object which was created during the authentication process.*/void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException; }/*** Strategy used to handle a failed authentication attempt.* <p>* Typical behaviour might be to redirect the user to the authentication page (in the case of a form login) to* allow them to try again. More sophisticated logic might be implemented depending on the type of the exception.* For example, a {@link CredentialsExpiredException} might cause a redirect to a web controller which allowed the* user to change their password.** @author Luke Taylor* @since 3.0*/ public interface AuthenticationFailureHandler {/*** Called when an authentication attempt fails.* @param request the request during which the authentication attempt occurred.* @param response the response.* @param exception the exception which was thrown to reject the authentication request.*/void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,AuthenticationException exception) throws IOException, ServletException; }Spring有2個(gè)內(nèi)置的成功處理程序?qū)崿F(xiàn)。 SimpleUrlAuthenticationSuccessHandler和SavedRequestAwareAuthenticationSuccessHandler。 后者擴(kuò)展了前者。
SavedRequestAwareAuthenticationSuccessHandler的目的是將用戶帶到他已重定向到Login頁面進(jìn)行身份驗(yàn)證的頁面。這是定義了form-login?元素時(shí)的默認(rèn)成功處理程序。 我們也可以使用自定義實(shí)現(xiàn)來覆蓋它。 假設(shè)我們總是希望在用戶登錄后顯示特定頁面,而不是讓他進(jìn)入他之前所在的頁面,我們可以將always-use-default-target設(shè)置為true。
對于故障處理程序,還有兩種內(nèi)置的實(shí)現(xiàn):SimpleUrlAuthenticationFailureHandler和ExceptionMappingAuthenticationFailureHandler。 后者擴(kuò)展了前者。
我們僅在SimpleUrlAuthenticationFailureHandler的情況下指定單個(gè)URL,在身份驗(yàn)證失敗的情況下將引導(dǎo)用戶進(jìn)入,在ExceptionMappingAuthenticationFailureHandler的情況下,我們根據(jù)身份驗(yàn)證異常的類型指定用戶將被引導(dǎo)到的多個(gè)URL(org.springframework的子類.security.core.AuthenticationException)在身份驗(yàn)證過程中拋出(UserDetailsS??ervice實(shí)現(xiàn)將引發(fā)異常)。
同樣,在定義自定義登錄頁面時(shí),我們將用戶名和密碼字段分別標(biāo)記為j_username和j_password ,并且Submit操作將默認(rèn)為j_spring_security_check 。 我們還可以配置這些字段名稱,并通過分別指定以下屬性來提交操作: 用戶名參數(shù),密碼參數(shù)和login-processing-url 。
過濾器定義如下所示:
<sec:custom-filter position="FORM_LOGIN_FILTER" ref="formLoginFilter" /> <bean id="formLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"><property name="authenticationManager" ref="authenticationManager"/><property name="filterProcessesUrl" value="/j_spring_security_check"/><property name="usernameParameter" value="username "/><property name="passwordParameter" value="password"/><property name="authenticationSuccessHandler"><bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler "><property name="alwaysUseDefaultTargetUrl" value="true"/><property name="defaultTargetUrl" value="/success.jsp"/></bean></property><property name="authenticationFailureHandler"><!--bean class=" org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler "/--><bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler"><property name="exceptionMappings"><props><prop key="org.springframework.security.authentication.BadCredentialsException">/login/badCredentials</prop><prop key="org.springframework.security.authentication.CredentialsExpiredException">/login/credentialsExpired</prop><prop key="org.springframework.security.authentication.LockedException">/login/accountLocked</prop><prop key="org.springframework.security.authentication.DisabledException">/login/accountDisabled</prop></props></property></bean></property></bean>在表單登錄的情況下,如基本身份驗(yàn)證中所述,注銷不會(huì)有任何問題。 但是缺點(diǎn)是用戶名和密碼在標(biāo)題中以明文形式發(fā)送。 這可以通過使用加密技術(shù)對密碼進(jìn)行編碼來解決。 Spring使用身份驗(yàn)證提供程序中的?password-encoder?元素對此提供了內(nèi)置支持。 這是我們必須如何配置它:
<sec:authentication-manager id="authenticationManager"><sec:authentication-provider><sec:password-encoder hash="md5"/><sec:jdbc-user-service data-source-ref="dataSource" /></sec:authentication-provider> </sec:authentication-manager>2.通過ACL進(jìn)行授權(quán)支持: Spring通過?http?中的?intercept-url?支持授權(quán)。
<sec:http access-decision-manager-ref="accessDecisionManager"><sec:intercept-url pattern="/app/messageList*" access="ROLE_USER,ROLE_ANONYMOUS"/><sec:intercept-url pattern="/app/messagePost*" access="ROLE_USER"/><sec:intercept-url pattern="/app/messageDelete*" access="ROLE_ADMIN"/><sec:intercept-url pattern="/app/*" access="ROLE_USER"/><form-login login-page="/login.jsp" default-target-url="/app/messagePost" authentication-failure-url="/login.jsp?error=true"/><!-- Other settings --> </sec:http>每個(gè)intercept-url指定一個(gè)URL模式,用戶訪問這些與指定模式匹配的URL必須具有的角色。 請注意,網(wǎng)址格式始終以“ *”結(jié)尾。 如果未指定“ *”,則問題是黑客可以通過僅在url中傳遞一些參數(shù)來繞過安全機(jī)制。
因此,在幕后發(fā)生的事情是當(dāng)Spring將所有這些URL作為元數(shù)據(jù)傳遞給FilterSecurityInterceptor時(shí)被攔截。 因此,這是不使用?intercept-url?即可配置的方法:
<sec:custom-filter position="FILTER_SECURITY_INTERCEPTOR" ref="filterSecurityInterceptor" /> <bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"><property name="authenticationManager" ref="authenticationManager"/><property name="accessDecisionManager" ref="accessDecisionManager"/><property name="securityMetadataSource"><sec:filter-security-metadata-source lowercase-comparisons="true" request-matcher="ant" use-expressions="true"><sec:intercept-url pattern="/app/messageList*" access="ROLE_USER,ROLE_ANONYMOUS"/><sec:intercept-url pattern="/app/messagePost*" access="ROLE_USER"/><sec:intercept-url pattern="/app/messageDelete*" access="ROLE_ADMIN"/><sec:intercept-url pattern="/app/*" access="ROLE_USER"/></sec:filter-security-metadata-source></property> </bean>因此,從上面的代碼中您可以看到匿名用戶只能訪問messageList頁面,并且要查看其他任何頁面,他應(yīng)該以用戶身份登錄到應(yīng)用程序。 同樣,如果您仔細(xì)觀察Bean聲明,則有一個(gè)屬性'accessDecisionManager'。 這樣做的目的是什么?
實(shí)際上是由Bean做出訪問控制決策。 它必須實(shí)現(xiàn)AccessDecisionManager接口。 Spring提供了三個(gè)內(nèi)置的訪問決策管理器。 在了解訪問決策管理器的工作原理之前,我們需要知道AccessDecisionVoter到底是什么。 AccessDecisionManager實(shí)際上由一個(gè)或多個(gè)訪問決策投票者組成。 該投票者封裝了允許/拒絕/放棄用戶查看資源的邏輯。 投票棄權(quán)或多或少類似于根本不投票,因此投票結(jié)果由AccessDecisionVoter接口中定義的ACCESS_GRANTED,ACCESS_DENIED和ACCESS_ABSTAIN常量字段表示。 我們可以定義自定義訪問決策投票者,并將其注入我們的訪問決策管理器定義中。 現(xiàn)在回到內(nèi)置的決策管理器,他們是:
默認(rèn)情況下,將基于AffirmativeBased的訪問決策管理器初始化,其中包含2個(gè)投票者:RoleVoter和AuthenticatedVoter。 如果用戶具有所需角色,RoleVoter會(huì)授予訪問權(quán)限。 但是請注意,如果投票者必須授予訪問權(quán)限,則該角色必須以“ ROLE_”前綴開頭。 但這也可以為其他前綴定制。 我們將很快看到如何做。 AuthenticatedVoter僅在用戶通過身份驗(yàn)證時(shí)才授予訪問權(quán)限。 接受的身份驗(yàn)證級別為IS_AUTHENTICATED_FULLY,IS_AUTHENTICATED_REMEMBERED和IS_AUTHENTICATED_ANONYMOUSLY。 假設(shè)我們要定義一個(gè)自定義投票器,并將其添加到訪問決策管理器中,我們可以這樣做:
<sec:http access-decision-manager-ref="accessDecisionManager" auto-config="true"><!-- filters declaration go here--> </sec:http><bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"><property name="decisionVoters"><list><bean class="org.springframework.security.access.vote.RoleVoter"><!-- Customize the prefix--><property name="rolePrefix" value="ROLE_"/></bean><bean class="org.springframework.security.access.vote.AuthenticatedVoter"/><bean class="com.pramati.security.voters.CustomVoter"/></list></property> </bean>3.注銷支持: Spring提供了一個(gè)處理程序來處理注銷請求。 可以配置如下:
<sec:http><!-- Other filter declarations here --><sec:logout /></sec:http>默認(rèn)情況下,注銷URL映射到/ j_spring_security_logout 。 我們可以通過指定logout-url屬性來自定義該URL。 同樣,當(dāng)用戶注銷時(shí),他將被帶到上下文路徑根。 如果必須將用戶重定向到其他URL,則必須通過logout-success-url進(jìn)行配置。 這是您的操作方式:
<sec:logout logout-url="/j_logMeOut" logout-success-url="/app/messageList"/>如果您希望登錄頁面在不同的情況下有所不同,而不是默認(rèn)使用一個(gè)特定的網(wǎng)址,那么我們必須實(shí)現(xiàn)LogoutSuccessHandler并將其引用到?logout?元素
<sec:logout logout-url="/j_logMeOut" success-handler-ref="customLogoutSuccessHandler"/>如果您不想使用?logout?元素,則可以按以下方法定義底層過濾器:
<sec:custom-filter position="LOGOUT_FILTER" ref="logoutFilter" /> <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"><constructor-arg value="/pages/Security/logout.html" /><constructor-arg><list><bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/></list></constructor-arg><property name="filterProcessesUrl" value="/j_logMeOut"/> </bean>4.匿名登錄支持:默認(rèn)情況下,Spring創(chuàng)建一個(gè)匿名角色。
因此,當(dāng)您將角色指定為“ ROLE_ANONYMOUS”或“ IS_AUTHENTICATED_ANONYMOUSLY”時(shí),任何匿名用戶都可以查看該頁面。 在AffirmativedBased訪問決策管理器中,當(dāng)RoleVoter看到訪問屬性設(shè)置為“ ROLE_ANONYMOUS”時(shí),將授予訪問權(quán)限。 同樣,如果將訪問屬性設(shè)置為“ IS_AUTHENTICATED_ANONYMOUSLY”,則AuthenticatedVoter會(huì)授予訪問權(quán)限。
假設(shè)要為匿名用戶分配其他角色名稱,則可以按如下方式覆蓋默認(rèn)配置:
<sec:http><sec:intercept-url pattern="/login.jsp*" filters="none"/><sec:intercept-url pattern="/*" access="ROLE_USER"/><!-- Defines a custom role in place of ROLE_ANONYMOUS. ROLE_ANONYMOUS will no more work, use ROLE_GUEST instead of it--><sec:anonymous username="guest" granted-authority="ROLE_GUEST" /> </sec:http><p style="text-align: justify;">Here is the how the underlying filter can be defined if you don't want to use ?anonymous? element:</p>1 <sec:custom-filter position="ANONYMOUS_FILTER" ref="anonymousFilter" /> <bean id="anonymousFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter" ><property name="userAttribute" value="ROLE_GUEST" /> </bean>5.記住我身份驗(yàn)證:這是指網(wǎng)站能夠記住會(huì)話之間主體的身份。 Spring通過在成功進(jìn)行交互式身份驗(yàn)證后向?yàn)g覽器發(fā)送cookie來實(shí)現(xiàn)此目的,該cookie的組成如下:
base64(用戶名+“:” + expirationTime +“:” + md5Hex(用戶名+“:” + expirationTime +“:”密碼+“:” +鍵))
現(xiàn)在,當(dāng)瀏覽器向服務(wù)器發(fā)出下一個(gè)請求時(shí),它還將與此Cookie一起發(fā)送。 現(xiàn)在,Spring在幕后執(zhí)行以下操作:
(a)從后端檢索給定用戶名的密碼
(b)從數(shù)據(jù)庫中獲取用戶名的密碼,并計(jì)算用戶名,密碼,expirationTime和密鑰的md5Hex()并將其與Cookie中的值進(jìn)行比較 (c)如果它們匹配–您已登錄! 如果不匹配,則說明您提供了偽造的Cookie,或者用戶名/密碼/密鑰之一已更改。
我們可以通過在?http?中添加元素來啟用“記住我”身份驗(yàn)證。 這是我們的方法:
<sec:http><!-- Other filter declarations here --><sec:remember-me key="myAppKey"/></sec:http>通常將自動(dòng)選擇UserDetailsS??ervice。 如果您的應(yīng)用程序上下文中有多個(gè),則需要指定與user-service-ref屬性一起使用的屬性,其中值是UserDetailsS??ervice bean的名稱。 需要注意的一點(diǎn)是,這里存在一個(gè)潛在的安全問題,因?yàn)椤坝涀∥摇绷钆瓶梢员徊东@,并且由于它在到期之前一直有效而可能被濫用。 通過使用滾動(dòng)令牌可以避免這種情況。 這是您可以實(shí)現(xiàn)基于令牌的“記住我”服務(wù)的方法:
<sec:http access-decision-manager-ref="accessDecisionManager"><!-- Other filter declarations here --><remember-me services-alias="rememberMeService" data-source-ref="dataSource"/><!-- <remember-me data-source-ref="dataSource" key="pramati"/> --></sec:http><bean id="tokenRepository" class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"><property name="dataSource" ref="dataSource"/><property name="createTableOnStartup" value="true"/> </bean><bean id="rememberMeService" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"><property name="userDetailsService" ref="userDetailsService"/><property name="tokenRepository" ref="tokenRepository"/> </bean>注意事項(xiàng):
(a)因?yàn)樵诙xbean'tokenRepository'時(shí)已指定'createTableOnStartup'為true,所以將在數(shù)據(jù)庫中創(chuàng)建一個(gè)新表persistent_logins。 這是用于創(chuàng)建表的sql:
create table persistent_logins (username varchar(64) not null,series varchar(64) primary key,token varchar(64) not null,last_used timestamp not null);(b)我們不再提供自己的安全令牌。 Spring將自動(dòng)生成令牌并將其放入/更新到persistent_tokens表中。 當(dāng)用戶從瀏覽器訪問應(yīng)用程序并通過選擇“記住我”選項(xiàng)登錄到應(yīng)用程序時(shí),將在此表中創(chuàng)建一個(gè)條目。 下次用戶從同一瀏覽器登錄時(shí),用戶將自動(dòng)登錄,并且數(shù)據(jù)庫中的令牌值將更改為新值,但系列值保持不變。 假設(shè)用戶現(xiàn)在從其他瀏覽器登錄并選擇記住我,那么將為該瀏覽器創(chuàng)建一個(gè)新條目。 當(dāng)他從該瀏覽器訪問應(yīng)用程序時(shí),隨后的更新將發(fā)生在該特定行本身上。
因此,使用這種方法的好處是,攻擊者將只能使用被盜的cookie,直到受害者用戶下次訪問該應(yīng)用程序?yàn)橹?#xff0c;而不是像先前的單令牌方法那樣在記住的cookie的整個(gè)生存期內(nèi)使用它。 當(dāng)受害者下一次訪問該網(wǎng)站時(shí),他將使用相同的cookie。 現(xiàn)在,Spring將拋出CookieTheftException,該異??捎糜谕ㄖ脩舭l(fā)生了盜竊。
可以使用安全鏈中的自定義過濾器來定義它,而不用使用到目前為止已使用的元素:
<sec:custom-filter position="REMEMBER_ME_FILTER" ref="rememberMeFilter" /><bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter"><property name="rememberMeServices" ref="rememberMeServices"/><property name="authenticationManager" ref="theAuthenticationManager" /> </bean><bean id="tokenRepository" class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"><property name="dataSource" ref="dataSource"/><property name="createTableOnStartup" value="false"/> </bean><bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"><property name="userDetailsService" ref="userDetailsService"/><property name="tokenRepository" ref="tokenRepository"/> </bean><bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.rememberme.RememberMeAuthenticationProvider"/>6.并發(fā)會(huì)話管理:假設(shè)我們不希望用戶同時(shí)從多個(gè)位置登錄到應(yīng)用程序,我們必須在Spring中啟用此功能。 這是我們的方法:
<sec:http><!-- Other filter declarations here --><sec:session-management session-authentication-error-url="/login.jsp?error=alreadyLoggedin" ><sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="true"expired-url="/login.jsp?error=alreadyLoggedin"/></sec:session-management> </sec:http>此外,我們還必須在web.xml中定義一個(gè)偵聽器,當(dāng)用戶從應(yīng)用程序注銷時(shí),該偵聽器對于引發(fā)事件(org.springframework.security.core.session.SessionDestroyedEvent)是必需的。
<listener><listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener>那么,現(xiàn)在幕后發(fā)生了什么? Spring如何獲得這種支持?
Spring使用的安全過濾器類似于我們一直在討論的內(nèi)容。 除此之外,它還使用ApplicationEvents。 當(dāng)spring看到需要并發(fā)控制時(shí),它將維護(hù)與主體相關(guān)聯(lián)的會(huì)話列表。 映射結(jié)構(gòu)如下所示(實(shí)際上在org.springframework.security.core.session.SessionRegistryImpl中定義):
ConcurrentMap<Object,Set<String>> principals =new ConcurrentHashMap<Object,Set<String>>();此處映射的鍵是User對象,該值是與他相關(guān)聯(lián)的會(huì)話ID的集合。 因此,當(dāng)集合的大小大于?concurrency-control?元素中定義的最大會(huì)話的值時(shí),將引發(fā)異常。 當(dāng)Spring看到定義的并發(fā)控制元素時(shí),SessionRegistryImpl(定義映射的地方)將在ConcurrentSessionControlStrategy內(nèi)部組成,并注入到UsernamePasswordAuthenticationFilter中。 現(xiàn)在,隨著用戶身份驗(yàn)證成功,Spring在上面討論的映射中添加了一個(gè)條目。
現(xiàn)在,當(dāng)用戶注銷時(shí),將在web.xml中定義偵聽器時(shí)引發(fā)SessionDestroyedEvent,如上所示。 SessionRegistryImpl偵聽此事件,并從正在維護(hù)的映射中刪除會(huì)話ID條目。 否則,即使用戶退出另一個(gè)會(huì)話或超時(shí),一旦超過會(huì)話允許量,用戶將永遠(yuǎn)無法再次登錄。 因此,這是?concurrency-control?的等效配置:
<sec:http><sec:custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" /><sec:custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" /><!-- Other filter declarations here --><sec:session-management session-authentication-strategy-ref="sessionAuthenticationStrategy"/> </sec:http><bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"><property name="sessionRegistry" ref="sessionRegistry" /><property name="expiredUrl" value="/session-expired.htm" /> </bean><bean id="myAuthFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"><property name="sessionAuthenticationStrategy" ref="sessionAuthenticationStrategy" /><property name="authenticationManager" ref="authenticationManager" /> </bean><bean id="sessionAuthenticationStrategy" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"><constructor-arg name="sessionRegistry" ref="sessionRegistry" /><property name="maximumSessions" value="1" /> </bean><bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" /> 作為本文的總結(jié),本文涉及了框架的基本配置和底層類,這對于根據(jù)我們的特定要求自定義安全性至關(guān)重要。
翻譯自: https://www.javacodegeeks.com/2013/11/spring-security-behind-the-scenes.html
總結(jié)
以上是生活随笔為你收集整理的Spring安全–幕后的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring交易可见性
- 下一篇: 电脑硬盘废物利用(废旧的电脑硬盘还可以这