spring security 记住我功能的实现原理

记住我大致思路

spring security 给我们提供了两种实现记住我的机制:

1  创建一个cookie,将用户名和密码等相关信息编码后放入,带下一个session时进行用户名密码的读取,并同数据库中的username,password进行匹配。

2 基于上面的实现方式有一个缺点,就是用户敏感信息暴露,为了解决这个我们通过一张凭证,在初次登陆后,我们生成一张凭证,cookie中存一份,数据库中存一份,下次session是直接比对凭证就ok了。

总体架构流程

记住我功能的实现通过该接口定义

public interface RememberMeServices {
    Authentication autoLogin(HttpServletRequest var1, HttpServletResponse var2);

    void loginFail(HttpServletRequest var1, HttpServletResponse var2);

    void loginSuccess(HttpServletRequest var1, HttpServletResponse var2, Authentication var3);
}

当用户首次勾选记住我然后登陆的话,会在UsernamePasswordAuthenticationFilter 中认证成功后,调用该接口方法

 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

        SecurityContextHolder.getContext().setAuthentication(authResult);
        //使用记住我
        this.rememberMeServices.loginSuccess(request, response, authResult);
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
//通过判断请求中是否有勾选记住我的选项记性记住我的处理工作

        if (!this.rememberMeRequested(request, this.parameter)) {
            this.logger.debug("Remember-me login not requested.");
        } else {
            this.onLoginSuccess(request, response, successfulAuthentication);
        }
    }

下面我们看一下PersistentTokenBasedRememberMeServices 里面的onLoginSuccess的实现

  protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
        String username = successfulAuthentication.getName();
        this.logger.debug("Creating new persistent login for user " + username);
//创建token
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());

        try {
               //记性token的持久化
            this.tokenRepository.createNewToken(persistentToken);
               //添加cookie
            this.addCookie(persistentToken, request, response);
        } catch (Exception var7) {
            this.logger.error("Failed to save persistent token ", var7);
        }

    }

当下次session会话时 spring security context中没有 Authentication实例的话,RememberMeAuthenticationFilter过滤器就会执行

 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            //通过该功能获取记住的用户,及通过userDetailsService获取用户的详细信息
            Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
            if (rememberMeAuth != null) {
                try {
                    //通过认证管理器进行验证
                    rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
                    //认证成功放入安全上下文中
                    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
                    this.onSuccessfulAuthentication(request, response, rememberMeAuth);
                  .....

    }

我们在去看看 PersistentTokenBasedRememberMeServices 中的this.rememberMeServices.autoLogin(request, response);的实现

public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
        //获取cookie
        String rememberMeCookie = this.extractRememberMeCookie(request);
        if (rememberMeCookie == null) {
            return null;
        } else {
            this.logger.debug("Remember-me cookie detected");
            //进行相关逻辑的判断
            if (rememberMeCookie.length() == 0) {
                this.logger.debug("Cookie was empty");
                this.cancelCookie(request, response);
                return null;
            } else {
                UserDetails user = null;

                try {
                    String[] cookieTokens = this.decodeCookie(rememberMeCookie);
                    //通过token 获取用户的详细信息  在这里面调用了userDetailsService
/*
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
                   ...
                return this.getUserDetailsService().loadUserByUsername(token.getUsername());
            }
        }
*/
                    user = this.processAutoLoginCookie(cookieTokens, request, response);
                    this.userDetailsChecker.check(user);
                    this.logger.debug("Remember-me cookie accepted");
                    //完成处理后返回用户名称
                    return this.createSuccessfulAuthentication(request, user);
                } catch (CookieTheftException var6) {
                    this.cancelCookie(request, response);
                    throw var6;
                } catch (UsernameNotFoundException var7) {
                    this.logger.debug("Remember-me login was valid but corresponding user not found.", var7);
                } catch (InvalidCookieException var8) {
                    this.logger.debug("Invalid remember-me cookie: " + var8.getMessage());
                } catch (AccountStatusException var9) {
                    this.logger.debug("Invalid UserDetails: " + var9.getMessage());
                } catch (RememberMeAuthenticationException var10) {
                    this.logger.debug(var10.getMessage());
                }

                this.cancelCookie(request, response);
                return null;
            }
        }
    }

那么众多的额provider中是哪个provider提供了对rememberme得认证支持呢?那就是

 

RememberMeAuthenticationProvider  但是这一步可以忽略不计,因为没有做什么逻辑,只是原封不动的返回去

 

当我们注销的时候经过LogoutFilter
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (this.requiresLogout(request, response)) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
           
            //此处有一个handler,因为AbstractRememberMeServices实现了该接口所以会调用
            this.handler.logout(request, response, auth);
            this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
        } else {
            chain.doFilter(request, response);
        }
    }




/AbstractRememberMeServices
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        //进行session的清除
        this.cancelCookie(request, response);
    }

RememberMeServices实现机制

public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
        //获取cookie中的数据
        String rememberMeCookie = this.extractRememberMeCookie(request);
        if (rememberMeCookie == null) {
            return null;
        } else {
            
            if (rememberMeCookie.length() == 0) {
              
            } else {
                UserDetails user = null;
                try {
                    //进行数据的解码
                    String[] cookieTokens = this.decodeCookie(rememberMeCookie);
                    //模板设计模式交给子类实现,主要作用是通过 数据获取用户信息,失败抛异常
                    user = this.processAutoLoginCookie(cookieTokens, request, response);
                    this.userDetailsChecker.check(user);
                   
                    return this.createSuccessfulAuthentication(request, user);
                } catch (CookieTheftException var6) {
                   
                }
                this.cancelCookie(request, response);
                return null;
            }
        }
    }
//TokenBasedRememberMeServices 的实现方法如下
 protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
        if (cookieTokens.length != 3) {
            throw new InvalidCookieException("Cookie token did not contain 3 tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
        } else {
            long tokenExpiryTime;
            try {
                tokenExpiryTime = new Long(cookieTokens[1]);
            } catch (NumberFormatException var8) {
                throw new InvalidCookieException("Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1] + "')");
            }

            if (this.isTokenExpired(tokenExpiryTime)) {
                throw new InvalidCookieException("Cookie token[1] has expired (expired on '" + new Date(tokenExpiryTime) + "'; current time is '" + new Date() + "')");
            } else {
                //没什么好说的 就是通过cookie中的数据读取用户信息
                UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(cookieTokens[0]);
                String expectedTokenSignature = this.makeTokenSignature(tokenExpiryTime, userDetails.getUsername(), userDetails.getPassword());
                if (!equals(expectedTokenSignature, cookieTokens[2])) {
                    throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2] + "' but expected '" + expectedTokenSignature + "'");
                } else {
                    return userDetails;
                }
            }
        }
    }
//PersistentTokenBasedRememberMeServices中

protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
        if (cookieTokens.length != 2) {
            throw new InvalidCookieException("Cookie token did not contain 2 tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
        } else {
            String presentedSeries = cookieTokens[0];
            String presentedToken = cookieTokens[1];

            //读取数据库中的凭证
            PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
            if (token == null) {
                throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
               //判断cookie中的凭证和数据库中的凭证是否一致
            } else if (!presentedToken.equals(token.getTokenValue())) {
                this.tokenRepository.removeUserTokens(token.getUsername());
                throw new CookieTheftException(this.messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
            } else if (token.getDate().getTime() + (long)this.getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
                throw new RememberMeAuthenticationException("Remember-me login has expired");
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'");
                }

                PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), this.generateTokenData(), new Date());

                try {
                    this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());
                    this.addCookie(newToken, request, response);
                } catch (Exception var9) {
                    this.logger.error("Failed to update token: ", var9);
                    throw new RememberMeAuthenticationException("Autologin failed due to data access problem");
                }
                //以上没有问题就载入用户详细信息
                return this.getUserDetailsService().loadUserByUsername(token.getUsername());
            }
        }
    }

关于其他两个方法的试下机制类似。请读者自行查看,但这里有必要说一下login Success 方法中的一个判断,用来是否开启记住我功能

public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
        //会通过查看request中是否有变量名为remember-me 的属性来执行
        if (!this.rememberMeRequested(request, this.parameter)) {
            this.logger.debug("Remember-me login not requested.");
        } else {
            this.onLoginSuccess(request, response, successfulAuthentication);
        }
    }

继承关系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值