SpringSecurity学习笔记(九)RememberMe进阶
参考视频,编程不良人
前面我们介绍了rememberMe的实现原理,从中我们可以思考这样一个问题,如果我们的cookie被非法用户获取,然后携带这个cookie进行访问我们的项目中的内容,就会导致非法用户登录。这个问题怎么解决呢?
RememberMe进阶
在之前我们提到了cookie的生成以及验证都是在TokenBasedRememberMeServices这个类里面完成的,RememberMeServices这个接口还有一个实现类PersistentTokenBasedRememberMeServices

PersistentTokenBasedRememberMeServices就是一个进阶版本的rememberMe的实现,这个生成的cookie是Series和TokenValue组成的
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);//这个生成的cookieaddCookie(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);}
如果下次会话过期了之后就会走下面这块的认证逻辑,这里面的PersistentTokenRepository 的实现默认是基于内存的实现
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 cookiethrow 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的持久化令牌
对于前面的基于内存的记住我的功能,一旦项目重启了之后就需要重新登录,这有的时候是不符合要求的,我们需要把这种基于内存实现的方式,放到数据库里面进行实现。

默认的是基于内存的,我们可以自定义基于数据库的实现。
在配置类中添加
@Beanprotected PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices(){PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices =
// new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, new InMemoryTokenRepositoryImpl());new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, jdbcTokenRepository());return persistentTokenBasedRememberMeServices;}@Beanprotected JdbcTokenRepositoryImpl jdbcTokenRepository(){JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);tokenRepository.setCreateTableOnStartup(true);return tokenRepository;}

这个时候启动项目就会生成表结构

此时我登录用户就会生成对应的cookie持久化到数据库

这个时候我重启项目然后刷新页面一样是不需要重新登录的。重启的时候要把这句话注释掉
// tokenRepository.setCreateTableOnStartup(true);
刷新之后

可以发现token的值变了,series值没变,这和我们上面的分析是一致的。
对于前后端分离项目,如果要设置rememberMe,就需要修改下面的部分

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;}
我们需要写个子类覆盖父类的rememberMeRequested方法。具体的做法可以参考不良人的视频
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
