Spring Security 記住我功能源碼解析

簡介

在這裏插入圖片描述

登錄流程

如上圖所示首先瀏覽器請求過來先經過UsernamePasswordAuthenticationFilter,認證成功之後AbstractAuthenticationProcessingFilter 的doFilter方法會調用successfulAuthentication(request, response, chain, authResult);該方法會首先會將認證成功的Authentication對象存入securityContext裏,創建出token寫入數據庫

AbstractAuthenticationProcessingFilter

前面過濾器認證成功後會到這個這個過濾器的successfulAuthentication方法中將認證成功的authResult存入securityContex中

protected void successfulAuthentication(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain, Authentication authResult)
      throws IOException, ServletException {

   if (logger.isDebugEnabled()) {
      logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
            + authResult);
   }

   SecurityContextHolder.getContext().setAuthentication(authResult);

   rememberMeServices.loginSuccess(request, response, authResult);

   // Fire event
   if (this.eventPublisher != null) {
      eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
            authResult, this.getClass()));
   }

   successHandler.onAuthenticationSuccess(request, response, authResult);
}

PersistentTokenBasedRememberMeServices

在這裏的rememberMeServices實現類爲PersistentTokenBasedRememberMeServices,其onLoginSuccess方法如下,該方法會創建出persistentToken,然後調用tokenRepository的createNewToken方法將該對象存入數據庫中。然後再將該token寫到cookies中

protected void onLoginSuccess(HttpServletRequest request,
      HttpServletResponse response, Authentication successfulAuthentication) {
   String username = successfulAuthentication.getName();

   logger.debug("Creating new persistent login for user " + username);

   PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
         username, generateSeriesData(), generateTokenData(), new Date());
   try {
      tokenRepository.createNewToken(persistentToken);
      addCookie(persistentToken, request, response);
   }
   catch (Exception e) {
      logger.error("Failed to save persistent token ", e);
   }
}

再次訪問

服務請求會到RememberMeAuthenticationFilter中,其會讀取Cookie中的Token,然後根據相關token從此數據庫獲取用戶名等,然後通過UserDetailService 的loadByUsername方法獲取Authentication對象,然後去認證,具體認證過程可以看Spring Security 用戶名密碼登錄源碼解析

RememberMeAuthenticationFilter

首先會進入到RememberMeAuthenticationFilter中,首先判斷securityContex中是否存在已經經過前面過濾器認證通過的Authentication對象。如果存在則調用其他過濾器。

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) {
      Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
            response);

      if (rememberMeAuth != null) {
         // Attempt authenticaton via AuthenticationManager
         try {
            rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

            // Store to SecurityContextHolder
            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) {
               eventPublisher
                     .publishEvent(new InteractiveAuthenticationSuccessEvent(
                           SecurityContextHolder.getContext()
                                 .getAuthentication(), this.getClass()));
            }

            if (successHandler != null) {
               successHandler.onAuthenticationSuccess(request, response,
                     rememberMeAuth);

               return;
            }

         }
         catch (AuthenticationException authenticationException) {
            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.loginFail(request, response);

            onUnsuccessfulAuthentication(request, response,
                  authenticationException);
         }
      }

      chain.doFilter(request, response);
   }
   else {
      if (logger.isDebugEnabled()) {
         logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
               + SecurityContextHolder.getContext().getAuthentication() + "'");
      }

      chain.doFilter(request, response);
   }
}

AbstractRememberMeServices

不存在則調用rememberMeServices的autoLogin方法,具體爲AbstractRememberMeServices
的autoLogin的方法

public final Authentication autoLogin(HttpServletRequest request,
      HttpServletResponse response) {
   String rememberMeCookie = extractRememberMeCookie(request);

   if (rememberMeCookie == null) {
      return null;
   }

   logger.debug("Remember-me cookie detected");

   if (rememberMeCookie.length() == 0) {
      logger.debug("Cookie was empty");
      cancelCookie(request, response);
      return null;
   }

   UserDetails user = null;

   try {
      String[] cookieTokens = decodeCookie(rememberMeCookie);
      user = processAutoLoginCookie(cookieTokens, request, response);
      userDetailsChecker.check(user);

      logger.debug("Remember-me cookie accepted");

      return createSuccessfulAuthentication(request, user);
   }
   catch (CookieTheftException cte) {
      cancelCookie(request, response);
      throw cte;
   }
   catch (UsernameNotFoundException noUser) {
      logger.debug("Remember-me login was valid but corresponding user not found.",
            noUser);
   }
   catch (InvalidCookieException invalidCookie) {
      logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());
   }
   catch (AccountStatusException statusInvalid) {
      logger.debug("Invalid UserDetails: " + statusInvalid.getMessage());
   }
   catch (RememberMeAuthenticationException e) {
      logger.debug(e.getMessage());
   }

   cancelCookie(request, response);
   return null;
}

PersistentTokenBasedRememberMeServices

其又將調用其子類PersistentTokenBasedRememberMeServices
的processAutoLoginCookie方法。根據cookies的presentedSeries值去數據庫查詢獲取token對象,然後進行相關校驗,具體見代碼
最後調用調用UserDetails的loadUserByUsername方法獲取Authentication對象,然後回到RememberMeAuthenticationFilter調用authenticationManager的authenticate進行認證,最後將認證通過的對象放入SecurityContex中並調用認證成功處理器。

	protected UserDetails processAutoLoginCookie(String[] cookieTokens,
			HttpServletRequest request, HttpServletResponse response) {

		if (cookieTokens.length != 2) {
			throw new InvalidCookieException("Cookie token did not contain " + 2
					+ " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
		}

		final String presentedSeries = cookieTokens[0];
		final String presentedToken = cookieTokens[1];

		PersistentRememberMeToken token = tokenRepository
				.getTokenForSeries(presentedSeries);

		if (token == null) {
			// No series match, so we can't authenticate using this cookie
			throw new RememberMeAuthenticationException(
					"No persistent token found for series id: " + presentedSeries);
		}

		// We have a match for this user/series combination
		if (!presentedToken.equals(token.getTokenValue())) {
			// Token doesn't match series value. Delete all logins for this user and throw
			// an exception to warn them.
			tokenRepository.removeUserTokens(token.getUsername());

			throw new CookieTheftException(
					messages.getMessage(
							"PersistentTokenBasedRememberMeServices.cookieStolen",
							"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
		}

		if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
				.currentTimeMillis()) {
			throw new RememberMeAuthenticationException("Remember-me login has expired");
		}

		// Token also matches, so login is valid. Update the token value, keeping the
		// *same* series number.
		if (logger.isDebugEnabled()) {
			logger.debug("Refreshing persistent login token for user '"
					+ token.getUsername() + "', series '" + token.getSeries() + "'");
		}

		PersistentRememberMeToken newToken = new PersistentRememberMeToken(
				token.getUsername(), token.getSeries(), generateTokenData(), new Date());

		try {
			tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
					newToken.getDate());
			addCookie(newToken, request, response);
		}
		catch (Exception e) {
			logger.error("Failed to update token: ", e);
			throw new RememberMeAuthenticationException(
					"Autologin failed due to data access problem");
		}

		return getUserDetailsService().loadUserByUsername(token.getUsername());
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章