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());
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值