zoukankan      html  css  js  c++  java
  • springsecurity 源码解读 之 RememberMeAuthenticationFilter

    RememberMeAuthenticationFilter 的作用很简单,就是用于当session 过期后,系统自动通过读取cookie 让系统自动登录。

    我们来看看Springsecurity的过滤器链条。

    我们发现这个 RememberMeAuthenticationFilter  在 匿名构造器之前,这个是为什么呢?

    还是从源码来分析:

    if (SecurityContextHolder.getContext().getAuthentication() == null) {
                Authentication rememberMeAuth = rememberMeServices.autoLogin(request, response);
    
                if (rememberMeAuth != null) {

    代码中有这样的一行,当SecurityContext 中 Authentication 为空时,他就会调用 rememberMeServices 自动登录。

    因此刚刚的问题也就好解释了,因为如果RememberMeAuthenticationFilter  没有实现自动登录,那么他的Authentication 还是为空,

    这是可以创建一个匿名登录,如果先创建了匿名登录,那么这个 RememberMeAuthenticationFilter   的判断就不会为null,过滤器将失效。

    我们看看rememberMeServices 的autoLogin 登录

    我们贴出实现的代码:

    public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
            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 {
                String[] cookieTokens = decodeCookie(rememberMeCookie);
                user = processAutoLoginCookie(cookieTokens, request, response);
                userDetailsChecker.check(user);
    
                logger.debug("Remember-me cookie accepted");
    
                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;
        }
    extractRememberMeCookie这个方法判断 SPRING_SECURITY_REMEMBER_ME_COOKIE 这样的cookie,如果没有就直接返回了null。

    processAutoLoginCookie:这个是处理cookie 并从cookie加载用户。

    默认springsecurity 使用类 TokenBasedRememberMeServices 来解析 cookie。

    实现代码如下:
    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) + "'");
            }
    
            long tokenExpiryTime;
    
            try {
                tokenExpiryTime = new Long(cookieTokens[1]).longValue();
            }
            catch (NumberFormatException nfe) {
                throw new InvalidCookieException("Cookie token[1] did not contain a valid number (contained '" +
                        cookieTokens[1] + "')");
            }
    
            if (isTokenExpired(tokenExpiryTime)) {
                throw new InvalidCookieException("Cookie token[1] has expired (expired on '"
                        + new Date(tokenExpiryTime) + "'; current time is '" + new Date() + "')");
            }
    
            // Check the user exists.
            // Defer lookup until after expiry time checked, to possibly avoid expensive database call.
    
            UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
    
            // Check signature of token matches remaining details.
            // Must do this after user lookup, as we need the DAO-derived password.
            // If efficiency was a major issue, just add in a UserCache implementation,
            // but recall that this method is usually only called once per HttpSession - if the token is valid,
            // it will cause SecurityContextHolder population, whilst if invalid, will cause the cookie to be cancelled.
            String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),
                    userDetails.getPassword());
    
            if (!equals(expectedTokenSignature,cookieTokens[2])) {
                throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2]
                                                                                                        + "' but expected '" + expectedTokenSignature + "'");
            }
    
            return userDetails;
        }

      
     protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
            String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
            MessageDigest digest;
            try {
                digest = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("No MD5 algorithm available!");
            }
    
            return new String(Hex.encode(digest.digest(data.getBytes())));
        }

    这个代码就是加载用户,并验证cookie 是否有效。

    因此我们在写入cookie 时,产生的cookie 过程如下:

    String enPassword=EncryptUtil.hexToBase64(password);
            
            long tokenValiditySeconds = 1209600; // 14 days
            long tokenExpiryTime = System.currentTimeMillis() + (tokenValiditySeconds * 1000);
            String signatureValue = makeTokenSignature(tokenExpiryTime,username,enPassword);
            String tokenValue = username + ":" + tokenExpiryTime + ":" + signatureValue;
            String tokenValueBase64 = new String(Base64.encodeBase64(tokenValue.getBytes()));
                    
            CookieUtil.addCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 
                    tokenValueBase64,60 * 60 * 24 * 365, true, request, response);
     


  • 相关阅读:
    洛谷P2292 [HNOI2004]L语言
    洛谷P4052 [JSOI2007]文本生成器(AC自动机)
    洛谷P3193 [HNOI2008]GT考试(KMP,矩阵)
    创建目录命令
    ssh免密码登录机器(使用公钥和秘钥进行加密来实现)
    kafka工作原理介绍
    KafKa集群安装、配置
    Kafka的partions和replication-factor参数的理解
    linux之find命令详解
    将用户需求和新型技术输入,优质服务和价值体验输出。
  • 原文地址:https://www.cnblogs.com/yg_zhang/p/10659490.html
Copyright © 2011-2022 走看看