Spring Security(12)——Remember-Me功能
目录
1.1 概述
1.2 基于简单加密token的方法
1.3 基于持久化token的方法
1.4 Remember-Me相关接口和实现类
1.4.1 TokenBasedRememberMeServices
1.4.2 PersistentTokenBasedRememberMeServices
1.1 概述
Remember-Me是指网站能够在Session之间记住登录用户的身份,具体来说就是我成功认证一次之后在一定的时间内我可以不用再输入用户名和密码进行登录了,系统会自动给我登录。这通常是通过服务端发送一个cookie给客户端浏览器,下次浏览器再访问服务端时服务端能够自动检测客户端的cookie,根据cookie值触发自动登录操作。Spring Security为这些操作的发生提供必要的钩子,并且针对于Remember-Me功能有两种实现。一种是简单的使用加密来保证基于cookie的token的安全,另一种是通过数据库或其它持久化存储机制来保存生成的token。
需要注意的是两种实现都需要一个UserDetailsService。如果你使用的AuthenticationProvider不使用UserDetailsService,那么记住我将会不起作用,除非在你的ApplicationContext中拥有一个UserDetailsService类型的bean。
1.2 基于简单加密token的方法
当用户选择了记住我成功登录后,Spring Security将会生成一个cookie发送给客户端浏览器。cookie值由如下方式组成:
base64(username+":"+expirationTime+":"+md5Hex(username+":"+expirationTime+":"+password+":"+key))
- username:登录的用户名。
- password:登录的密码。
- expirationTime:token失效的日期和时间,以毫秒表示。
- key:用来防止修改token的一个key。
这样用来实现Remember-Me功能的token只能在指定的时间内有效,且必须保证token中所包含的username、password和key没有被改变才行。需要注意的是,这样做其实是存在安全隐患的,那就是在用户获取到实现记住我功能的token后,任何用户都可以在该token过期之前通过该token进行自动登录。如果用户发现自己的token被盗用了,那么他可以通过改变自己的登录密码来立即使其所有的记住我token失效。如果希望我们的应用能够更安全一点,可以使用接下来要介绍的持久化token方式,或者不使用Remember-Me功能,因为Remember-Me功能总是有点不安全的。
使用这种方式时,我们只需要在http元素下定义一个remember-me元素,同时指定其key属性即可。key属性是用来标记存放token的cookie的,对应上文提到的生成token时的那个key。
<security:http auto-config="true">
<security:form-login/>
<!-- 定义记住我功能 -->
<security:remember-me key="elim"/>
<security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
这里有两个需要注意的地方。第一,如果你的登录页面是自定义的,那么需要在登录页面上新增一个名为“_spring_security_remember_me”的checkbox,这是基于NameSpace定义提供的默认名称,如果要自定义可以自己定义TokenBasedRememberMeServices或PersistentTokenBasedRememberMeServices对应的bean,然后通过其parameter属性进行指定,具体操作请参考后文关于《Remember-Me相关接口和实现类》部分内容。第二,上述功能需要一个UserDetailsService,如果在你的ApplicationContext中已经拥有一个了,那么Spring Security将自动获取;如果没有,那么当然你需要定义一个;如果拥有在ApplicationContext中拥有多个UserDetailsService定义,那么你需要通过remember-me元素的user-service-ref属性指定将要使用的那个。如:
<security:http auto-config="true">
<security:form-login/>
<!-- 定义记住我功能,通过user-service-ref指定将要使用的UserDetailsService-->
<security:remember-me key="elim" user-service-ref="userDetailsService"/>
<security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
<bean id="userDetailsService"class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
1.3 基于持久化token的方法
持久化token的方法跟简单加密token的方法在实现Remember-Me功能上大体相同,都是在用户选择了“记住我”成功登录后,将生成的token存入cookie中并发送到客户端浏览器,待到下次用户访问系统时,系统将直接从客户端cookie中读取token进行认证。所不同的是基于简单加密token的方法,一旦用户登录成功后,生成的token将在客户端保存一段时间,如果用户不点击退出登录,或者不修改密码,那么在cookie失效之前,他都可以使用该token进行登录,哪怕该token被别人盗用了,用户与盗用者都同样可以进行登录。而基于持久化token的方法采用这样的实现逻辑:
(1)用户选择了“记住我”成功登录后,将会把username、随机产生的序列号、生成的token存入一个数据库表中,同时将它们的组合生成一个cookie发送给客户端浏览器。
(2)当下一次没有登录的用户访问系统时,首先检查cookie,如果对应cookie中包含的username、序列号和token与数据库中保存的一致,则表示其通过验证,系统将重新生成一个新的token替换数据库中对应组合的旧token,序列号保持不变,同时删除旧的cookie,重新生成包含新生成的token,就的序列号和username的cookie发送给客户端。
(3)如果检查cookie时,cookie中包含的username和序列号跟数据库中保存的匹配,但是token不匹配。这种情况极有可能是因为你的cookie被人盗用了,由于盗用者使用你原本通过认证的cookie进行登录了导致旧的token失效,而产生了新的token。这个时候Spring Security就可以发现cookie被盗用的情况,它将删除数据库中与当前用户相关的所有token记录,这样盗用者使用原有的cookie将不能再登录,同时提醒用户其帐号有被盗用的可能性。
(4)如果对应cookie不存在,或者包含的username和序列号与数据库中保存的不一致,那么将会引导用户到登录页面。
从以上逻辑我们可以看出持久化token的方法比简单加密token的方法更安全,因为一旦你的cookie被人盗用了,你只要再利用原有的cookie试图自动登录一次,原有的token将失效导致盗用者不能再使用原来盗用的cookie进行登录了,同时用户可以发现自己的cookie有被盗用的可能性。但因为cookie被盗用后盗用者还可以在用户下一次登录前顺利的进行登录,所以如果你的应用对安全性要求比较高就不要使用Remember-Me功能了。
使用持久化token方法时需要我们的数据库中拥有如下表及其表结构。
create table persistent_logins (username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
然后还是通过remember-me元素来使用,只是这个时候我们需要其data-source-ref属性指定对应的数据源,同时别忘了它也同样需要ApplicationContext中拥有UserDetailsService,如果拥有多个,请使用user-service-ref属性指定remember-me使用的是哪一个。
<security:http auto-config="true">
<security:form-login/>
<!-- 定义记住我功能 -->
<security:remember-me data-source-ref="dataSource"/>
<security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
1.4 Remember-Me相关接口和实现类
在上述介绍中,我们实现Remember-Me功能是通过Spring Security为了简化Remember-Me而提供的NameSpace进行定义的。而底层实际上还是通过RememberMeServices、UsernamePasswordAuthenticationFilter和RememberMeAuthenticationFilter的协作来完成的。RememberMeServices是Spring Security为Remember-Me提供的一个服务接口,其定义如下。
publicinterface RememberMeServices {
/**
* 自动登录。在实现这个方法的时候应该判断用户提供的Remember-Me cookie是否有效,如果无效,应当直接忽略。
* 如果认证成功应当返回一个AuthenticationToken,推荐返回RememberMeAuthenticationToken;
* 如果认证不成功应当返回null。
*/
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
/**
* 在用户登录失败时调用。实现者应当做一些类似于删除cookie之类的处理。
*/
void loginFail(HttpServletRequest request, HttpServletResponse response);
/**
* 在用户成功登录后调用。实现者可以在这里判断用户是否选择了“Remember-Me”登录,然后做相应的处理。
*/
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication);
}
UsernamePasswordAuthenticationFilter拥有一个RememberMeServices的引用,默认是一个空实现的NullRememberMeServices,而实际当我们通过remember-me定义启用Remember-Me时,它会是一个具体的实现。用户的请求会先通过UsernamePasswordAuthenticationFilter,如认证成功会调用RememberMeServices的loginSuccess()方法,否则调用RememberMeServices的loginFail()方法。UsernamePasswordAuthenticationFilter是不会调用RememberMeServices的autoLogin()方法进行自动登录的。之后运行到RememberMeAuthenticationFilter时如果检测到还没有登录,那么RememberMeAuthenticationFilter会尝试着调用所包含的RememberMeServices的autoLogin()方法进行自动登录。关于RememberMeServices Spring Security已经为我们提供了两种实现,分别对应于前文提到的基于简单加密token和基于持久化token的方法。
1.4.1 TokenBasedRememberMeServices
TokenBasedRememberMeServices对应于前文介绍的使用namespace时基于简单加密token的实现。TokenBasedRememberMeServices会在用户选择了记住我成功登录后,生成一个包含token信息的cookie发送到客户端;如果用户登录失败则会删除客户端保存的实现Remember-Me的cookie。需要自动登录时,它会判断cookie中所包含的关于Remember-Me的信息是否与系统一致,一致则返回一个RememberMeAuthenticationToken供RememberMeAuthenticationProvider处理,不一致则会删除客户端的Remember-Me cookie。TokenBasedRememberMeServices还实现了Spring Security的LogoutHandler接口,所以它可以在用户退出登录时立即清除Remember-Me cookie。
如果把使用namespace定义Remember-Me改为直接定义RememberMeServices和对应的Filter来使用的话,那么我们可以如下定义。
<security:http>
<security:form-login login-page="/login.jsp"/>
<security:intercept-url pattern="/login*.jsp*"access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="ROLE_USER" />
<!-- 把usernamePasswordAuthenticationFilter加入FilterChain -->
<security:custom-filter ref="usernamePasswordAuthenticationFilter"before="FORM_LOGIN_FILTER"/>
<security:custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>
</security:http>
<!-- 用于认证的AuthenticationManager -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider
user-service-ref="userDetailsService"/>
<security:authentication-provider ref="rememberMeAuthenticationProvider"/>
</security:authentication-manager>
<bean id="userDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="usernamePasswordAuthenticationFilter"class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="authenticationManager"/>
<!-- 指定request中包含的用户名对应的参数名 -->
<property name="usernameParameter" value="username"/>
<property name="passwordParameter" value="password"/>
<!-- 指定登录的提交地址 -->
<property name="filterProcessesUrl" value="/login.do"/>
</bean>
<!-- Remember-Me对应的Filter -->
<bean id="rememberMeFilter"
class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices" />
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<!-- RememberMeServices的实现 -->
<bean id="rememberMeServices"
class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="userDetailsService" />
<property name="key" value="elim" />
<!-- 指定request中包含的用户是否选择了记住我的参数名 -->
<property name="parameter" value="rememberMe"/>
</bean>
<!-- key值需与对应的RememberMeServices保持一致 -->
<bean id="rememberMeAuthenticationProvider"
class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="elim" />
</bean>
需要注意的是RememberMeAuthenticationProvider在认证RememberMeAuthenticationToken的时候是比较它们拥有的key是否相等,而RememberMeAuthenticationToken的key是TokenBasedRememberMeServices提供的,所以在使用时需要保证RememberMeAuthenticationProvider和TokenBasedRememberMeServices的key属性值保持一致。需要配置UsernamePasswordAuthenticationFilter的rememberMeServices为我们定义好的TokenBasedRememberMeServices,把RememberMeAuthenticationProvider加入AuthenticationManager的providers列表,并添加RememberMeAuthenticationFilter和UsernamePasswordAuthenticationFilter到FilterChainProxy。
1.4.2 PersistentTokenBasedRememberMeServices
PersistentTokenBasedRememberMeServices是RememberMeServices基于前文提到的持久化token的方式实现的。具体实现逻辑跟前文介绍的以NameSpace的方式使用基于持久化token的Remember-Me是一样的,这里就不再赘述了。此外,如果单独使用,其使用方式和上文描述的TokenBasedRememberMeServices是一样的,这里也不再赘述了。
需要注意的是PersistentTokenBasedRememberMeServices是需要将token进行持久化的,所以我们必须为其指定存储token的PersistentTokenRepository。Spring Security对此有两种实现,InMemoryTokenRepositoryImpl和JdbcTokenRepositoryImpl。前者是将token存放在内存中的,通常用于测试,而后者是将token存放在数据库中。PersistentTokenBasedRememberMeServices默认使用的是前者,我们可以通过其tokenRepository属性来指定使用的PersistentTokenRepository。
使用JdbcTokenRepositoryImpl时我们可以使用在前文提到的默认表结构。如果需要使用自定义的表,那么我们可以对JdbcTokenRepositoryImpl进行重写。定义JdbcTokenRepositoryImpl时需要指定一个数据源dataSource,同时可以通过设置参数createTableOnStartup的值来控制是否要在系统启动时创建对应的存入token的表,默认创建语句为“create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)”,但是如果自动创建时对应的表已经存在于数据库中,则会抛出异常。createTableOnStartup属性默认为false。
直接显示地使用PersistentTokenBasedRememberMeServices和上文提到的直接显示地使用TokenBasedRememberMeServices的方式是一样的,我们只需要将上文提到的配置中RememberMeServices实现类TokenBasedRememberMeServices换成PersistentTokenBasedRememberMeServices即可。
<!-- RememberMeServices的实现 -->
<bean id="rememberMeServices"
class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
<property name="userDetailsService" ref="userDetailsService" />
<property name="key" value="elim" />
<!-- 指定request中包含的用户是否选择了记住我的参数名 -->
<property name="parameter" value="rememberMe"/>
<!-- 指定PersistentTokenRepository -->
<property name="tokenRepository">
<beanclass="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 是否在系统启动时创建持久化token的数据库表 -->
<property name="createTableOnStartup" value="false"/>
</bean>
</property>
</bean>
(注:本文是基于Spring Security3.1.6所写)
Spring Security(12)——Remember-Me功能的更多相关文章
- Spring Security 3.1 中功能强大的加密工具 PasswordEncoder
Spring Security 3.1 中功能强大的加密工具 PasswordEncoder 博客分类: security spring springsecurity 好吧,这种加密机制很复杂,还是 ...
- 「快学springboot」集成Spring Security实现鉴权功能
Spring Security介绍 Spring Security是Spring全家桶中的处理身份和权限问题的一员.Spring Security可以根据使用者的需要定制相关的角色身份和身份所具有的权 ...
- Spring Security教程(七):RememberMe功能
在之前的教程中一笔带过式的讲了下RememberMe记住密码的功能,那篇的Remember功能是最简易的配置,其功能和安全性都不强.这里就配置下security中RememberMe的各种方式. 一. ...
- Spring Security基本原理
近期研究了Spring Security,现进行记录. 首先先进行一个最简单的demo.默认情况下,在Spring Boot里,如果在classpath下面有Spring Security相关的jar ...
- Spring Security研究(1)
1, 获取Spring Security的Jar包 :从Spring网站下载页下载或者从Maven中央仓库下载.一个好办法是参考实例应用中包含的依赖库. 2,项目模块: Core - spring ...
- Spring Security 整合freemaker 实现简单登录和角色控制
Spring Security 整合freemaker 实现简单登录和角色控制 写这篇文章是因为我做了一个电商网站项目,近期刚加上权限控制.整个过程很简单,在此给大家梳理一下,也算是自己对知识 ...
- 【Spring Security】七、RememberMe配置
一.概述 RememberMe 是指用户在网站上能够在 Session 之间记住登录用户的身份的凭证,通俗的来说就是用户登陆成功认证一次之后在制定的一定时间内可以不用再输入用户名和密码进行自动登录.这 ...
- spring-security-4 (5)spring security Java配置实现自定义表单认证与授权
前面三篇讲解了spring security的搭建以及简单的表单认证与授权原理.本篇将实现我们自定义的表单登录与认证. 本篇不会再讲项目的搭建过程,因为跟第二节的搭建如出一辙.本篇也不会将项目中所有 ...
- 转:Spring学习笔记---Spring Security登录页
转:http://axuebin.com/blog/2016/06/21/spring-security/?utm_source=tuicool&utm_medium=referral. 提示 ...
- Spring Boot整合实战Spring Security JWT权限鉴权系统
目前流行的前后端分离让Java程序员可以更加专注的做好后台业务逻辑的功能实现,提供如返回Json格式的数据接口就可以.像以前做项目的安全认证基于 session 的登录拦截,属于后端全栈式的开发的模式 ...
随机推荐
- codewars-random(2)
找出数组中的间谍 思路一:遍历一遍数组,开始前将flag设置为0:将count设为0:每当出现一个奇数(注意负数)count加1,当count大于等于2时将flag至为1: 再遍历一遍数组,如果fla ...
- 如何运用xp_cmdshell 执行多行cmd命令
因为需要利用sql 脚本调用外部程序,所以尝试xp_cmdshell 命令,发现其默认只能执行一行命令,最后找到了下面方法: 将多行cmd 命令处理成bat文件,然后直接利用所在路径+文件来执行: ...
- mysql的部分基础知识....
- Winform DataGridView直接导出Excel
/// <summary> /// 导出excel /// </summary> /// <param name="fileName">导出文件 ...
- linux 安装php的redis拓展
安装步骤: #wget https://github.com/nicolasff/phpredis/archive/2.2.4.tar.gz #tar -zxvf 2.2.4 #cd phpredi ...
- 添加JSTL 1.2 依赖库
添加JSTL 1.2 依赖库 JSTL 是一项很有历史的技术,而且版本自Java 5以来长期停留在1.2.但在做简单演示的页面时jstl依然有用,当前我们依然能看到这项技术(在博客.文档的demo里很 ...
- 前端 TDD 开发
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px ".PingFang SC"; color: #454545 } p.p2 ...
- java通过JDBC链接SQLServer2012 (含1433端口打通)
首先,在连接数据库之前必须保证SQL Server 2012是采用SQL Server身份验证方式而不是windows身份验证方式.如果在安装时选用了后者,则重新设置如下: http://blog.1 ...
- mac生成ssh keys
打开终端 输入ssh-keygen,然后系统提示输入文件保存位置等信息,连续敲三次回车即可,生成的SSH key文件保存在中-/.ssh/id_rsa.pub
- Scala分号推断
看这样段代码,Scala会把它当作两个语句,x 和 +y,如果想把它作为一个语句,可以把它们放在括号里(x+y) x +y 或者也可以把 + 放在行末,也正因为此,串接类似于 + 这样的中缀操作符的时 ...