Remember-Me功能

目录

1.1概述

1.2基于简单加密token的方法

1.3基于持久化token的方法

1.4Remember-Me相关接口和实现类

1.4.1TokenBasedRememberMeServices

1.4.2PersistentTokenBasedRememberMeServices

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:httpauto-config="true">

<security:form-login/>

<security:remember-mekey="elim"/>

<security:intercept-urlpattern="/**"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:httpauto-config="true">

<security:form-login/>

<security:remember-mekey="elim"user-service-ref="userDetailsService"/>

<security:intercept-urlpattern="/**"access="ROLE_USER"/>

security:http>

<beanid="userDetailsService"class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">

<propertyname="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:httpauto-config="true">

<security:form-login/>

<security:remember-medata-source-ref="dataSource"/>

<security:intercept-urlpattern="/**"access="ROLE_USER"/>

security:http>

1.4Remember-Me相关接口和实现类

在上述介绍中,我们实现Remember-Me功能是通过Spring Security为了简化Remember-Me而提供的NameSpace进行定义的。而底层实际上还是通过RememberMeServices、UsernamePasswordAuthenticationFilter和RememberMeAuthenticationFilter的协作来完成的。RememberMeServices是Spring Security为Remember-Me提供的一个服务接口,其定义如下。

publicinterfaceRememberMeServices {

/**

* 自动登录。在实现这个方法的时候应该判断用户提供的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-loginlogin-page="/login.jsp"/>

<security:intercept-urlpattern="/login*.jsp*"access="IS_AUTHENTICATED_ANONYMOUSLY"/>

<security:intercept-urlpattern="/**"access="ROLE_USER"/>

<security:custom-filterref="usernamePasswordAuthenticationFilter"before="FORM_LOGIN_FILTER"/>

<security:custom-filterref="rememberMeFilter"position="REMEMBER_ME_FILTER"/>

security:http>

<security:authentication-manageralias="authenticationManager">

<security:authentication-provider

user-service-ref="userDetailsService"/>

<security:authentication-providerref="rememberMeAuthenticationProvider"/>

security:authentication-manager>

<beanid="userDetailsService"

class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">

<propertyname="dataSource"ref="dataSource"/>

bean>

<beanid="usernamePasswordAuthenticationFilter"class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">

<propertyname="rememberMeServices"ref="rememberMeServices"/>

<propertyname="authenticationManager"ref="authenticationManager"/>

<propertyname="usernameParameter"value="username"/>

<propertyname="passwordParameter"value="password"/>

<propertyname="filterProcessesUrl"value="/login.do"/>

bean>

<beanid="rememberMeFilter"

class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">

<propertyname="rememberMeServices"ref="rememberMeServices"/>

<propertyname="authenticationManager"ref="authenticationManager"/>

bean>

<beanid="rememberMeServices"

class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">

<propertyname="userDetailsService"ref="userDetailsService"/>

<propertyname="key"value="elim"/>

<propertyname="parameter"value="rememberMe"/>

bean>

<beanid="rememberMeAuthenticationProvider"

class="org.springframework.security.authentication.RememberMeAuthenticationProvider">

<propertyname="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即可。

<beanid="rememberMeServices"

class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">

<propertyname="userDetailsService"ref="userDetailsService"/>

<propertyname="key"value="elim"/>

<propertyname="parameter"value="rememberMe"/>

<propertyname="tokenRepository">

<beanclass="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">

<propertyname="dataSource"ref="dataSource"/>

<propertyname="createTableOnStartup"value="false"/>

bean>

property>

bean>

Remember-Me功能的更多相关文章

  1. 制作类似ThinkPHP框架中的PATHINFO模式功能

    一.PATHINFO功能简述 搞PHP的都知道ThinkPHP是一个免费开源的轻量级PHP框架,虽说轻量但它的功能却很强大.这也是我接触学习的第一个框架.TP框架中的URL默认模式即是PathInfo ...

  2. PHP搭建大文件切割分块上传功能

    背景 在网站开发中,文件上传是很常见的一个功能.相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示"该文件过大".因为一般情况下,我们都需要对上传的文件大小做限制,防止出现 ...

  3. SQL Server2014 SP2新增的数据库克隆功能

    SQL Server2014 SP2新增的数据库克隆功能 创建测试库 --创建测试数据库 create database testtest use testtest go --创建表 )) --插入数 ...

  4. SQL Server 数据加密功能解析

    SQL Server 数据加密功能解析 转载自: 腾云阁 https://www.qcloud.com/community/article/194 数据加密是数据库被破解.物理介质被盗.备份被窃取的最 ...

  5. Taurus.MVC 2.2 开源发布:WebAPI 功能增强(请求跨域及Json转换)

    背景: 1:有用户反馈了关于跨域请求的问题. 2:有用户反馈了参数获取的问题. 3:JsonHelper的增强. 在综合上面的条件下,有了2.2版本的更新,也因此写了此文. 开源地址: https:/ ...

  6. TinyWeb v1.0 正式完成第一个Release版本(功能基于 libuv 跨平台库)

    使用方法很简单,很容易融入现有项目,使现有项目拥有Web网站功能和WebSocket,以及Socket直连! 并且包含了一个跨平台(windows/linux)工具集合; 嗯,也挺棒的^,^ 在项目中 ...

  7. Java 8五大主要功能为开发者提供了哪些便利?

    两年前当Java 8发布后,立即受到了业界的欢迎,因为它大大提高了Java的性能.它独特的卖点是,顾及了编程语言的每一个方面,包括JVM(Java虚拟机)和编译器,并且改良了其它帮助系统. Java是 ...

  8. Vue + Webpack + Vue-loader 系列教程(1)功能介绍篇

    原文地址:https://lvyongbo.gitbooks.io/vue-loader/content/ Vue-loader 是什么? vue-loader 是一个加载器,能把如下格式的 Vue ...

  9. .Net Core MVC 网站开发(Ninesky) 2.2、栏目管理功能-System区域添加

    在asp或asp.net中为了方便网站的结构清晰,通常把具有类似功能的页面放到一个文件夹中,用户管理功能都放在Admin文件夹下,用户功能都放在Member文件夹下,在MVC中,通常使用区域(Area ...

  10. ASP.NET MVC5+EF6+EasyUI 后台管理系统(69)-微信公众平台开发-功能概述

    系列目录 为什么要先发这个文章? 因为接下来的文章是关于微信开发的系列,心中一定要有一个概念,知道自己接下来要做什么功能. 而且微信到处都是坑,我首先要把微信与本地跑通起来才敢发布,否则中间出现坑导致 ...

随机推荐

  1. 替代jquery

    如果不需要过多操作,不引用jquery 1.document.ready :$(function(){}) http://www.cnblogs.com/a546558309/p/3478344.ht ...

  2. group_concat

    分割字符串 group_concat(a.EvidencesID separator ',') as EvidencesID #在MySQL配置文件(my.ini)中默认无该配置项,使用默认值时,值为 ...

  3. hdu 4027 Can you answer these queries? 线段树

    线段树+剪枝优化!!! 代码如下: #include<iostream> #include<stdio.h> #include<algorithm> #includ ...

  4. WebSocket 是什么原理?为什么可以实现持久连接

    你可以把 WebSocket 看成是 HTTP 协议为了支持长连接所打的一个大补丁,它和 HTTP 有一些共性,是为了解决 HTTP 本身无法解决的某些问题而做出的一个改良设计.在以前 HTTP 协议 ...

  5. servlet学习笔记三

    Servlet主要内容: 1)状态跟踪 一.状态跟踪 HTTP协议是无状态协议,即请求与请求之间没有任何关系,也就是不会记住任何数据. 但若想在请求间传递数据,怎么办?web里的三个基本容器对象可以解 ...

  6. MySql对空间数据库的支持

    地址: MySQL5.1中文在线API:http://doc.mysql.cn/mysql5/refman-5.1-zh.html-chapter/spatial-extensions-in-mysq ...

  7. Oracle ->> 连续聚合

    select id, grp_factor, sum (id) over( partition by grp_factor order by id rows between unbounded pre ...

  8. java.lang.ClassNotFoundException: org.springframework.context.event.GenericApplicationListener ----good

    Caused by: java.lang.NoClassDefFoundError: org/springframework/context/event/GenericApplicationListe ...

  9. 【verilog】fdisplay中如何保存有符号形式

    2014-01-02 10:10:29 参考:http://xilinx.eetop.cn/viewthread-275584 使用系统任务$signed,如 $fdisplay(fp, " ...

  10. C# GUID的使用

    GUID(全局统一标识符)是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的.通常平台会提供生成GUID的API.生成算法很有意思,用到了以太网卡地址.纳秒级时间.芯片ID码和许多可 ...