【Spring Security】七、RememberMe配置
一、概述
- 一种是简单的使用加密来保证基于 cookie 的 token 的安全
- 另一种是通过数据库或其它持久化存储机制来保存生成的 token
- 第一中方式不安全,就是说在用户获取到实现记住我功能的 token 后,任何用户都可以在该 token 过期之前通过该 token 进行自动登录。如果用户发现自己的 token 被盗用了,那么他可以通过改变自己的登录密码来立即使其所有的记住我 token 失效
- 如果希望我们的应用能够更安全,那就使用第二种方式。第二种方式也是详细要讲解的。
基本原理


二、基于简单加密的方式
首先需要改动login.jsp,把记住我的checkbox的name修改为自定义的名称
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>自定义登陆页面</title>
</head>
<body>
<div class="error ${param.error == true ? '' : 'hide'}">
登陆失败<br>
${sessionScope['SPRING_SECURITY_LAST_EXCEPTION'].message}
</div>
<form method="post" action="${pageContext.request.contextPath}/j_spring_security_check" style="width:260px; text-align: center">
<fieldset>
<legend>登陆</legend>
用户: <input type="text" name="j_username" style="width: 150px;" value="${sessionScope['SPRING_SECURITY_LAST_USERNAME']}" />
<br/>
密码: <input type="password" name="j_password" style="width: 150px;" />
<br/>
<input type="checkbox" name="remember-me" />记住我<br/>
<input type="submit" value="登陆" />
<input type="reset" value="重置" />
</fieldset>
</form>
</body>
</html>
需要特别注意的是,这两种方式在配置的时候都要提供一个UserDetailsService,这个东西其实就是之前配置的jdbc-user-service标签的一个实现类,配置代码如下:
<beans:bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <beans:property name="usersByUsernameQuery" value="select username,password,status as enabled from user where username = ?" /> <beans:property name="authoritiesByUsernameQuery" value="select user.username,role.name from user,role,user_role where user.id=user_role.user_id and user_role.role_id=role.id and user.username=?" /> <beans:property name="dataSource" ref="dataSource" /> </beans:bean>
说明:
- dataSource就是连接数据库的数据源;
- usersByUsernameQuery就是配置jdbc-user-service时候的users-by-username-query,这个是根据用户名来查询用户的sql语句;
- 同理authoritiesByUsernameQuery就是对应的authorities-by-username-query,这个用来根据用户名查询对应的权限。
<!-- Remember-Me 对应的 Filter -->
<beans:bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<beans:property name="rememberMeServices" ref="rememberMeServices" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
这个过滤器仅仅这样配置是不会起作用的,还要把它加入的Security的FilterChain中去,用<custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>即可,另外这个过滤器要提供一个rememberMeServices和一个用户认证的authenticationManager,后面这个其实就是authentication-manager所配置的东西,而前面这个需要另外配置,配置方式如下:
<!-- RememberMeServices 的实现 -->
<beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
<beans:property name="userDetailsService" ref="userDetailsService" />
<beans:property name="key" value="sunny" />
<!-- 指定 request 中包含的用户是否选择了记住我的参数名 -->
<beans:property name="parameter" value="remember-me" />
<beans:property name="tokenRepository">
<beans:bean class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<beans:property name="dataSource" ref="dataSource"/>
<!-- 是否在启动时创建持久化 token 的数据库表 若为true,但数据有这个表时,会启动失败,提示表已存在 -->
<beans:property name="createTableOnStartup" value="false"/>
</beans:bean>
</beans:property>
</beans:bean>
- 这个配置中的userDetailsService就是上面配置的,直接引用上面的即可;
- key就是token中的key,这个key可以用来方式token被修改,另外这个key要和后面配置的rememberMeAuthenticationProvider的key要一样;
- parameter就是登陆界面的点击记住密码的checkbox的name值,这个一定要一致,要不然没有效果的。
- 初次之外还要配置一个用户记住密码做认证的authenticationManager
<!-- 记住密码 ,key 值需与对应的 RememberMeServices 保持一致 -->
<beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
<beans:property name="key" value="sunny" />
</beans:bean>
同时还要将其添加到authentication-manager标签中去
<!--认证管理-->
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailsService"></authentication-provider>
<!-- 记住密码 -->
<authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider>
</authentication-manager>
最后面将rememberMeServices添加到myUsernamePasswordAuthenticationFilter中去。
<!-- 自定义登录过滤器 -->
<beans:bean id="myUsernamePasswordAuthenticationFilter" class="com.sunny.auth.MyUsernamePasswordAuthenticationFilter">
<beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler" />
<beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler" />
<!-- 记住我 -->
<beans:property name="rememberMeServices" ref="rememberMeServices" />
</beans:bean>
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 不需要访问权限 -->
<http pattern="/page/login.jsp" security="none"></http>
<http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
<!-- <form-login login-page="/page/login.jsp" default-target-url="/page/admin.jsp" authentication-failure-url="/page/login.jsp?error=true" /> -->
<logout invalidate-session="true" logout-success-url="/page/login.jsp" logout-url="/j_spring_security_logout" />
<!-- 自定义登录过滤器 -->
<custom-filter ref="myUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER" />
<!--替换默认REMEMBER_ME_FILTER-->
<custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>
<!-- 通过配置custom-filter来增加过滤器,before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默认的过滤器之前执行。 -->
<custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
</http>
<!-- 登录切入点 -->
<beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/page/login.jsp"/>
</beans:bean>
<!-- 自定义登录过滤器 -->
<beans:bean id="myUsernamePasswordAuthenticationFilter" class="com.sunny.auth.MyUsernamePasswordAuthenticationFilter">
<beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler" />
<beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler" />
<!-- 记住我 -->
<beans:property name="rememberMeServices" ref="rememberMeServices" />
</beans:bean>
<!-- 登录成功 -->
<beans:bean id="loginLogAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/page/admin.jsp" />
</beans:bean>
<!-- 登录失败 -->
<beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/page/login.jsp" />
</beans:bean>
<!-- 认证过滤器 -->
<beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<!-- 用户拥有的权限 -->
<beans:property name="accessDecisionManager" ref="accessDecisionManager" />
<!-- 用户是否拥有所请求资源的权限 -->
<beans:property name="authenticationManager" ref="authenticationManager" />
<!-- 资源与权限对应关系 -->
<beans:property name="securityMetadataSource" ref="securityMetadataSource" />
</beans:bean>
<!-- 授权管理器 -->
<beans:bean id="accessDecisionManager" class="com.sunny.auth.MyAccessDecisionManager">
</beans:bean>
<!--自定义的切入点-->
<beans:bean id="securityMetadataSource" class="com.sunny.auth.MyFilterInvocationSecurityMetadataSource">
<beans:property name="builder" ref="builder"/>
</beans:bean>
<beans:bean id="builder" class="com.sunny.auth.JdbcRequestMapBulider">
<beans:property name="dataSource" ref="dataSource" />
<beans:property name="resourceQuery" value="select re.res_string,r.name from role r,resc re,resc_role rr where r.id=rr.role_id and re.id=rr.resc_id" />
</beans:bean>
<!--认证管理-->
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailsService"></authentication-provider>
<!-- 记住密码 -->
<authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider>
</authentication-manager>
<!-- Remember-Me 对应的 Filter -->
<beans:bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<beans:property name="rememberMeServices" ref="rememberMeServices" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<!-- RememberMeServices 的实现 -->
<beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
<beans:property name="userDetailsService" ref="userDetailsService" />
<beans:property name="key" value="sunny" />
<!-- 指定 request 中包含的用户是否选择了记住我的参数名 -->
<beans:property name="parameter" value="remember-me" />
<beans:property name="tokenRepository">
<beans:bean class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<beans:property name="dataSource" ref="dataSource"/>
<!-- 是否在启动时创建持久化 token 的数据库表 若为true,但数据有这个表时,会启动失败,提示表已存在 -->
<beans:property name="createTableOnStartup" value="false"/>
</beans:bean>
</beans:property>
</beans:bean>
<!-- 记住密码 ,key 值需与对应的 RememberMeServices 保持一致 -->
<beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
<beans:property name="key" value="sunny" />
</beans:bean>
<beans:bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="usersByUsernameQuery" value="select username,password,status as enabled from user where username = ?" />
<beans:property name="authoritiesByUsernameQuery" value="select user.username,role.name from user,role,user_role where user.id=user_role.user_id and user_role.role_id=role.id and user.username=?" />
<beans:property name="dataSource" ref="dataSource" />
</beans:bean>
</beans:beans>
spring-dataSource.xml保持不变
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 数据源 -->
<beans:bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 此为c3p0在spring中直接配置datasource c3p0是一个开源的JDBC连接池 -->
<beans:property name="driverClass" value="com.mysql.jdbc.Driver" />
<beans:property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springsecurity?useUnicode=true&characterEncoding=UTF-8" />
<beans:property name="user" value="root" />
<beans:property name="password" value="" />
<beans:property name="maxPoolSize" value="50"></beans:property>
<beans:property name="minPoolSize" value="10"></beans:property>
<beans:property name="initialPoolSize" value="10"></beans:property>
<beans:property name="maxIdleTime" value="25000"></beans:property>
<beans:property name="acquireIncrement" value="1"></beans:property>
<beans:property name="acquireRetryAttempts" value="30"></beans:property>
<beans:property name="acquireRetryDelay" value="1000"></beans:property>
<beans:property name="testConnectionOnCheckin" value="true"></beans:property>
<beans:property name="idleConnectionTestPeriod" value="18000"></beans:property>
<beans:property name="checkoutTimeout" value="5000"></beans:property>
<beans:property name="automaticTestTable" value="t_c3p0"></beans:property>
</beans:bean>
</beans:beans>
三、基于持久化的方式配置
在将这配置之前先对上面rememberService中的实现类TokenBasedRememberMeServices进行简单的讲解,该类主要是基于简单加密 token 的一个实现类。
- TokenBasedRememberMeServices 会在用户选择了记住我成功登录后,生成一个包含 token 信息的 cookie 发送到客户端;
- 如果用户登录失败则会删除客户端保存的实现 Remember-Me 的 cookie。
- 需要自动登录时,它会判断 cookie 中所包含的关于 Remember-Me 的信息是否与系统一致,一致则返回一个 RememberMeAuthenticationToken 供 RememberMeAuthenticationProvider 处理,不一致则会删除客户端的 Remember-Me cookie。
- TokenBasedRememberMeServices 还实现了 Spring Security 的 LogoutHandler 接口,所以它可以在用户退出登录时立即清除 Remember-Me cookie。
而基础持久化方式配置的实质就是这个类不同,基于持久化方式配置的所用的实现类为:PersistentTokenBasedRememberMeServices,一看名字就知道其作用,就是将token进行持久化保存起来,要保存数据相应的就是为其制定保存的地方,这个保存的地方就是用PersistentTokenRepository来指定的,Spring Security 对此有两种实现,InMemoryTokenRepositoryImpl 和 JdbcTokenRepositoryImpl。前者是将 token 存放在内存中的,通常用于测试,而后者是将 token 存放在数据库中。PersistentTokenBasedRememberMeServices 默认使用的是前者,我们可以通过其 tokenRepository 属性来指定使用的 PersistentTokenRepository。这例子用JdbcTokenRepositoryImpl来进行持久化保存,显然要往数据库保存数据,肯定要有一张表,这个表security也有提供并且是固定的,sql语句为:
) ) ) not null, last_used timestamp not null)
在数据库中创建该表即可。
创建后的表为:

四、两种配置的效果
没勾选【记住我】时候,登陆后再退出,再访问/page/admin.jsp的时候就要求重新登陆。同时数据库也不会新增数据。
当勾选【记住我】在登陆后,就算退出登陆后,再访问/page/admin.jsp也可以不用直接访问,同时,数据库也会将登陆信息保存起来,比较两次数据还可以发现,除了用户名没变,其他的数据都会因为第二次访问而进行更新
【Spring Security】七、RememberMe配置的更多相关文章
- spring boot rest 接口集成 spring security(2) - JWT配置
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
- CAS Spring Security 3 整合配置(转)
一般来说, Web 应用的安全性包括用户认证( Authentication )和用户授权( Authorization )两个部分.用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否 ...
- Spring Security基于Java配置
Maven依赖 <dependencies> <!-- ... other dependency elements ... --> <dependency> < ...
- Spring Security(三) —— 核心配置解读
摘要: 原创出处 https://www.cnkirito.moe/spring-security-3/ 「老徐」欢迎转载,保留摘要,谢谢! 3 核心配置解读 上一篇文章<Spring Secu ...
- spring security实现动态配置url权限的两种方法
缘起 标准的RABC, 权限需要支持动态配置,spring security默认是在代码里约定好权限,真实的业务场景通常需要可以支持动态配置角色访问权限,即在运行时去配置url对应的访问角色. 基于s ...
- spring security 3.2 配置详解(结合数据库)
没事就来了解下spring security.网上找了很多资料.有过时的,也有不是很全面的.各种问题也算是让我碰了个遍.这样吧.我先把整个流程写下来,之后在各个易混点分析吧. 1.建立几个必要的页面. ...
- 【JavaEE】SSH+Spring Security基础上配置AOP+log4j
Spring Oauth2大多数情况下还是用不到的,主要使用的还是Spring+SpringMVC+Hibernate,有时候加上SpringSecurity,因此,本文及以后的文章的example中 ...
- Spring Security Oauth2 的配置
使用oauth2保护你的应用,可以分为简易的分为三个步骤 配置资源服务器 配置认证服务器 配置spring security 前两点是oauth2的主体内容,但前面我已经描述过了,spring sec ...
- Spring Security之动态配置资源权限
在Spring Security中实现通过数据库动态配置url资源权限,需要通过配置验证过滤器来实现资源权限的加载.验证.系统启动时,到数据库加载系统资源权限列表,当有请求访问时,通过对比系统资源权限 ...
- spring-security-4 (2)spring security 基于Java配置的搭建
一.spring security的模块 搭建spring security首先我们要导入必须的jar,即maven的依赖.spring security按模块划分,一个模块对应一个jar. spri ...
随机推荐
- 华为手机安装 charles 证书
- 复习loadRunner参数化
参数化: 为什么要用参数化? 如果是单一数据,那么会纯测试缓存. 如果是参数化,基本上大部分数据不会被缓存命中. 极端情况:所有的数据都不会被缓存命中,或者少量命中. 在loadrunner中,所有的 ...
- android安全检测工具,梆梆安全 - 防止反编译|APP安全加固|应用加固|盗版监测
android安全检测工具,梆梆安全 - 防止反编译|APP安全加固|应用加固|盗版监测https://dev.bangcle.com/ 业内专业的应用加固服务供应商 帮助数十万APP抵御破解风险,早 ...
- go语言,golang学习笔记2 web框架选择
go语言,golang学习笔记2 web框架选择 用什么go web框架比较好呢?能不能推荐个中文资料多的web框架呢? beego框架用的人最多,中文资料最多 首页 - beego: 简约 & ...
- 如何用nginx在本地把9000端口转发到80端口上
起因看到一个用java写的轻博客,于是就兴致冲冲的试用一下.由于是lnmp的环境,Nginx占用了80端口,新博客只能用其他的端口,这里选择了9000端口,本地测试没问题.总不能访问了域名然后在加上端 ...
- Deploying docker registry v2
生成证书 openssl genrsa -out mydomain.key 2048 生成秘钥 openssl req -newkey rsa:4096 -nodes -sha256 -keyout ...
- zabbix agent配置详解(windows)
客户端操作 标注:监控zabbix_agentd客户端安装对象是win server 2008操作系统 64位. 1. 下载zabbix_agentd监控客户端软件安装包(windows操作系统客 ...
- Web开发相关笔记 #04# WebSocket
本文的主要内容: HTTP VS. WebSocket WebSocket 的客户端实现(JavaScript) WebSocket 的服务端实现(Java & apache WebSocke ...
- selenium使用location定位元素坐标偏差
python+selenium+Chromedriver使用location定位元素坐标偏差使用xpath定位元素,用.location获取坐标值,截取网页截图的一部分出现偏差. 之所以会出现这个坐标 ...
- 新增async
import asyncio from threading import Thread import time now = lambda: time.time() print('start',now( ...