Remember-Me 驗證

Remember-me 或持久登入驗證指的是網站能夠在工作階段之間記住主體的身份。這通常是透過傳送 Cookie 到瀏覽器來完成,Cookie 會在未來的工作階段中被偵測到,並導致自動登入發生。Spring Security 提供了這些操作發生的必要掛鉤,並且有兩個具體的 remember-me 實作。其中一個使用雜湊來保護基於 Cookie 的權杖的安全性,另一個則使用資料庫或其他持久儲存機制來儲存產生的權杖。

請注意,這兩個實作都需要 UserDetailsService。如果您使用的驗證提供者未使用 UserDetailsService(例如,LDAP 提供者),除非您的應用程式內容中也有 UserDetailsService bean,否則它將無法運作。

簡單的基於雜湊權杖方法

此方法使用雜湊來實現有用的 remember-me 策略。本質上,在成功互動式驗證後,會傳送一個 Cookie 到瀏覽器,該 Cookie 的組成如下

base64(username + ":" + expirationTime + ":" + algorithmName + ":"
algorithmHex(username + ":" + expirationTime + ":" password + ":" + key))

username:          As identifiable to the UserDetailsService
password:          That matches the one in the retrieved UserDetails
expirationTime:    The date and time when the remember-me token expires, expressed in milliseconds
key:               A private key to prevent modification of the remember-me token
algorithmName:     The algorithm used to generate and to verify the remember-me token signature

remember-me 權杖僅在指定的期間內有效,且僅在使用者名稱、密碼和金鑰未變更的情況下有效。值得注意的是,這存在潛在的安全性問題,因為捕獲的 remember-me 權杖可以從任何使用者代理程式使用,直到權杖過期為止。這與摘要式驗證的問題相同。如果主體意識到權杖已被捕獲,他們可以輕鬆變更其密碼,並立即讓所有已發出的 remember-me 權杖失效。如果需要更高的安全性,您應該使用下一節中描述的方法。或者,根本不應使用 remember-me 服務。

如果您熟悉關於命名空間組態章節中討論的主題,您可以透過新增 <remember-me> 元素來啟用 remember-me 驗證

<http>
...
<remember-me key="myAppKey"/>
</http>

UserDetailsService 通常會自動選取。如果您的應用程式內容中有多個,您需要使用 user-service-ref 屬性指定應使用哪個,其中值是您的 UserDetailsService bean 的名稱。

持久權杖方法

此方法基於文章 Improved Persistent Login Cookie Best Practice,並進行了一些小的修改 [1]。若要搭配命名空間組態使用此方法,您需要提供資料來源參考

<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>

資料庫應包含 persistent_logins 表格,使用以下 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)

Remember-Me 介面和實作

Remember-me 與 UsernamePasswordAuthenticationFilter 一起使用,並透過 AbstractAuthenticationProcessingFilter 超類別中的掛鉤實作。它也在 BasicAuthenticationFilter 中使用。掛鉤會在適當的時間調用具體的 RememberMeServices。以下清單顯示了介面

Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,
	Authentication successfulAuthentication);

請參閱 RememberMeServices 的 Javadoc 以獲得關於方法用途的更完整討論,儘管請注意,在此階段,AbstractAuthenticationProcessingFilter 僅調用 loginFail()loginSuccess() 方法。autoLogin() 方法由 RememberMeAuthenticationFilterSecurityContextHolder 不包含 Authentication 時調用。因此,此介面為底層 remember-me 實作提供了足夠的驗證相關事件通知,並在候選 Web 請求可能包含 Cookie 並希望被記住時委派給實作。此設計允許任意數量的 remember-me 實作策略。

我們稍早已經看到 Spring Security 提供了兩個實作。接下來我們將依序查看每個實作。

TokenBasedRememberMeServices

此實作支援 簡單的基於雜湊權杖方法 中描述的更簡單方法。TokenBasedRememberMeServices 產生一個 RememberMeAuthenticationToken,由 RememberMeAuthenticationProvider 處理。key 在此驗證提供者和 TokenBasedRememberMeServices 之間共享。此外,TokenBasedRememberMeServices 需要一個 UserDetailsService,它可以從中檢索使用者名稱和密碼以進行簽名比較,並產生 RememberMeAuthenticationToken 以包含正確的 GrantedAuthority 實例。TokenBasedRememberMeServices 也實作了 Spring Security 的 LogoutHandler 介面,以便它可以與 LogoutFilter 一起使用,以自動清除 Cookie。

預設情況下,此實作使用 SHA-256 演算法來編碼權杖簽名。為了驗證權杖簽名,將解析並使用從 algorithmName 檢索的演算法。如果沒有 algorithmName,將使用預設的匹配演算法,即 SHA-256。您可以為簽名編碼和簽名匹配指定不同的演算法,這允許使用者安全地升級到不同的編碼演算法,同時在沒有 algorithmName 的情況下仍然能夠驗證舊的演算法。若要執行此操作,您可以將自訂的 TokenBasedRememberMeServices 指定為 Bean,並在組態中使用它。

  • Java

  • XML

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
	http
			.authorizeHttpRequests((authorize) -> authorize
					.anyRequest().authenticated()
			)
			.rememberMe((remember) -> remember
				.rememberMeServices(rememberMeServices)
			);
	return http.build();
}

@Bean
RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
	RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
	TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
	rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
	return rememberMe;
}
<http>
  <remember-me services-ref="rememberMeServices"/>
</http>

<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
    <property name="userDetailsService" ref="myUserDetailsService"/>
    <property name="key" value="springRocks"/>
    <property name="matchingAlgorithm" value="MD5"/>
    <property name="encodingAlgorithm" value="SHA256"/>
</bean>

應用程式內容中需要以下 bean 才能啟用 remember-me 服務

  • Java

  • XML

@Bean
RememberMeAuthenticationFilter rememberMeFilter() {
    RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter();
    rememberMeFilter.setRememberMeServices(rememberMeServices());
    rememberMeFilter.setAuthenticationManager(theAuthenticationManager);
    return rememberMeFilter;
}

@Bean
TokenBasedRememberMeServices rememberMeServices() {
    TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices();
    rememberMeServices.setUserDetailsService(myUserDetailsService);
    rememberMeServices.setKey("springRocks");
    return rememberMeServices;
}

@Bean
RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
    RememberMeAuthenticationProvider rememberMeAuthenticationProvider = new RememberMeAuthenticationProvider();
    rememberMeAuthenticationProvider.setKey("springRocks");
    return rememberMeAuthenticationProvider;
}
<bean id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />
</bean>

<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
</bean>

<bean id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>
</bean>

請記住將您的 RememberMeServices 實作新增到您的 UsernamePasswordAuthenticationFilter.setRememberMeServices() 屬性,在您的 AuthenticationManager.setProviders() 清單中包含 RememberMeAuthenticationProvider,並將 RememberMeAuthenticationFilter 新增到您的 FilterChainProxy 中(通常緊接在您的 UsernamePasswordAuthenticationFilter 之後)。

PersistentTokenBasedRememberMeServices

您可以像使用 TokenBasedRememberMeServices 一樣使用此類別,但它還需要使用 PersistentTokenRepository 進行組態以儲存權杖。

  • InMemoryTokenRepositoryImpl 僅用於測試。

  • JdbcTokenRepositoryImpl 將權杖儲存在資料庫中。

請參閱 持久權杖方法 以取得資料庫結構描述。


1. 本質上,使用者名稱未包含在 Cookie 中,以防止不必要地暴露有效的登入名稱。本文的評論區有關於此的討論。