springsecurity 源码解读 之 RememberMeAuthenticationFilter
Posted 自由港
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);
以上是关于springsecurity 源码解读 之 RememberMeAuthenticationFilter的主要内容,如果未能解决你的问题,请参考以下文章
源码解析SpringSecurity-UsernamePasswordAuthenticationFilter
ElasticSearchEs 源码之 AsyncSearchMaintenanceService 源码解读
ElasticSearchEs 源码之 CacheService 源码解读
ElasticSearchEs 源码之 CcrRestoreSourceService 源码解读