1. 程式人生 > >springsecurity 源碼解讀 之 RememberMeAuthenticationFilter

springsecurity 源碼解讀 之 RememberMeAuthenticationFilter

orm ren eth http ESS sin per 就是 res

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);

 


springsecurity 源碼解讀 之 RememberMeAuthenticationFilter