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

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

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

记住我基本原理

Spring Security系列之记住我(十二)
  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);
    
    复制代码
  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. 检查用户凭证

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

查看所有标签

猜你喜欢:

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

The Web Designer's Idea Book, Vol. 2

The Web Designer's Idea Book, Vol. 2

Patrick McNeil / How / 2010-9-19 / USD 30.00

Web Design Inspiration at a Glance Volume 2 of The Web Designer's Idea Book includes more than 650 new websites arranged thematically, so you can easily find inspiration for your work. Auth......一起来看看 《The Web Designer's Idea Book, Vol. 2》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换