在以前的教程中一筆帶過式的講了下RememberMe記住密碼的功能,那篇的Remember功能是最簡易的配置,其功能和安全性都不強。這裏就配置下security中RememberMe的各類方式。mysql
RememberMe 是指用戶在網站上可以在 Session 之間記住登陸用戶的身份的憑證,通俗的來講就是用戶登錄成功認證一次以後在制定的必定時間內能夠不用再輸入用戶名和密碼進行自動登陸。這個過程當中經過服務端發送一個 cookie 給客戶端瀏覽器保存,下次瀏覽器再訪問服務端時服務端可以自動檢測客戶端的 cookie,根據 cookie 值觸發自動登陸操做。Spring Security中的 Remember-Me 功能一般有兩種實現方式。一種是簡單的使用加密來保證基於 cookie 的 token 的安全,另外一種是經過數據庫或其它持久化存儲機制來保存生成的 token。
兩種方式的區別:第一中方式不安全,就是說在用戶獲取到實現記住我功能的 token 後,任何用戶均可以在該 token 過時以前經過該 token 進行自動登陸。若是用戶發現本身的 token 被盜用了,那麼他能夠經過改變本身的登陸密碼來當即使其全部的記住我 token 失效。若是但願咱們的應用可以更安全,那就使用第二種方式。第二種方式也是詳細要講解的。web
<beans:bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <beans:property name="usersByUsernameQuery" value="select username,password,status as enabled from user where username = ?" /> <beans:property name="authoritiesByUsernameQuery" value="select user.username,role.name from user,role,user_role where user.id=user_role.user_id and user_role.role_id=role.id and user.username=?" /> <beans:property name="dataSource" ref="dataSource" /> </beans:bean>
下面來配置rememberMe的過濾器:spring
<!-- Remember-Me 對應的 Filter --> <beans:bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter"> <beans:property name="rememberMeServices" ref="rememberMeServices" /> <beans:property name="authenticationManager" ref="authenticationManager" /> </beans:bean>
這個過濾器僅僅這樣配置是不會起做用的,還要把它加入的Security的FilterChain中去,用<custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>便可,另外這個過濾器要提供一個rememberMeServices和一個用戶認證的authenticationManager,後面這個其實就是authentication-manager所配置的東西,而前面這個須要另外配置,配置方式以下:sql
<bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices"> <property name="userDetailsService" ref="userDetailsService" /> <property name="key" value="zmc" /> <!-- 指定 request 中包含的用戶是否選擇了記住個人蔘數名 --> <property name="parameter" value="rememberMe"/> </bean>
這個配置中的userDetailsService就是上面配置的,直接引用上面的便可;key就是token中的key,這個key能夠用來方式token被修改,另外這個key要和後面配置的rememberMeAuthenticationProvider的key要同樣;parameter就是登錄界面的點擊記住密碼的checkbox的name值,這個必定要一直,要否則沒有效果的。
除此以外還要配置一個用戶記住密碼作認證的authenticationManager數據庫
<bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider"> <property name="key" value="zmc" /> </bean>
同時還要將其添加到authentication-manager標籤中去瀏覽器
<authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsService"> </authentication-provider> <!-- 記住密碼 --> <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider> </authentication-manager>
<?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/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <http pattern="/login.jsp" security="none"></http> <http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint"> <!-- <form-login login-page="/login.jsp" default-target-url="/index.jsp" authentication-failure-url="/login.jsp?error=true" /> --> <logout invalidate-session="true" logout-success-url="/login.jsp" logout-url="/j_spring_security_logout" /> <custom-filter ref="myUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER" /> <!--替換默認REMEMBER_ME_FILTER--> <custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/> <!-- 經過配置custom-filter來增長過濾器,before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默認的過濾器以前執行。 --> <custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" /> </http> <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/login.jsp" /> </beans:bean> <!-- 數據源 --> <beans:bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <!-- 此爲c3p0在spring中直接配置datasource c3p0是一個開源的JDBC鏈接池 --> <beans:property name="driverClass" value="com.mysql.jdbc.Driver" /> <beans:property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springsecuritydemo?useUnicode=true&characterEncoding=UTF-8" /> <beans:property name="user" value="root" /> <beans:property name="password" value="" /> <beans:property name="maxPoolSize" value="50"></beans:property> <beans:property name="minPoolSize" value="10"></beans:property> <beans:property name="initialPoolSize" value="10"></beans:property> <beans:property name="maxIdleTime" value="25000"></beans:property> <beans:property name="acquireIncrement" value="1"></beans:property> <beans:property name="acquireRetryAttempts" value="30"></beans:property> <beans:property name="acquireRetryDelay" value="1000"></beans:property> <beans:property name="testConnectionOnCheckin" value="true"></beans:property> <beans:property name="idleConnectionTestPeriod" value="18000"></beans:property> <beans:property name="checkoutTimeout" value="5000"></beans:property> <beans:property name="automaticTestTable" value="t_c3p0"></beans:property> </beans:bean> <beans:bean id="builder" class="com.zmc.demo.JdbcRequestMapBulider"> <beans:property name="dataSource" ref="dataSource" /> <beans:property name="resourceQuery" value="select re.res_string,r.name from role r,resc re,resc_role rr where r.id=rr.role_id and re.id=rr.resc_id" /> </beans:bean> <beans:bean id="myUsernamePasswordAuthenticationFilter" class="com.zmc.demo.MyUsernamePasswordAuthenticationFilter "> <beans:property name="filterProcessesUrl" value="/j_spring_security_check" /> <beans:property name="authenticationManager" ref="authenticationManager" /> <beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler" /> <beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler" /> <beans:property name="rememberMeServices" ref="rememberMeServices" /> </beans:bean> <beans:bean id="loginLogAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> <beans:property name="targetUrlParameter" value="/index.jsp" /> </beans:bean> <beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <beans:property name="defaultFailureUrl" value="/login.jsp" /> </beans:bean> <!-- 認證過濾器 --> <beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"> <!-- 用戶擁有的權限 --> <beans:property name="accessDecisionManager" ref="accessDecisionManager" /> <!-- 用戶是否擁有所請求資源的權限 --> <beans:property name="authenticationManager" ref="authenticationManager" /> <!-- 資源與權限對應關係 --> <beans:property name="securityMetadataSource" ref="securityMetadataSource" /> </beans:bean> <!-- acl領域模型 --> <beans:bean class="com.zmc.demo.MyAccessDecisionManager" id="accessDecisionManager"> </beans:bean> <!-- --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsService </authentication-provider> <!-- 記住密碼 --> <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider> </authentication-manager> <!-- 配置userDetailsService --> <beans:bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <beans:property name="usersByUsernameQuery" value="select username,password,status as enabled from user where username = ?" /> <beans:property name="authoritiesByUsernameQuery" value="select user.username,role.name from user,role,user_role where user.id=user_role.user_id and user_role.role_id=role.id and user.username=?" /> <beans:property name="dataSource" ref="dataSource" /> </beans:bean> <!-- Remember-Me 對應的 Filter --> <beans:bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter"> <beans:property name="rememberMeServices" ref="rememberMeServices" /> <beans:property name="authenticationManager" ref="authenticationManager" /> </beans:bean> <!-- rememberService --> <!-- RememberMeServices 的實現 --> <beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices"> <beans:property name="userDetailsService" ref="userDetailsService" /> <beans:property name="key" value="zmc" /> <!-- 指定 request 中包含的用戶是否選擇了記住個人蔘數名 --> <beans:property name="parameter" value="rememberMe" /> </beans:property> </beans:bean> <!-- 記住密碼 --> <!-- key 值需與對應的 RememberMeServices 保持一致 --> <beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider"> <beans:property name="key" value="zmc" /> </beans:bean> <beans:bean id="securityMetadataSource" class="com.zmc.demo.MyFilterInvocationSecurityMetadataSource"> <beans:property name="builder" ref="builder"></beans:property> </beans:bean> </beans:beans>
由於這個例子是在以前的例子上進行修改的,因此其它的沒講到的一些配置在以前博客都有詳細的講解,請參考以前的博客。安全
在講這配置以前先對上面rememberService中的實現類TokenBasedRememberMeServices進行簡單的講解,該類主要是基於簡單加密 token 的一個實現類。TokenBasedRememberMeServices 會在用戶選擇了記住我成功登陸後,生成一個包含 token 信息的 cookie 發送到客戶端;若是用戶登陸失敗則會刪除客戶端保存的實現 Remember-Me 的 cookie。須要自動登陸時,它會判斷 cookie 中所包含的關於 Remember-Me 的信息是否與系統一致,一致則返回一個 RememberMeAuthenticationToken 供 RememberMeAuthenticationProvider 處理,不一致則會刪除客戶端的 Remember-Me cookie。TokenBasedRememberMeServices 還實現了 Spring Security 的 LogoutHandler 接口,因此它能夠在用戶退出登陸時當即清除 Remember-Me cookie。cookie
而基礎持久化方式配置的實質就是這個類不一樣,基於持久化方式配置的所用的實現類爲:PersistentTokenBasedRememberMeServices,一看名字就知道其做用,就是將token進行持久化保存起來,要保存數據相應的就是爲其制定保存的地方,這個保存的地方就是用PersistentTokenRepository來指定的,Spring Security 對此有兩種實現,InMemoryTokenRepositoryImpl 和 JdbcTokenRepositoryImpl。前者是將 token 存放在內存中的,一般用於測試,然後者是將 token 存放在數據庫中。PersistentTokenBasedRememberMeServices 默認使用的是前者,咱們能夠經過其 tokenRepository 屬性來指定使用的 PersistentTokenRepository。這例子用JdbcTokenRepositoryImpl來進行持久化保存,顯然要往數據庫保存數據,確定要有一張表,這個表security也有提供,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)。在數據庫中建立該表便可。session
建立後的表爲:jsp
因此持久化方式配置只須要將第一種方式的TokenBasedRememberMeServices進行修改就能夠,用如下代碼提換就能夠了:
<beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"> <beans:property name="userDetailsService" ref="userDetailsService" /> <beans:property name="key" value="zmc" /> <!-- 指定 request 中包含的用戶是否選擇了記住個人蔘數名 --> <beans:property name="parameter" value="rememberMe" /> <beans:property name="tokenRepository"> <beans:bean class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"> <!-- 數據源 --> <beans:property name="dataSource" ref="dataSource"/> <!-- 是否在啓動時建立持久化 token 的數據庫表 若爲true,但數據有這個表時,會啓動失敗,提示表已存在 --> <beans:property name="createTableOnStartup" value="false"/> </beans:bean> </beans:property> </beans:bean>
從上面的圖能夠看出,但沒點擊2周不用登錄的時候,登錄後再退出,再訪問資源的時候就要求從新登錄。同時數據庫也不會新增數據。
從上圖能夠看到,當勾選2周不用登錄在登錄後,就算退出登錄後,再訪問資源也能夠不用直接訪問,同時,數據庫也會將登錄信息保存起來,比較兩次數據還能夠發現,除了用戶名沒變,其餘的數據都會由於第二次訪問而進行更新。