在开始之前我们补充一下四个知识:(1) Whitelist IP Range,这个很容易理解,添加IP地址限制。我们可以在WebSecurityConfig中的configure()中添加
(2)Message Localization,这个就是我们之前使用过的i18n,我们可以先看一下登录页面:
没有错,这也是登录页面,只不过是从我们登录页面点了“Spanish”
这一块在对我们国际化项目时比较有用。
(3)登出的处理,如果我们不处理,在登录后进去点击右上角的 Logout链接,会出现
我们需要新增一个logout.html,同时修改一下WMvcConfig配置,添加一条新纪录
然后我们就可以正常退出了。
(4)自定义的AuthenticationSuccessHandler,这个有点像我们之前用到过的CustomAuthenticationFailureHandler,我们可以在认证成功之后统一做一些处理,而不是简简单单的页面跳转——我们可以在session中存一些信息,比如下面username.这个可以用于页面显示用户名。
我们还可以更精确地控制不同用户跳转地页面:常见的就是“Admin”页面
我们后面还有对这个处理器进行修改,所有在后面的时候我们再来具体看这个类。
好了,现在回到,“记住我”这个主题上来。
如果是自己的个人电脑,每次让我输入用户名和密码的确安全一些,但是每次操作起来却很麻烦。我们现在使用的APP也都有类似记住我的功能,不用每次登录的确是件用户体验良好的事。那么我们先从代码改造上来看看我们需要实现记住我,需要做那些工作。
1.修改WebSecurityConfig配置:
我看看到上面有一个CustomRememberMeServices,可以看一下全代码:
我们再具体看一下CustomRememberMeServices类体系结构
我们可以看到最上面是RememberMeServices接口
我们先把代码改动讲完,最后我们再看看spring-security 内部怎么实现这个记住我的功能,不然在此处展开内部实现实现细节,会打乱“代码改造”节奏。
2.修改login.html页面,添加记住我——remember-me的checkbox
效果如下:
我们正常登录后可以看到控制台:
至此,我们的代码改造完成了,可以来验证一下。首先我们可以看到数据库中多了一张表(需要jdbcTokenRepository.setCreateTableOnStartup(true);这行代码执行后我删除了)
成功登录里面也是有数据的
然后我们重启服务,来模拟触发session失效,然后刷新浏览器,注意此时我们浏览器的地址是
http://localhost:18080/homepage.html?user=wys2317@hotmail.com
如果没有添加记住我,刷新页面肯定是跳转到登录页面,因为这个页面需要用户登录后才能访问,由于我们使用“持久化的记住我”功能,我们不会再跳转到登录页面了,服务器认为我们已经登录过了——因为我们的浏览器中有remember-me的cookie.
现在已经完成了 “持久化的记住我”功能 ,我们再来看看它的内部实现。
Spring Security 过滤器中 AbstractAuthenticationProcessingFilter 和RememberMeAuthenticationFilter 会调用这个接口中的实现类中的方法。我们关心的是RememberMeAuthenticationFilter,再一起来看看它的源码中核心代码
其中 rememberMeServices 会调用自动登录方法来返回一个Authentication
Authentication rememberMeAuth = rememberMeServices.autoLogin(request,response);
我们再看看 autoLogin()的具体实现-->AbstractRememberMeServices
我们看到extractRememberMeCookie(request);它的具体实现
它就是从request中获取Cookie [],在前面我们看到浏览器控制台中cookie是一长串字符串
VjUlMkIwVFJnVlF6RnZ4SEN6JTJGaW92SEElM0QlM0Q6MFpWT1RyOFAzSFNaUGI5eiUyQjRlVU1BJTNEJTNE
然后我们数据库中保存是username,series,token,last_used。
显然返回给浏览器端的cookie是经过编码的,我们拿到浏览器端的cookie同样需要解码。String[] cookieTokens = decodeCookie(rememberMeCookie);
然后使用解码后的cookie完成自动登录
user = processAutoLoginCookie(cookieTokens, request, response);
userDetailsChecker.check(user);
return createSuccessfulAuthentication(request, user);
最后我们可以小结一下“记住我”完成自动登录的内部细节:
首先是PersistentTokenBasedRememberMeServices在用户成功登录后,会生成一个PersistentRememberMeToken,然后保存在数据库中,同时把cookie回写到request中,这样浏览器端就有了cookie.
假设过了一段时间但是cookie仍然没有过期的情况下访问需要登录的资源,RememberMeAuthenticationFilter会调用RememberMeServices中的autoLogin(),尝试使用请求的cookie来完成用户自动登陆过程,再返回用户请求的资源。
在我们的代码项目中还是有可以改进的地方,在cookie有效的情况下,如果访问/login还是会跳转到登录页面,一个比较好的方式是直接跳转到成功登录后的页面。
小结:“ 记住我 ”是一个用户体验友好的功能,对于用户而言没有“持久化”,这是后端需要完成的工作,它大大减少了用户手动登录的次数(因为这项操作比较麻烦),尽管牺牲了一些安全性,返回给浏览器了编码后的用户信息——cookie,从后面的源码中我们可以看到解码后可以拿到用户名和过期时间。所以后面就有了我们需要防止用户暴力破解密码的功能。这个留到下一篇。“记住我”这个功能除了完成自动登录,其实它还在“自动地”更新cookie的有效期,默认的token有效期我们可以在源码TokenBasedRememberMeServices中看到是14天。
具体的代码可以参见github.