当前位置:网站首页>Security RememberMe原理分析
Security RememberMe原理分析
2022-07-02 09:55:00 【InfoQ】
Security RememberMe原理分析
.rememberMe()
.key("xiepanapn")
@Override
public void init(H http) throws Exception {
validateInput();
String key = getKey();
RememberMeServices rememberMeServices = getRememberMeServices(http, key);
http.setSharedObject(RememberMeServices.class, rememberMeServices);
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if (logoutConfigurer != null && this.logoutHandler != null) {
logoutConfigurer.addLogoutHandler(this.logoutHandler);
}
RememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider(
key);
authenticationProvider = postProcess(authenticationProvider);
http.authenticationProvider(authenticationProvider);
initDefaultLoginFilter(http);
}
private AbstractRememberMeServices createRememberMeServices(H http, String key) {
return this.tokenRepository == null
? createTokenBasedRememberMeServices(http, key)
: createPersistentRememberMeServices(http, key);
}
@Override
public void configure(H http) {
RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter(
http.getSharedObject(AuthenticationManager.class),
this.rememberMeServices);
if (this.authenticationSuccessHandler != null) {
rememberMeFilter
.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
}
rememberMeFilter = postProcess(rememberMeFilter);
http.addFilter(rememberMeFilter);
}
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) {
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);
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
}
if (this.successHandler != null) {
this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
return;
}
} catch (AuthenticationException var8) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);
}
this.rememberMeServices.loginFail(request, response);
this.onUnsuccessfulAuthentication(request, response, var8);
}
}
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(request, response);
}
}
- 请求到达时,先判断SecurityContextHolder中是否有值,没有表示用户尚未登录,此时调用autoLogin进行自动登录。
- 自动登录成功后返回rememberMeAuth,不为null时表示自动登录成功,此时调用authenticate方法对key进行校验,将登录成功的用户信息保存到SecurityContextHolder中,然后调用登录成功回调,发布登录成功事件。
- 如果自动登录失败,调用rememberMeServices.loginFail方法处理登录失败逻辑
AbstractRememberMeServices
public interface RememberMeServices {
Authentication autoLogin(HttpServletRequest var1, HttpServletResponse var2);
void loginFail(HttpServletRequest var1, HttpServletResponse var2);
void loginSuccess(HttpServletRequest var1, HttpServletResponse var2, Authentication var3);
}
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
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);
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;
}
}
}
- 调用extractRememberMeCookie方法从当前请求中提取出需要的Cookie信息。rememberMeCookie为null返回null,长度为0取消cookie,将remember-me的值设置为null
- 调用decodeCookie方法对获取的令牌进行解析,解析的结果是第一部分是当前登录的用户名,第二部分是时间戳,第三部分是签名,提取出来组成一个数组。
- 调用processAutoLoginCookie方法来对Cookie进行验证,验证通过返回用户信息,由子类实现
- 最后调用createSuccessfulAuthentication创建登录成功的用户对象,类型为RememberMeAuthenticationToken ,与用户名密码登录的用户对象不同UsernamePasswordAuthenticationToken
ublic 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);
}
}
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (this.alwaysRemember) {
return true;
} else {
String paramValue = request.getParameter(parameter);
if (paramValue != null && (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"))) {
return true;
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')");
}
return false;
}
}
}
protected void setCookie(String[] tokens, int maxAge, HttpServletRequest request, HttpServletResponse response) {
String cookieValue = this.encodeCookie(tokens);
Cookie cookie = new Cookie(this.cookieName, cookieValue);
cookie.setMaxAge(maxAge);
cookie.setPath(this.getCookiePath(request));
if (this.cookieDomain != null) {
cookie.setDomain(this.cookieDomain);
}
if (maxAge < 1) {
cookie.setVersion(1);
}
if (this.useSecureCookie == null) {
cookie.setSecure(request.isSecure());
} else {
cookie.setSecure(this.useSecureCookie);
}
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
protected String encodeCookie(String[] cookieTokens) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < cookieTokens.length; ++i) {
try {
sb.append(URLEncoder.encode(cookieTokens[i], StandardCharsets.UTF_8.toString()));
} catch (UnsupportedEncodingException var5) {
this.logger.error(var5.getMessage(), var5);
}
if (i < cookieTokens.length - 1) {
sb.append(":");
}
}
String value = sb.toString();
sb = new StringBuilder(new String(Base64.getEncoder().encode(value.getBytes())));
while(sb.charAt(sb.length() - 1) == '=') {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
TokenBasedRememberMeServices
processAutoLoginCookie
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 {
UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(cookieTokens[0]);
Assert.notNull(userDetails, () -> {
return "UserDetailsService " + this.getUserDetailsService() + " returned null for username " + cookieTokens[0] + ". This is an interface contract violation";
});
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;
}
}
}
}
- 判断cookieTokens长度是否为3 不是3格式不对 直接抛出异常
- 从cookieTokens数组中提取第一项,判断是否过期
- 获取第0项,得到用户名查询当前用户信息
- 调用makeTokenSignature生成签名,签名生成方式:username + ":" + tokenExpiryTime + ":" + password + ":" + this.getKey()组成字符串进行MD5加密
- 判断第4步生成的签名与传进来的签名是否相同
protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
String data = username + ":" + tokenExpiryTime + ":" + password + ":" + this.getKey();
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException var8) {
throw new IllegalStateException("No MD5 algorithm available!");
}
return new String(Hex.encode(digest.digest(data.getBytes())));
}
onLoginSuccess
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username = this.retrieveUserName(successfulAuthentication);
String password = this.retrievePassword(successfulAuthentication);
if (!StringUtils.hasLength(username)) {
this.logger.debug("Unable to retrieve username");
} else {
if (!StringUtils.hasLength(password)) {
UserDetails user = this.getUserDetailsService().loadUserByUsername(username);
password = user.getPassword();
if (!StringUtils.hasLength(password)) {
this.logger.debug("Unable to obtain password for user: " + username);
return;
}
}
int tokenLifetime = this.calculateLoginLifetime(request, successfulAuthentication);
long expiryTime = System.currentTimeMillis();
expiryTime += 1000L * (long)(tokenLifetime < 0 ? 1209600 : tokenLifetime);
String signatureValue = this.makeTokenSignature(expiryTime, username, password);
this.setCookie(new String[]{username, Long.toString(expiryTime), signatureValue}, tokenLifetime, request, response);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime) + "'");
}
}
}
- 获取用户名密码信息 密码没有从数据库重新加载密码
- 计算出令牌的过期时间,令牌默认有效期是14天
- 根据令牌过期时间,用户名密码计算签名
- 调用setCookie方法设置Cookie,分别传入用户名过期时间签名,在setCookie中数组转字符串并进行Base64编码
PersistentTokenBasedRememberMeServices
processAutoLoginCookie
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);
} 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());
}
}
}
- cookieTokens数组长度为2 第0项为series 第1项为token
- 提取出两项数据根据series查询数据库,token不相同说明自动登录令牌已泄露,此时移除所有自动登录记录,抛出异常
- 根据数据库中查询出来的结果判断是否过期
- 生成新的PersistentRememberMeToken 用户名和series不变,更改token和当前时间,修改数据库
- 调用addCookie添加Cookie,addCookie方法中调用setCookie设置
- 感觉用户名查询用户对象并返回
onLoginSuccess
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username = successfulAuthentication.getName();
this.logger.debug("Creating new persistent login for user " + username);
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
try {
this.tokenRepository.createNewToken(persistentToken);
this.addCookie(persistentToken, request, response);
} catch (Exception var7) {
this.logger.error("Failed to save persistent token ", var7);
}
}
边栏推荐
- Jerry's watch ringtone audition [article]
- Get started REPORT | today, talk about the microservice architecture currently used by Tencent
- linux下清理系统缓存并释放内存
- Unity SKFramework框架(十六)、Package Manager 開發工具包管理器
- ADB basic commands
- Async/await asynchronous function
- The UVM Primer——Chapter2: A Conventional Testbench for the TinyALU
- js4day(DOM开始:获取DOM元素内容,修改元素样式,修改表单元素属性,setInterval定时器,轮播图案例)
- 架构师必须了解的 5 种最佳软件架构模式
- Linear DP acwing 898 Number triangle
猜你喜欢

JS iterator generator asynchronous code processing promise+ generator - > await/async
![JDBC prevent SQL injection problems and solutions [preparedstatement]](/img/32/f71f5a31cdf710704267ff100b85d7.png)
JDBC prevent SQL injection problems and solutions [preparedstatement]

Crowncad (crown CAD), the first fully independent 3D CAD platform based on Cloud Architecture in China

Unity SKFramework框架(十六)、Package Manager 开发工具包管理器

嵌入式软件开发

Unity SKFramework框架(十二)、Score 计分模块

Linear DP acwing 899 Edit distance

js2day(又是i++和++i,if语句,三元运算符,switch、while语句,for循环语句)

Tencent three sides: in the process of writing files, the process crashes, and will the file data be lost?

Ntmfs4c05nt1g N-ch 30V 11.9a MOS tube, pdf
随机推荐
Explain in detail the process of realizing Chinese text classification by CNN
Traverse entrylist method correctly
Unity SKFramework框架(十六)、Package Manager 开发工具包管理器
Everyone wants to eat a broken buffet. It's almost cold
[opencv learning] [image histogram and equalization]
Jerry's watch time synchronization [chapter]
Independent and controllable 3D cloud CAD: crowncad enables innovative design of enterprises
Js2day (also i++ and ++i, if statements, ternary operators, switch, while statements, for loop statements)
Js1day (syntaxe d'entrée / sortie, type de données, conversion de type de données, Var et let différenciés)
面渣逆袭:MySQL六十六问,两万字+五十图详解!有点六
上手报告|今天聊聊腾讯目前在用的微服务架构
Record idea shortcut keys
Js10day (API phased completion, regular expression introduction, custom attributes, filtering sensitive word cases, registration module verification cases)
[opencv learning] [Canny edge detection]
JS generates 4-digit verification code
How to get the operating system running PHP- How to get the OS on which PHP is running?
(7) Web security | penetration testing | how does network security determine whether CND exists, and how to bypass CND to find the real IP
Jerry's weather code table [chapter]
[opencv learning] [image pyramid]
Unity skframework framework (XIV), extension extension function