记住我大致思路
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);
}
}