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所写)

(注:原创文章,转载请注明出处。原文地址:http://haohaoxuexi.iteye.com/blog/2163997)

Spring Security(12)——Remember-Me功能相关推荐

  1. 9.Spring Security添加记住我功能

    在网站的登录页面中,记住我选项是一个很常见的功能,勾选记住我后在一段时间内,用户无需进行登录操作就可以访问系统资源.在Spring Security中添加记住我功能很简单,大致过程是:当用户勾选了记住 ...

  2. 【Spring Security】的RememberMe功能流程与源码详解

    文章目录 前言 原理 基础版 搭建 初始化sql 依赖引入 配置类 验证 源码分析 进阶版 集成 源码分析 疑问1 疑问2 鉴权 升级版 集成 初始化sql 配置类 验证 源码分析 鉴权 流程 扩展版 ...

  3. Spring Security 3.1 中功能强大的加密工具 PasswordEncoder

    去年发生的密码泄漏事件,我们也对密码加密做了重新研究.  在筛选加密方法的过程中,发现了Spring Security 3.1.0版本中提供了新的PasswordEncoder,它的加密方法非常给力! ...

  4. Spring Security记住我功能之潜在的账号盗取风险

    Spring Security 前言 Spring Security过滤器链 账号风险 如何避免呢? 前言 记住我功能方便是大家看得见的,但是安全性却令人担忧.因为Cookie毕竟是保存在客户端的,很 ...

  5. Spring Security 入门 Remember-Me 记住我功能

    用户选择了"记住我"成功登录后,将会把username.随机生成的序列号.生成的token存入一个数据库表中,同时将它们的组合生成一个cookie发送给客户端浏览器. 当没有登录的 ...

  6. oidc auth2.0_使用Spring Security 5.0和OIDC轻松构建身份验证

    oidc auth2.0 "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和 ...

  7. 使用Spring Security 5.0和OIDC轻松构建身份验证

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. Spri ...

  8. 看完阿里p8整理的Spring Security应用到源码手册,豁然开朗

    又逢"金九银十",年轻的毕业生们满怀希望与忐忑,去寻找.竞争一个工作机会.已经在职的开发同学,也想通过社会招聘或者内推的时机争取到更好的待遇.更大的平台. 然而,面试人群众多,技术 ...

  9. Spring Security使用记录

    文章目录 **前置知识** **1.概念介绍** **1.1权限管理** **1.2完成权限管理需要三个对象** **1.3Spring Security** **1.3.1创建web工程并导入jar ...

  10. SpringBoot 集成 Spring Security

    Spring Security Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架.它实际上是保护基于spring的应用程序的标准. Spring Security是一个 ...

最新文章

  1. Windows自带的杀进程工具
  2. RxJava2 源码解析(一)
  3. [翻译]你或许还未听说过的一些ASP.NET 2.0要诀
  4. android 重装sdk或者系统的时模拟器出现can open ****
  5. Golang实践录:命令行cobra库实例优化
  6. 【luogu T34117 打油门】 题解
  7. SSM公司企业绩效考核管理系统
  8. Excel 图表与数据透视表制作
  9. cve-2018-1273复现思路及简单利用(避坑)
  10. 武魂java_js及jsp.java查错的几种方式
  11. Docker容器详细操作
  12. 苹果新专利针对骑自行车摔倒情况,苹果Find My使自行车免于丢失
  13. C++面向对象程序设计:地铁自动售票系统
  14. 深富策略军工股掀起涨停潮
  15. python股票数据分析_用Python抓取新浪的股票数据
  16. 女性英文名對照及涵意大全
  17. 法规标准-ISO 15622标准解读(2018版)
  18. 餐饮业数字化提速,OceanBase助海底捞变身“云上捞”
  19. 政务云系统——亿网软通“互联网+”政务云解决方案
  20. 谷歌地图拼接软件_谷歌地图上又有新发现?出现两个长条弧状物体,颜色一白和一黑...

热门文章

  1. 毒液蛋白质相互作用分析
  2. win7开机后svchost..exe占用CPU和内存很高的解决方法
  3. 如何使用burp绕过token
  4. 微信小程序——【云音乐播放器】
  5. 大数据技术之数据采集篇
  6. python BFS最短路径问题
  7. 【QTP专题】03_Add-in Manager插件
  8. MyISAM和InnoDB的区别
  9. 要想文章上Google搜索引擎首页,SEO 要做好下面7个点优化
  10. 错误解决:Problem binding to [0.0.0.0:10033] java.net.BindException