在以前的教程中一笔带过式的讲了下RememberMe记住密码的功能,那篇的Remember功能是最简易的配置,其功能和安全性都不强。这里就配置下security中RememberMe的各类方式。mysql
RememberMe 是指用户在网站上可以在 Session 之间记住登陆用户的身份的凭证,通俗的来讲就是用户登录成功认证一次以后在制定的必定时间内能够不用再输入用户名和密码进行自动登陆。这个过程当中经过服务端发送一个 cookie 给客户端浏览器保存,下次浏览器再访问服务端时服务端可以自动检测客户端的 cookie,根据 cookie 值触发自动登陆操做。Spring Security中的 Remember-Me 功能一般有两种实现方式。一种是简单的使用加密来保证基于 cookie 的 token 的安全,另外一种是经过数据库或其它持久化存储机制来保存生成的 token。
两种方式的区别:第一中方式不安全,就是说在用户获取到实现记住我功能的 token 后,任何用户均可以在该 token 过时以前经过该 token 进行自动登陆。若是用户发现本身的 token 被盗用了,那么他能够经过改变本身的登陆密码来当即使其全部的记住我 token 失效。若是但愿咱们的应用可以更安全,那就使用第二种方式。第二种方式也是详细要讲解的。web
<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>
下面来配置rememberMe的过滤器:spring
<!-- 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所配置的东西,而前面这个须要另外配置,配置方式以下:sql
<bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices"> <property name="userDetailsService" ref="userDetailsService" /> <property name="key" value="zmc" /> <!-- 指定 request 中包含的用户是否选择了记住个人参数名 --> <property name="parameter" value="rememberMe"/> </bean>
这个配置中的userDetailsService就是上面配置的,直接引用上面的便可;key就是token中的key,这个key能够用来方式token被修改,另外这个key要和后面配置的rememberMeAuthenticationProvider的key要同样;parameter就是登录界面的点击记住密码的checkbox的name值,这个必定要一直,要否则没有效果的。
除此以外还要配置一个用户记住密码作认证的authenticationManager数据库
<bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider"> <property name="key" value="zmc" /> </bean>
同时还要将其添加到authentication-manager标签中去浏览器
<authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsService"> </authentication-provider> <!-- 记住密码 --> <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider> </authentication-manager>
<?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="/login.jsp" security="none"></http> <http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint"> <!-- <form-login login-page="/login.jsp" default-target-url="/index.jsp" authentication-failure-url="/login.jsp?error=true" /> --> <logout invalidate-session="true" logout-success-url="/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="/login.jsp" /> </beans:bean> <!-- 数据源 --> <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/springsecuritydemo?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:bean id="builder" class="com.zmc.demo.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> <beans:bean id="myUsernamePasswordAuthenticationFilter" class="com.zmc.demo.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="targetUrlParameter" value="/index.jsp" /> </beans:bean> <beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <beans:property name="defaultFailureUrl" value="/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> <!-- acl领域模型 --> <beans:bean class="com.zmc.demo.MyAccessDecisionManager" id="accessDecisionManager"> </beans:bean> <!-- --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsService </authentication-provider> <!-- 记住密码 --> <authentication-provider ref="rememberMeAuthenticationProvider"></authentication-provider> </authentication-manager> <!-- 配置userDetailsService --> <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> <!-- 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> <!-- rememberService --> <!-- RememberMeServices 的实现 --> <beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices"> <beans:property name="userDetailsService" ref="userDetailsService" /> <beans:property name="key" value="zmc" /> <!-- 指定 request 中包含的用户是否选择了记住个人参数名 --> <beans:property name="parameter" value="rememberMe" /> </beans:property> </beans:bean> <!-- 记住密码 --> <!-- key 值需与对应的 RememberMeServices 保持一致 --> <beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider"> <beans:property name="key" value="zmc" /> </beans:bean> <beans:bean id="securityMetadataSource" class="com.zmc.demo.MyFilterInvocationSecurityMetadataSource"> <beans:property name="builder" ref="builder"></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。cookie
而基础持久化方式配置的实质就是这个类不一样,基于持久化方式配置的所用的实现类为:PersistentTokenBasedRememberMeServices,一看名字就知道其做用,就是将token进行持久化保存起来,要保存数据相应的就是为其制定保存的地方,这个保存的地方就是用PersistentTokenRepository来指定的,Spring Security 对此有两种实现,InMemoryTokenRepositoryImpl 和 JdbcTokenRepositoryImpl。前者是将 token 存放在内存中的,一般用于测试,然后者是将 token 存放在数据库中。PersistentTokenBasedRememberMeServices 默认使用的是前者,咱们能够经过其 tokenRepository 属性来指定使用的 PersistentTokenRepository。这例子用JdbcTokenRepositoryImpl来进行持久化保存,显然要往数据库保存数据,确定要有一张表,这个表security也有提供,sql语句为:create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)。在数据库中建立该表便可。session
建立后的表为:jsp
因此持久化方式配置只须要将第一种方式的TokenBasedRememberMeServices进行修改就能够,用如下代码提换就能够了:
<beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"> <beans:property name="userDetailsService" ref="userDetailsService" /> <beans:property name="key" value="zmc" /> <!-- 指定 request 中包含的用户是否选择了记住个人参数名 --> <beans:property name="parameter" value="rememberMe" /> <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>
从上面的图能够看出,但没点击2周不用登录的时候,登录后再退出,再访问资源的时候就要求从新登录。同时数据库也不会新增数据。
从上图能够看到,当勾选2周不用登录在登录后,就算退出登录后,再访问资源也能够不用直接访问,同时,数据库也会将登录信息保存起来,比较两次数据还能够发现,除了用户名没变,其余的数据都会由于第二次访问而进行更新。