Spring Security源码分析七:Spring Security 记住我

栏目: Java · 发布时间: 8年前

内容简介:Spring Security源码分析七:Spring Security 记住我

有这样一个场景——有个用户初访并登录了你的网站,然而第二天他又来了,却必须再次登录。于是就有了“记住我”这样的功能来方便用户使用,然而有一件不言自明的事情,那就是这种认证状态的”旷日持久“早已超出了用户原本所需要的使用范围。这意味着,他们可以关闭浏览器,然后再关闭电脑,下周或者下个月,乃至更久以后再回来,只要这间隔时间不要太离谱,该网站总会知道谁是谁,并一如既往的为他们提供所有相同的功能和服务——与许久前他们离开的时候别无二致。

记住我基本原理

user-gold-cdn.xitu.io/2018/1/17/1…
  1. 用户认证成功之后调用 RemeberMeService 根据用户名名生成 TokenTokenRepository 写入到数据库,同时也将 Token 写入到浏览器的 Cookie
  2. 重启服务之后,用户再次登入系统会由 RememberMeAuthenticationFilter 拦截,从 Cookie 中读取 Token 信息,与 persistent_logins 表匹配判断是否使用记住我功能。最中由 UserDetailsService 查询用户信息

记住我实现

  1. 创建 persistent_logins
create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null);
  1. 登陆页面添加记住我复选款(name必须是remeber-me)
<input name="remember-me" type="checkbox"> 下次自动登录
  1. 配置 MerryyouSecurityConfig
http.
......
                .and()
                .rememberMe()
                .tokenRepository(persistentTokenRepository())//设置操作表的Repository
                .tokenValiditySeconds(securityProperties.getRememberMeSeconds())//设置记住我的时间
                .userDetailsService(userDetailsService)//设置userDetailsService
                .and()
	......

效果如下

user-gold-cdn.xitu.io/2018/1/17/1…

源码分析

首次登录

AbstractAuthenticationProcessingFilter#successfulAuthentication

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);
		}
		//# 1.将已认证过的Authentication放入到SecurityContext中
		SecurityContextHolder.getContext().setAuthentication(authResult);
		//# 2.登录成功调用rememberMeServices
		rememberMeServices.loginSuccess(request, response, authResult);

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

		successHandler.onAuthenticationSuccess(request, response, authResult);
	}
  1. 将已认证过的Authentication放入到SecurityContext中
  2. 登录成功调用rememberMeServices

AbstractRememberMeServices#loginSuccess

private String parameter = DEFAULT_PARAMETER;//remember-me

public final void loginSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication successfulAuthentication) {
		// #1.判断是否勾选记住我
		if (!rememberMeRequested(request, parameter)) {
			logger.debug("Remember-me login not requested.");
			return;
		}

		onLoginSuccess(request, response, successfulAuthentication);
	}
  1. 判断是否勾选记住我

PersistentTokenBasedRememberMeServices#onLoginSuccess

protected void onLoginSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication successfulAuthentication) {
		//#1.获取用户名
		String username = successfulAuthentication.getName();

		logger.debug("Creating new persistent login for user " + username);
		//#2.创建Token
		PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
				username, generateSeriesData(), generateTokenData(), new Date());
		try {
			//#3.存储都数据库
			tokenRepository.createNewToken(persistentToken);
			//#4.写入到浏览器的Cookie中
			addCookie(persistentToken, request, response);
		}
		catch (Exception e) {
			logger.error("Failed to save persistent token ", e);
		}
	}
  1. 获取用户名
  2. 创建Token
  3. 存储都数据库
  4. 写入到浏览器的Cookie中

二次登录Remember-me

RememberMeAuthenticationFilter#doFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		//#1.判断SecurityContext中没有Authentication
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			//#2.从Cookie查询用户信息返回RememberMeAuthenticationToken
			Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
					response);

			if (rememberMeAuth != null) {
				// Attempt authenticaton via AuthenticationManager
				try {
					//#3.如果不为空则由authenticationManager认证
					rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

					// Store to SecurityContextHolder
					SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

					onSuccessfulAuthentication(request, response, rememberMeAuth);
......
  1. 判断SecurityContext中没有Authentication
  2. 从Cookie查询用户信息返回RememberMeAuthenticationToken
  3. 如果不为空则由authenticationManager认证

AbstractRememberMeServices#autoLogin

public final Authentication autoLogin(HttpServletRequest request,
			HttpServletResponse response) {
			//#1.获取Cookie
		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 {
			//#2.解析Cookie
			String[] cookieTokens = decodeCookie(rememberMeCookie);
			//#3.获取用户凭证
			user = processAutoLoginCookie(cookieTokens, request, response);
			//#4.检查用户凭证
			userDetailsChecker.check(user);

			logger.debug("Remember-me cookie accepted");
			//#5.返回Authentication
			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;
	}
  1. 获取Cookie
  2. 解析Cookie
  3. 获取用户凭证
  4. 检查用户凭证

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Foundations of PEAR

Foundations of PEAR

Good, Nathan A./ Kent, Allan / Springer-Verlag New York Inc / 2006-11 / $ 50.84

PEAR, the PHP Extension and Application Repository, is a bountiful resource for any PHP developer. Within its confines lie the tools that you need to do your job more quickly and efficiently. You need......一起来看看 《Foundations of PEAR》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具