当前位置:网站首页>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);
}
}
边栏推荐
- Browser storage scheme
- Apply lnk306gn-tl converter, non isolated power supply
- Ltc3307ahv meets EMI standard, step-down converter qca7005-al33 phy
- Linear DP acwing 895 Longest ascending subsequence
- Linear DP acwing 898 Number triangle
- Unity skframework framework (XVIII), roamcameracontroller roaming perspective camera control script
- Record idea shortcut keys
- Jerry's watch stops ringing [article]
- 三翼鸟两周年:羽翼渐丰,腾飞指日可待
- 面渣逆袭:MySQL六十六问,两万字+五十图详解!有点六
猜你喜欢

Japan bet on national luck: Web3.0, anyway, is not the first time to fail!

Word efficiency guide - word's own template

Unity skframework framework (XVII), freecameracontroller God view / free view camera control script

Five best software architecture patterns that architects must understand

js4day(DOM开始:获取DOM元素内容,修改元素样式,修改表单元素属性,setInterval定时器,轮播图案例)

VIM super practical guide collection of this one is enough
![[opencv learning] [common image convolution kernel]](/img/15/d1e8b8aa3c613755e64edb8c9a0f54.jpg)
[opencv learning] [common image convolution kernel]
![[opencv learning] [image histogram and equalization]](/img/e7/b8dc55a9febf2b2949fce3a7ac21f9.jpg)
[opencv learning] [image histogram and equalization]

Unity SKFramework框架(十七)、FreeCameraController 上帝视角/自由视角相机控制脚本

嵌入式软件开发
随机推荐
EasyDSS点播服务分享时间出错如何修改?
Unity SKFramework框架(十六)、Package Manager 開發工具包管理器
Professor of Shanghai Jiaotong University: he Yuanjun - bounding box (containment / bounding box)
Linear DP acwing 895 Longest ascending subsequence
Ruby: how to copy variables without pointing to the same object- Ruby: how can I copy a variable without pointing to the same object?
Browser node event loop
文件的下载与图片的预览
Unity SKFramework框架(十九)、POI 兴趣点/信息点
JS iterator generator asynchronous code processing promise+ generator - > await/async
Record idea shortcut keys
[opencv learning] [image pyramid]
Unity SKFramework框架(十八)、RoamCameraController 漫游视角相机控制脚本
moon
Counter attack of flour dregs: MySQL 66 questions, 20000 words + 50 pictures in detail! A little six
Counting class DP acwing 900 Integer partition
Unity skframework framework (XVI), package manager development kit Manager
8A Synchronous Step-Down regulator tps568230rjer_ Specification information
Jerry's watch stops ringing [article]
Hundreds of web page special effects can be used. Don't you come and have a look?
机器学习基础(二)——训练集和测试集的划分