当前位置:网站首页>Principle analysis of security rememberme
Principle analysis of security rememberme
2022-07-02 13:13:00 【InfoQ】
Security RememberMe Principle analysis
.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);
}
}
- When the request arrives , First judge SecurityContextHolder Is there any value in , No indicates that the user has not logged in , Call at this time autoLogin Automatic login .
- After successful automatic login, return rememberMeAuth, Not for null Indicates that the automatic login is successful , Call at this time authenticate Method pair key check , Save the login successful user information to SecurityContextHolder in , Then call the login success callback , Post login success Events .
- If automatic login fails , call rememberMeServices.loginFail Method to handle login failure logic
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;
}
}
}
- call extractRememberMeCookie Method extracts the required from the current request Cookie Information .rememberMeCookie by null return null, The length is 0 Cancel cookie, take remember-me Is set to null
- call decodeCookie Method to parse the obtained token , The result of parsing is that the first part is the currently logged in user name , The second part is the timestamp , The third part is signature , Extract it to form an array .
- call processAutoLoginCookie Method Cookie To verify , User information is returned after verification , Implemented by subclasses
- Last call createSuccessfulAuthentication Create a user object with successful login , The type is RememberMeAuthenticationToken , Different from the user object logged in with user name and password 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;
}
}
}
}
- Judge cookieTokens Is the length 3 No 3 Wrong format Throw an exception directly
- from cookieTokens Extract the first item from the array , Judge whether it is overdue
- For the first 0 term , Get the user name and query the current user information
- call makeTokenSignature Generate signature , Signature generation method :username + ":" + tokenExpiryTime + ":" + password + ":" + this.getKey() Form a string for MD5 encryption
- Judgment No. 4 Whether the signature generated in this step is the same as the one passed in
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) + "'");
}
}
}
- Get user name and password information The password was not reloaded from the database
- Calculate the expiration time of the token , The default validity period of the token is 14 God
- According to the token expiration time , User name password calculation signature
- call setCookie Method setting Cookie, Pass in the user name, expiration time and signature respectively , stay setCookie The array is converted to a string and Base64 code
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 The array length is 2 The first 0 Items for series The first 1 Items for token
- Extract two data according to series Query the database ,token The difference indicates that the automatic login token has been compromised , At this point, remove all automatic login records , Throw an exception
- Judge whether it is expired according to the query results in the database
- Generate a new PersistentRememberMeToken User name and series unchanged , change token And the current time , modify the database
- call addCookie add to Cookie,addCookie Call in method setCookie Set up
- Feel the user name, query the user object and return
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);
}
}
边栏推荐
- 阿里发布的Redis开发文档,涵盖了所有的redis操作
- 挥发性有机物TVOC、VOC、VOCS气体检测+解决方案
- Finally, someone explained the supervised learning clearly
- Tencent three sides: in the process of writing files, the process crashes, and will the file data be lost?
- 文件的下载与图片的预览
- 屠榜多目标跟踪!BoT-SORT:稳健的关联多行人跟踪
- Unity SKFramework框架(十九)、POI 兴趣点/信息点
- 【云原生数据库】遇到慢SQL该怎么办(上)?
- Professor of Shanghai Jiaotong University: he Yuanjun - bounding box (containment / bounding box)
- 诚邀青年创作者,一起在元宇宙里与投资人、创业者交流人生如何做选择……...
猜你喜欢

Js2day (also i++ and ++i, if statements, ternary operators, switch, while statements, for loop statements)

【蓝桥杯选拔赛真题43】Scratch航天飞行 少儿编程scratch蓝桥杯选拔赛真题讲解

Fundamentals of face recognition (facenet)

Web基础

West digital decided to raise the price of flash memory products immediately after the factory was polluted by materials

难忘阿里,4面技术5面HR附加笔试面,走的真艰难真心酸

Ltc3307ahv meets EMI standard, step-down converter qca7005-al33 phy
![Lucky numbers in the [leetcode daily question] matrix](/img/c8/47a22644bf8cc1f49c5668d72161b6.jpg)
Lucky numbers in the [leetcode daily question] matrix

Get started REPORT | today, talk about the microservice architecture currently used by Tencent
![[200 opencv routines] 100 Adaptive local noise reduction filter](/img/89/9e9b667dd28cb25af005b6028ef26c.jpg)
[200 opencv routines] 100 Adaptive local noise reduction filter
随机推荐
Unity SKFramework框架(十四)、Extension 扩展函数
Professor of Shanghai Jiaotong University: he Yuanjun - bounding box (containment / bounding box)
How can attribute mapping of entity classes be without it?
运维必备——ELK日志分析系统
SAP MM 因物料有负库存导致MMPV开账期失败问题之对策
Ali was killed by two programming problems at the beginning, pushed inward again, and finally landed (he has taken an electronic offer)
Five best software architecture patterns that architects must understand
上海交大教授:何援军——包围盒(包容体/包围盒子)
Unity SKFramework框架(十九)、POI 兴趣点/信息点
C modifier
moon
[200 opencv routines] 100 Adaptive local noise reduction filter
【笔耕不辍勋章活动】生命不止,写作不息
[opencv learning] [image filtering]
Unity skframework framework (XVI), package manager development kit Manager
Jerry's watch stops ringing [article]
How to get the operating system running PHP- How to get the OS on which PHP is running?
Jerry's weather code table [chapter]
Unity skframework framework (XX), VFX lab special effects library
二、帧模式 MPLS 操作