Spring Security系列之记住我(十二)

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

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

记住我基本原理

Spring Security系列之记住我(十二)
  1. 用户认证成功之后调用 RemeberMeService 根据用户名名生成 Token 由 TokenRepository 写入到数据库,同时也将 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);
    
    复制代码
  2. 登陆页面添加记住我复选款(name必须是remeber-me)
    <input name="remember-me" type="checkbox"> 下次自动登录
    复制代码
  3. 配置MerryyouSecurityConfig
    http.
    ......
                 .and()
                 .rememberMe()
                 .tokenRepository(persistentTokenRepository())//设置操作表的Repository
                 .tokenValiditySeconds(securityProperties.getRememberMeSeconds())//设置记住我的时间
                 .userDetailsService(userDetailsService)//设置userDetailsService
                 .and()
     ......
    复制代码

效果如下

Spring Security系列之记住我(十二)

源码分析

首次登录

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

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

查看所有标签

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

Web程序设计

Web程序设计

(美)塞巴斯塔 / 王春智、刘伟梅 / 清华大学出版社 / 2011-1 / 69.00元

《Web程序设计(第6版)》浓墨重彩地描述客户端和服务器端Web开发技术,深入分析Web站点构造和维护工具,并透彻讲解主流Web编程语言。《Web程序设计(第6版)》对上一版内容做了全面细致的修改,并融入了NetBeans 6.7、Visual Studio 8和ASP.NET Web服务等最新技术。《Web程序设计(第6版)》既可以作为高校教材,也可供专业Web编程人员参考使用。一起来看看 《Web程序设计》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具