Reference video, bad programming people
Earlier we introduced the implementation principle of rememberMe, from which we can think about such a problem, if our cookie is obtained by an illegal user, and then carry this cookie to access the content of our project, it will lead to illegal user login. How to solve this problem?
RememberMeAdvanced
We mentioned before that the generation and verification of cookies are all TokenBasedRememberMeServices
done in this class. RememberMeServices
This interface also has an implementation class PersistentTokenBasedRememberMeServices
PersistentTokenBasedRememberMeServices
that is an advanced version of rememberMe. The generated cookie is Series
composed TokenValue
of
protected void onLoginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {
String username = successfulAuthentication.getName();
logger.debug("Creating new persistent login for user " + username);
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
username, generateSeriesData(), generateTokenData(), new Date());
try {
tokenRepository.createNewToken(persistentToken);
//这个生成的cookie
addCookie(persistentToken, request, response);
}
catch (Exception e) {
logger.error("Failed to save persistent token ", e);
}
}
private void addCookie(PersistentRememberMeToken token, HttpServletRequest request,
HttpServletResponse response) {
setCookie(new String[] {
token.getSeries(), token.getTokenValue() },
getTokenValiditySeconds(), request, response);
}
If the next session expires, the following authentication logic will be followed, and PersistentTokenRepository
the implementation here is based on memory by default.
private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl();
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) + "'");
}
final String presentedSeries = cookieTokens[0];
final String presentedToken = cookieTokens[1];
PersistentRememberMeToken token = tokenRepository
.getTokenForSeries(presentedSeries);
if (token == null) {
// No series match, so we can't authenticate using this cookie
throw new RememberMeAuthenticationException(
"No persistent token found for series id: " + presentedSeries);
}
// We have a match for this user/series combination
//这里对比内存中token对应的value和cookie里面的进行对比,如果相同则认证通过
if (!presentedToken.equals(token.getTokenValue())) {
// Token doesn't match series value. Delete all logins for this user and throw
// an exception to warn them.
tokenRepository.removeUserTokens(token.getUsername());
throw new CookieTheftException(
messages.getMessage(
"PersistentTokenBasedRememberMeServices.cookieStolen",
"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
}
if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
.currentTimeMillis()) {
throw new RememberMeAuthenticationException("Remember-me login has expired");
}
// Token also matches, so login is valid. Update the token value, keeping the
// *same* series number.
if (logger.isDebugEnabled()) {
logger.debug("Refreshing persistent login token for user '"
+ token.getUsername() + "', series '" + token.getSeries() + "'");
}
//下面这一块的逻辑就是把cookie进行一个更新,也就是说一旦会话失效,如果使用了之前的cookie就会生成新
//的cookie,原阿里的cookie九无法使用了。这在一定程度上增加了安全性
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), token.getSeries(), generateTokenData(), new Date());
try {
//这里更新的时候series是不变的,变的是series对应的value值
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");
}
return getUserDetailsService().loadUserByUsername(token.getUsername());
}
RememberMe's persistent token
For the previous memory-based remember-me function, once the project is restarted, you need to log in again, which sometimes does not meet the requirements. We need to put this memory-based implementation into the database for implementation.
The default is memory-based, and we can customize the database-based implementation.
In the configuration class add
@Bean
protected PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices(){
PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices =
// new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, new InMemoryTokenRepositoryImpl());
new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, jdbcTokenRepository());
return persistentTokenBasedRememberMeServices;
}
@Bean
protected JdbcTokenRepositoryImpl jdbcTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
At this time, when the project is started, the table structure will be generated.
At this time, when I log in to the user, the corresponding cookie will be generated and persisted to the database.
At this time, if I restart the project and refresh the page, there is no need to log in again. Comment out this sentence when restarting
// tokenRepository.setCreateTableOnStartup(true);
After refreshing,
it can be found that the value of token has changed, but the value of series has not changed, which is consistent with our above analysis.
For front-end and back-end separation projects, if you want to set rememberMe, you need to modify the following part
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (alwaysRemember) {
return true;
}
String paramValue = request.getParameter(parameter);
if (paramValue != null) {
if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
|| paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
return true;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Did not send remember-me cookie (principal did not set parameter '"
+ parameter + "')");
}
return false;
}
We need to write a subclass to override the rememberMeRequested method of the parent class. For specific methods, please refer to the video of bad people