【Spring Security】7、RememberMe配置

1、概述

RememberMe 是指用戶在網站上可以在 Session 之間記住登陸用戶的身份的憑證,通俗的來講就是用戶登錄成功認證一次以後在制定的必定時間內能夠不用再輸入用戶名和密碼進行自動登陸。這個過程當中經過服務端發送一個 cookie 給客戶端瀏覽器保存,下次瀏覽器再訪問服務端時服務端可以自動檢測客戶端的 cookie,根據 cookie 值觸發自動登陸操做。
 
Spring Security中的 Remember-Me 功能一般有兩種實現方式。
  • 一種是簡單的使用加密來保證基於 cookie 的 token 的安全
  • 另外一種是經過數據庫或其它持久化存儲機制來保存生成的 token
兩種方式的區別:
  • 第一中方式不安全,就是說在用戶獲取到實現記住我功能的 token 後,任何用戶均可以在該 token 過時以前經過該 token 進行自動登陸。若是用戶發現本身的 token 被盜用了,那麼他能夠經過改變本身的登陸密碼來當即使其全部的記住我 token 失效
  • 若是但願咱們的應用可以更安全,那就使用第二種方式。第二種方式也是詳細要講解的。

基本原理html

 

 

2、基於簡單加密的方式

首先須要改動login.jsp,把記住個人checkbox的name修改成自定義的名稱
java

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>自定義登錄頁面</title>
    </head>
    <body>
        <div class="error ${param.error == true ? '' : 'hide'}">  
                登錄失敗<br>  
            ${sessionScope['SPRING_SECURITY_LAST_EXCEPTION'].message}  
        </div>  
        <form method="post" action="${pageContext.request.contextPath}/j_spring_security_check" style="width:260px; text-align: center">  
            <fieldset>  
                <legend>登錄</legend>  
                用戶: <input type="text" name="j_username" style="width: 150px;" value="${sessionScope['SPRING_SECURITY_LAST_USERNAME']}" />
                <br/>  
                   密碼: <input type="password" name="j_password" style="width: 150px;" />
                  <br/>  
                <input type="checkbox" name="remember-me" />記住我<br/>  
                <input type="submit" value="登錄" />
                <input type="reset" value="重置" />
            </fieldset>  
       </form>  
    </body>
</html>

須要特別注意的是,這兩種方式在配置的時候都要提供一個UserDetailsService,這個東西其實就是以前配置的jdbc-user-service標籤的一個實現類,配置代碼以下:mysql

<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>

說明:web

  • dataSource就是鏈接數據庫的數據源;
  • usersByUsernameQuery就是配置jdbc-user-service時候的users-by-username-query,這個是根據用戶名來查詢用戶的sql語句;
  • 同理authoritiesByUsernameQuery就是對應的authorities-by-username-query,這個用來根據用戶名查詢對應的權限。
下面來配置rememberMe的過濾器:
  <!-- 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>

這個過濾器僅僅這樣配置是不會起做用的,還要把它加入的SecurityFilterChain中去,用<custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>便可,另外這個過濾器要提供一個rememberMeServices和一個用戶認證的authenticationManager,後面這個其實就是authentication-manager所配置的東西,而前面這個須要另外配置,配置方式以下:spring

   <!-- RememberMeServices 的實現 -->
    <beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
        <beans:property name="userDetailsService" ref="userDetailsService" />
        <beans:property name="key" value="sunny" />
        <!-- 指定 request 中包含的用戶是否選擇了記住個人蔘數名 -->
        <beans:property name="parameter" value="remember-me" />
        <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>
  • 這個配置中的userDetailsService就是上面配置的,直接引用上面的便可;
  • key就是token中的key,這個key能夠用來方式token被修改,另外這個key要和後面配置的rememberMeAuthenticationProvider的key要同樣;
  • parameter就是登錄界面的點擊記住密碼的checkboxname值,這個必定要一致,要否則沒有效果的。
  • 初次以外還要配置一個用戶記住密碼作認證的authenticationManager
  <!-- 記住密碼 ,key 值需與對應的 RememberMeServices 保持一致 -->
    <beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
        <beans:property name="key" value="sunny" />
    </beans:bean>

同時還要將其添加到authentication-manager標籤中去sql

   <!--認證管理-->
    <authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="userDetailsService"></authentication-provider>
        <!-- 記住密碼 -->
        <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider>
    </authentication-manager>

最後面將rememberMeServices添加到myUsernamePasswordAuthenticationFilter中去。數據庫

  <!-- 自定義登陸過濾器 -->
    <beans:bean id="myUsernamePasswordAuthenticationFilter" class="com.sunny.auth.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>

 

最終的spring-security.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/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="/page/login.jsp" security="none"></http>
    <http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <!-- <form-login login-page="/page/login.jsp" default-target-url="/page/admin.jsp" authentication-failure-url="/page/login.jsp?error=true" /> -->
        <logout invalidate-session="true" logout-success-url="/page/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="/page/login.jsp"/>
    </beans:bean>
    <!-- 自定義登陸過濾器 -->
    <beans:bean id="myUsernamePasswordAuthenticationFilter" class="com.sunny.auth.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="defaultTargetUrl" value="/page/admin.jsp" />
    </beans:bean>
     <!-- 登陸失敗 -->
    <beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/page/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>
    
    <!-- 受權管理器 -->
    <beans:bean id="accessDecisionManager" class="com.sunny.auth.MyAccessDecisionManager">
    </beans:bean>
    
     <!--自定義的切入點-->
    <beans:bean id="securityMetadataSource" class="com.sunny.auth.MyFilterInvocationSecurityMetadataSource">
        <beans:property name="builder" ref="builder"/>
    </beans:bean>
    
    <beans:bean id="builder" class="com.sunny.auth.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>
    
     <!--認證管理-->
    <authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="userDetailsService"></authentication-provider>
        <!-- 記住密碼 -->
        <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider>
    </authentication-manager>
    
    
    <!-- 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>
    <!-- RememberMeServices 的實現 -->
    <beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
        <beans:property name="userDetailsService" ref="userDetailsService" />
        <beans:property name="key" value="sunny" />
        <!-- 指定 request 中包含的用戶是否選擇了記住個人蔘數名 -->
        <beans:property name="parameter" value="remember-me" />
        <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>
    
    <!-- 記住密碼 ,key 值需與對應的 RememberMeServices 保持一致 -->
    <beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
        <beans:property name="key" value="sunny" />
    </beans:bean>
    
    <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>
    
</beans:beans>

spring-dataSource.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/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">
    <!-- 數據源 -->
    <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/springsecurity?useUnicode=true&amp;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:beans>

 

3、基於持久化的方式配置

在將這配置以前先對上面rememberService中的實現類TokenBasedRememberMeServices進行簡單的講解,該類主要是基於簡單加密 token 的一個實現類。安全

  • TokenBasedRememberMeServices 會在用戶選擇了記住我成功登陸後,生成一個包含 token 信息的 cookie 發送到客戶端;
  • 若是用戶登陸失敗則會刪除客戶端保存的實現 Remember-Me 的 cookie。
  • 須要自動登陸時,它會判斷 cookie 中所包含的關於 Remember-Me 的信息是否與系統一致,一致則返回一個 RememberMeAuthenticationToken 供 RememberMeAuthenticationProvider 處理,不一致則會刪除客戶端的 Remember-Me cookie。
  • TokenBasedRememberMeServices 還實現了 Spring Security 的 LogoutHandler 接口,因此它能夠在用戶退出登陸時當即清除 Remember-Me cookie。

而基礎持久化方式配置的實質就是這個類不一樣,基於持久化方式配置的所用的實現類爲:PersistentTokenBasedRememberMeServices,一看名字就知道其做用,就是將token進行持久化保存起來,要保存數據相應的就是爲其制定保存的地方,這個保存的地方就是用PersistentTokenRepository來指定的,Spring Security 對此有兩種實現,InMemoryTokenRepositoryImpl 和 JdbcTokenRepositoryImpl。前者是將 token 存放在內存中的,一般用於測試,然後者是將 token 存放在數據庫中。PersistentTokenBasedRememberMeServices 默認使用的是前者,咱們能夠經過其 tokenRepository 屬性來指定使用的 PersistentTokenRepository。這例子用JdbcTokenRepositoryImpl來進行持久化保存,顯然要往數據庫保存數據,確定要有一張表,這個表security也有提供而且是固定的,sql語句爲:cookie

create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)

在數據庫中建立該表便可。

建立後的表爲:

 

4、兩種配置的效果

沒勾選【記住我】時候,登錄後再退出,再訪問/page/admin.jsp的時候就要求從新登錄。同時數據庫也不會新增數據。

當勾選【記住我】在登錄後,就算退出登錄後,再訪問/page/admin.jsp也能夠不用直接訪問,同時,數據庫也會將登錄信息保存起來,比較兩次數據還能夠發現,除了用戶名沒變,其餘的數據都會由於第二次訪問而進行更新

相關文章
相關標籤/搜索