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

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

记住我基本原理

《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);
  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()
    ......

效果如下

《Spring Security源码分析七: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. 检查用户凭证

代码下载

从我的 github 中下载,https://github.com/longfeizheng/logback

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/dandandeshangni/article/details/79090952
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞