AbstractRememberMeServices有兩個(gè)實(shí)現(xiàn)類
實(shí)現(xiàn)類 | 服務(wù)端存儲(chǔ)token | 校驗(yàn)后更新token有效期 | cookie內(nèi)容 |
---|---|---|---|
TokenBasedRememberMeServices | 否 | 否 | username, expireTime,token |
PersistentRememberMeToken | 是 | 是 | series详恼,tokenValue |
TokenBasedRememberMeServices
cookie經(jīng)Base64解碼后属瓣,獲得字符串?dāng)?shù)組
內(nèi)容分別為username, expireTime洗出,token
這個(gè)token是按username:tokenExpiryTime:password:key拼接后進(jìn)行MD5摘要得到
其中key是RememberMeConfigurer初始化通過UUID隨機(jī)生成
/**
* Calculates the digital signature to be put in the cookie. Default value is MD5
* ("username:tokenExpiryTime:password:key")
*/
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())));
}
token校驗(yàn)關(guān)鍵點(diǎn):會(huì)再次使用username:tokenExpiryTime:password:key生成token哲银,并與傳入的token作比對(duì)
此處的password來自u(píng)serDetails徙缴,確保了cookie內(nèi)容的安全性帅涂,并可知若用戶更改了密碼劈狐,該token亦會(huì)失效
UserDetails userDetails = getUserDetailsService().loadUserByUsername(
cookieTokens[0]);
Assert.notNull(userDetails, () -> "UserDetailsService " + getUserDetailsService()
+ " returned null for username " + cookieTokens[0] + ". "
+ "This is an interface contract violation");
// 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 + "'");
}
PersistentTokenBasedRememberMeServices
cookie經(jīng)Base64解碼后,獲得字符串?dāng)?shù)組
內(nèi)容分別為series,tokenValue
series,tokenValue均為隨機(jī)生成的Base64字符串陵叽,相當(dāng)于隨機(jī)生成一份賬密
protected String generateSeriesData() {
byte[] newSeries = new byte[seriesLength];
random.nextBytes(newSeries);
return new String(Base64.getEncoder().encode(newSeries));
}
protected String generateTokenData() {
byte[] newToken = new byte[tokenLength];
random.nextBytes(newToken);
return new String(Base64.getEncoder().encode(newToken));
}
其中series作為key在首次生成后即穩(wěn)定不變狞尔,tokenValue在每次校驗(yàn)后會(huì)更新,并與series一同寫回cookie
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), token.getSeries(), generateTokenData(), new Date());
try {
tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
newToken.getDate());
addCookie(newToken, request, response);
}
catch (Exception e) {
logger.error("Failed to update token: ", e);
throw new RememberMeAuthenticationException(
"Autologin failed due to data access problem");
}
series作為key咨跌,將整個(gè)PersistentRememberMeToken存儲(chǔ)在tokenRepository
PersistentRememberMeToken
包含username,series,tokenValue,date
其中date含義為創(chuàng)建時(shí)間沪么,用于判斷有效期
自動(dòng)登錄時(shí)會(huì)刷新tokenValue與date