Spring Security RememberMe 自动登录流程

Spring Security 是根据cookie来完成自动登录功能的,所以我将解析分为两部分:何时让请求带上cookie;如何去解析cookie。

1、何时添加cookie

还记得处理登录的过滤器嘛,没错,一切都是从哪里开始,AbstractAuthenticationProcessingFilter。我们先去扎到它的doFilter()方法,如下:

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
        
		try {
            // 1. 第一部分
			Authentication authenticationResult = attemptAuthentication(request, response);
			if (authenticationResult == null) {
				return;
			}
			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
			if (this.continueChainBeforeSuccessfulAuthentication) {
				chain.doFilter(request, response);
			}
            // 2. 第二部分
			successfulAuthentication(request, response, chain, authenticationResult);
		}
        // 3. 第三部分
		catch (InternalAuthenticationServiceException failed) {
			unsuccessfulAuthentication(request, response, failed);
		}
		catch (AuthenticationException ex) {
			unsuccessfulAuthentication(request, response, ex);
		}
	}

我们能很简单地将它所要实现的功能分为三部分:①:认证过程;②认证成功处理;③:认证失败、报错处理。

我们顺着第二部分走下去:

	protected void successfulAuthentication(HttpServletRequest request, 
                                            HttpServletResponse response, 
                                            FilterChain chain,
			Authentication authResult) throws IOException, ServletException { 
		SecurityContextHolder.getContext().setAuthentication(authResult);
		// 1. 第一部分
		this.rememberMeServices.loginSuccess(request, response, authResult);
        
		if (this.eventPublisher != null) {
			this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
		}
        // 2. 第二部分
		this.successHandler.onAuthenticationSuccess(request, response, authResult);
	}

我们又能将该方法所要实现的功能分为两部分:①:rememberServices;②:成功处理措施

我们从第一部分走,我们所要寻找的接口为RememberMeServices,默认实现为NullRememberMeServices。看了下接口,我们发现如果有使用remember功能,那使用的实现类为TokenBasedRememberMeServices

顺着往下找loginSuccess()方法:

	public final void loginSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication successfulAuthentication) {
		if (!rememberMeRequested(request, this.parameter)) {
			this.logger.debug("Remember-me login not requested.");
			return;
		}
        // 调用下面的onLoginSuccess()方法
		onLoginSuccess(request, response, successfulAuthentication);
	}


	public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication successfulAuthentication) {
        // 1. 获取用户名和密码
		String username = retrieveUserName(successfulAuthentication);
		String password = retrievePassword(successfulAuthentication);
		// 2. 用户、密码校验
		if (!StringUtils.hasLength(username)) {
			this.logger.debug("Unable to retrieve username");
			return;
		}
		if (!StringUtils.hasLength(password)) {
			UserDetails user = getUserDetailsService().loadUserByUsername(username);
			password = user.getPassword();
			if (!StringUtils.hasLength(password)) {
				this.logger.debug("Unable to obtain password for user: " + username);
				return;
			}
		}
        // 3. 生成cookie并设置到请求中
		int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
		long expiryTime = System.currentTimeMillis();
		// SEC-949
		expiryTime += 1000L * ((tokenLifetime < 0) ? TWO_WEEKS_S : tokenLifetime);
		String signatureValue = makeTokenSignature(expiryTime, username, password);
		setCookie(new String[] { username, Long.toString(expiryTime), signatureValue }, 
                  				tokenLifetime, request,response);
	}

我们又将上面方法的流程分为三步:

  • 获取用户名和密码
  • 对用户名校验是否为空;对密码校验是否被擦除,如果是,则重新获取
  • 用户名、密码、过期时间传入方法生成cookie并在请求上设置
  • cookie的组成有用户名、密码、过期时间、key组成,采用MD5加密方式,其中key为一个UUID字符串,每次程序重启都会改变,建议设置

登录流程的记住我功能就此结束,下面看看如何进行验证

2、如何去解析cookie,实现自动登录

Spring Security中也有一个过滤器来实现自动登录的功能,那就是RememberMeAuthenticationFilter,go,我们直接去看它的doIFilter()方法。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    	// 1. 查看有没有Authentication
		if (SecurityContextHolder.getContext().getAuthentication() != null) {
			chain.doFilter(request, response);
			return;
		}
    	// 2. 解析cookie
		Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
    	
		if (rememberMeAuth != null) {
			// Attempt authenticaton via AuthenticationManager
			try {
                // 3. 验证Authentication
				rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
				SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
                // 4. 成功后的操作
				onSuccessfulAuthentication(request, response, rememberMeAuth);
				if (this.eventPublisher != null) {
					this.eventPublisher.publishEvent(
                        new InteractiveAuthenticationSuccessEvent(
							SecurityContextHolder.getContext()
                            .getAuthentication(), this.getClass()));
				}
                
				if (this.successHandler != null) {
					this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
					return;
				}
			}
            // 5. 失败后的操作
			catch (AuthenticationException ex) {
				this.rememberMeServices.loginFail(request, response);
				onUnsuccessfulAuthentication(request, response, ex);
			}
		}
		chain.doFilter(request, response);
	}

我们可以将上面的方法实现分为5个部分:

  • 查看是否需要解析cookie
  • 解析cookie
  • 验证Authentication
  • 验证成功后的操作
  • 验证失败后的操作

我们就主要看下,cookie是怎么解析出来的

public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
    	// 1. 根据remember-me找到相应的cookie
		String rememberMeCookie = extractRememberMeCookie(request);
		if (rememberMeCookie == null) {
			return null;
		}
		if (rememberMeCookie.length() == 0) {
			cancelCookie(request, response);
			return null;
		}
    	
		try {
            // 2. 解码cookie
			String[] cookieTokens = decodeCookie(rememberMeCookie);
            // 3. 验证解码出来的信息
			UserDetails user = processAutoLoginCookie(cookieTokens, request, response);
			this.userDetailsChecker.check(user);
			return createSuccessfulAuthentication(request, user);
		}
		catch (CookieTheftException ex) {
			cancelCookie(request, response);
			throw ex;
		}
		catch (UsernameNotFoundException ex) {
			this.logger.debug("Remember-me login was valid but corresponding user not found.", ex);
		}
		catch (InvalidCookieException ex) {
			this.logger.debug("Invalid remember-me cookie: " + ex.getMessage());
		}
		catch (AccountStatusException ex) {
			this.logger.debug("Invalid UserDetails: " + ex.getMessage());
		}
		catch (RememberMeAuthenticationException ex) {
			this.logger.debug(ex.getMessage());
		}
		cancelCookie(request, response);
		return null;
	}

上面方法主要实现三个:

  • 获取cookie并验证它
  • 解码cookie
  • 拿解码出来的信息去核对,也就是调用loadUserByUsername()方法。

就这样,很简单!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值