天天看點

Spring Security Web 5.1.2 源碼解析 -- RememberMeAuthenticationFilter概述源代碼解析相關文章參考文章

概述

預設情況下,如果安全配置開啟了

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

,這時候請求最終會被拒絕通路目标頁面,原因是權限不足(雖然認證通過)。

如果你想觀察該過濾器的行為,可以這麼做:
  1. 在配置中開啟

    Remember Me

    機制,則此過濾器會被使用;
  2. 啟動應用,打開浏覽器,提供正确的使用者名密碼,選擇

    Remember Me

    選項,然後送出完成一次成功的登入;
  3. 關閉整個浏覽器;
  4. 重新打開剛剛關閉的浏覽器;
  5. 直接通路某個受

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