概述
預設情況下,如果安全配置開啟了
Remember Me
機制,使用者在登入界面上會看到
Remember Me
選擇框,如果使用者選擇了該選擇框,會導緻生成一個名為
remember-me
,屬性
httpOnly
為
true
的
cookie
,其值是一個
RememberMe token
。
是一個
RememberMe token
編碼的字元串,解碼後格式為
Base64
,比如:
{使用者名}:{Token過期時間戳}:{Token簽名摘要}
admin:1545787408479:d0b0e7a53960e94b521bee3f02ba0bf5
而該過濾器在每次請求到達時會檢測
SecurityContext
屬性
Authentication
是否已經設定。如果沒有設定,會進入該過濾器的職責邏輯。它嘗試擷取名為
remember-me
的
cookie
,擷取到的話會認為這是一次
Remember Me
登入嘗試,從中分析出使用者名,
Token
過期時間戳,簽名摘要,針對使用者庫驗證這些資訊,認證通過的話,就會往
SecurityContext
裡面設定
Authentication
為一個針對請求中所指定使用者的
RememberMeAuthenticationToken
。
認證成功的話,也會向應用上下文釋出事件
InteractiveAuthenticationSuccessEvent
。
預設情況下不管認證成功還是失敗,請求都會被繼續執行。
不過也可以指定一個給目前過濾器,這樣當
AuthenticationSuccessHandler
登入認證成功時,處理委托給該
Remember Me
,而不再繼續原請求的處理。利用這種機制,可以為
AuthenticationSuccessHandler
登入認證成功指定特定的跳轉位址。
Remember Me
Remember Me
登入認證成功并不代表使用者一定可以通路到目标頁面,因為如果
Remember Me
登入認證成功對應使用者通路權限級别為
isRememberMe
,而目标頁面需要更高的通路權限級别
fullyAuthenticated
,這時候請求最終會被拒絕通路目标頁面,原因是權限不足(雖然認證通過)。
如果你想觀察該過濾器的行為,可以這麼做:
- 在配置中開啟
機制,則此過濾器會被使用;
Remember Me
- 啟動應用,打開浏覽器,提供正确的使用者名密碼,選擇
選項,然後送出完成一次成功的登入;
Remember Me
- 關閉整個浏覽器;
- 重新打開剛剛關閉的浏覽器;
- 直接通路某個受
rememberMe
通路級别保護的頁面,你會看到該過濾器的職責邏輯被執行,目标頁面可以通路。
注意 : 這裡如果通路某個受
通路級别保護的頁面,目标頁面則不能通路,浏覽器會被跳轉到登入頁面。
fullyAuthenticated
源代碼解析
package org.springframework.security.web.authentication.rememberme;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
public class RememberMeAuthenticationFilter extends GenericFilterBean implements
ApplicationEventPublisherAware {
// ~ Instance fields
// =======================================================================================
private ApplicationEventPublisher eventPublisher;
private AuthenticationSuccessHandler successHandler;
private AuthenticationManager authenticationManager;
private RememberMeServices rememberMeServices;
public RememberMeAuthenticationFilter(AuthenticationManager authenticationManager,
RememberMeServices rememberMeServices) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
this.authenticationManager = authenticationManager;
this.rememberMeServices = rememberMeServices;
}
// ~ Methods
// =====================================================================================
@Override
public void afterPropertiesSet() {
Assert.notNull(authenticationManager, "authenticationManager must be specified");
Assert.notNull(rememberMeServices, "rememberMeServices must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (SecurityContextHolder.getContext().getAuthentication() == null) {
// 如果SecurityContext中authentication為空則嘗試 remember me 自動認證,
// 預設情況下這裡rememberMeServices會是一個TokenBasedRememberMeServices,
// 其自動 remember me 認證過程如下:
// 1. 擷取 cookie remember-me 的值 , 一個base64 編碼串;
// 2. 從上面cookie之中解析出資訊:使用者名,token 過期時間,token 簽名
// 3. 檢查使用者是否存在,token是否過期,token 簽名是否一緻,
// 上面三個步驟都通過的情況下再檢查一下賬号是否鎖定,過期,禁用,密碼過期等現象,
// 如果上面這些驗證都通過,則認為認證成功,會構造一個
// RememberMeAuthenticationToken并傳回
// 上面的認證失敗會有rememberMeAuth==null
Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
response);
if (rememberMeAuth != null) {
// Attempt authenticaton via AuthenticationManager
try {
// 如果上面的 Remember Me 認證成功,則需要使用 authenticationManager
// 認證該rememberMeAuth
rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
// Store to SecurityContextHolder
// 将認證成功的rememberMeAuth放到SecurityContextHolder中的SecurityContext
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
// 成功時的其他操作:空方法,其實沒有其他在這裡做
onSuccessfulAuthentication(request, response, rememberMeAuth);
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder populated with remember-me token: '"
+ SecurityContextHolder.getContext().getAuthentication()
+ "'");
}
// Fire event
if (this.eventPublisher != null) {
// 釋出事件 InteractiveAuthenticationSuccessEvent 到應用上下文
eventPublisher
.publishEvent(new InteractiveAuthenticationSuccessEvent(
SecurityContextHolder.getContext()
.getAuthentication(), this.getClass()));
}
if (successHandler != null) {
// 如果指定了 successHandler ,則調用它,
// 預設情況下這個 successHandler 為 null
successHandler.onAuthenticationSuccess(request, response,
rememberMeAuth);
// 如果指定了 successHandler,在它調用之後,不再繼續 filter chain 的執行
return;
}
}
catch (AuthenticationException authenticationException) {
// Remember Me 認證失敗的情況
if (logger.isDebugEnabled()) {
logger.debug(
"SecurityContextHolder not populated with remember-me token, as "
+ "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
+ rememberMeAuth
+ "'; invalidating remember-me token",
authenticationException);
}
// rememberMeServices 的認證失敗處理
rememberMeServices.loginFail(request, response);
// 空方法,這裡什麼都不做
onUnsuccessfulAuthentication(request, response,
authenticationException);
}
}
// 繼續 filter chain 執行
chain.doFilter(request, response);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
// 繼續 filter chain 執行
chain.doFilter(request, response);
}
}
/**
* Called if a remember-me token is presented and successfully authenticated by the
* RememberMeServices autoLogin method and the
* AuthenticationManager.
*/
protected void onSuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, Authentication authResult) {
}
/**
* Called if the AuthenticationManager rejects the authentication object
* returned from the RememberMeServices autoLogin method. This method
* will not be called when no remember-me token is present in the request and
* autoLogin reurns null.
*/
protected void onUnsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed) {
}
public RememberMeServices getRememberMeServices() {
return rememberMeServices;
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
/**
* Allows control over the destination a remembered user is sent to when they are
* successfully authenticated. By default, the filter will just allow the current
* request to proceed, but if an AuthenticationSuccessHandler is set, it will
* be invoked and the doFilter() method will return immediately, thus allowing
* the application to redirect the user to a specific URL, regardless of whatthe
* original request was for.
* 預設情況下,Remember Me 登入認證成功時filter chain會繼續執行。但是也允許指定一個
* AuthenticationSuccessHandler , 這樣就可以控制 Remember Me 登入認證成功時的目标
* 跳轉位址(當然會忽略原始的請求目标)。
* @param successHandler the strategy to invoke immediately before returning from
* doFilter().
*/
public void setAuthenticationSuccessHandler(
AuthenticationSuccessHandler successHandler) {
Assert.notNull(successHandler, "successHandler cannot be null");
this.successHandler = successHandler;
}
}
相關文章
參考文章
- Spring Security Web 5.1.2 源碼解析 – 安全相關Filter清單