博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
title: Spring Security源码分析七:Spring Security 记住我
阅读量:7108 次
发布时间:2019-06-28

本文共 5664 字,大约阅读时间需要 18 分钟。

hot3.png

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

记住我基本原理

  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)
 下次自动登录
  1. 配置
http.......                .and()                .rememberMe()                .tokenRepository(persistentTokenRepository())//设置操作表的Repository                .tokenValiditySeconds(securityProperties.getRememberMeSeconds())//设置记住我的时间                .userDetailsService(userDetailsService)//设置userDetailsService                .and()	......

效果如下

源码分析

首次登录

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-mepublic 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. 检查用户凭证

代码下载

从我的 github 中下载,

转载于:https://my.oschina.net/merryyou/blog/1608801

你可能感兴趣的文章
Goland中Redis的set求并集的错误处理
查看>>
Timer
查看>>
ComboBox
查看>>
C++ sort()函数和C qsort()函数用法总结
查看>>
【图像处理】工业相机原理详述 (转载)
查看>>
【分布式】Zookeeper应用场景
查看>>
【堆】
查看>>
Asp.net基础概念整理(一) Web应用程序和网站的区别
查看>>
[02-02 ]Java数据库链接范列
查看>>
一些常用的Bootstrap模板资源站
查看>>
taro 填坑之路(二)taro 通过事件监听 实现组件间传值
查看>>
数组操作
查看>>
POJ 3613 Cow Relays
查看>>
20155222卢梓杰 课下测试04补做
查看>>
SQL注入
查看>>
怎么查看在centos中创建的用户组
查看>>
为什么说http协议是无状态协议
查看>>
[导入]Wap系统中Session信息保存问题解决方法
查看>>
动态输出javascript
查看>>
android_error
查看>>