1. 首页
  2. IT资讯

SpringSecurity记住我功能如何实现?含源码分析

“u003Cdivu003Eu003Cpu003E u003Cstrongu003Espring security提供了”记住我”的功能,来完成用户下次访问时自动登录功能,而无需再次输入用户名密码。u003Cu002Fstrongu003E 下面,我们来通过代码演示该功能的实现——主要是通过配置remember-me标签。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F6734d592626642e8b8e33b7b6478a7e4″ img_width=”548″ img_height=”362″ alt=”SpringSecurity记住我功能如何实现?含源码分析” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E 我们通过如下的配置过程来实现“记住我”的功能:u003Cu002Fpu003Eu003Cpu003E u003Cstrongu003E1、搭建maven项目(web工程),引入功能的相关依赖 u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E 2、配置web.xml中的spring、springsecurity的加载、springmvc的DispatcherServletu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E 3、配置spring、springmvc、springsecurity的配置文件u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E 4、提供UserDetailService接口的实现类,采用写死数据完成认证用户的封装u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E 5、controller代码提供查询返回当前登录人姓名的功能u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E 6、准备登录页面login.html以及登录成功页面index.html和登录失败页面failer.htmlu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E 7、测试“记住我”u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E1、搭建maven项目(web工程),引入功能的相关依赖u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E maven工程打包方式选择war,同时需要在项目pom做如下配置(只提供主要的依赖和插件,其他的可以参见附件代码):u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E<dependencies>u003Cu002Fpu003Eu003Cpu003E <dependency>u003Cu002Fpu003Eu003Cpu003E <groupId>org.springframework.security<u002FgroupId>u003Cu002Fpu003Eu003Cpu003E <artifactId>spring-security-web<u002FartifactId>u003Cu002Fpu003Eu003Cpu003E <version>5.0.1.RELEASE<u002Fversion>u003Cu002Fpu003Eu003Cpu003E <u002Fdependency>u003Cu002Fpu003Eu003Cpu003E <dependency>u003Cu002Fpu003Eu003Cpu003E <groupId>org.springframework.security<u002FgroupId>u003Cu002Fpu003Eu003Cpu003E <artifactId>spring-security-config<u002FartifactId>u003Cu002Fpu003Eu003Cpu003E <version>5.0.1.RELEASE<u002Fversion>u003Cu002Fpu003Eu003Cpu003E <u002Fdependency>u003Cu002Fpu003Eu003Cpu003E <dependency>u003Cu002Fpu003Eu003Cpu003E <groupId>org.springframework<u002FgroupId>u003Cu002Fpu003Eu003Cpu003E <artifactId>spring-webmvc<u002FartifactId>u003Cu002Fpu003Eu003Cpu003E <version>5.0.3.RELEASE<u002Fversion>u003Cu002Fpu003Eu003Cpu003E <u002Fdependency>u003Cu002Fpu003Eu003Cpu003E <dependency>u003Cu002Fpu003Eu003Cpu003E <groupId>javax.servlet<u002FgroupId>u003Cu002Fpu003Eu003Cpu003E <artifactId>servlet-api<u002FartifactId>u003Cu002Fpu003Eu003Cpu003E <version>2.5<u002Fversion>u003Cu002Fpu003Eu003Cpu003E <scope>provided<u002Fscope>u003Cu002Fpu003Eu003Cpu003E <u002Fdependency>u003Cu002Fpu003Eu003Cpu003E <dependency>u003Cu002Fpu003Eu003Cpu003E <groupId>org.springframework<u002FgroupId>u003Cu002Fpu003Eu003Cpu003E <artifactId>spring-context<u002FartifactId>u003Cu002Fpu003Eu003Cpu003E <version>5.0.3.RELEASE<u002Fversion>u003Cu002Fpu003Eu003Cpu003E <scope>compile<u002Fscope>u003Cu002Fpu003Eu003Cpu003E <u002Fdependency>u003Cu002Fpu003Eu003Cpu003E <dependency>u003Cu002Fpu003Eu003Cpu003E <groupId>org.aspectj<u002FgroupId>u003Cu002Fpu003Eu003Cpu003E <artifactId>aspectjweaver<u002FartifactId>u003Cu002Fpu003Eu003Cpu003E <version>1.6.8<u002Fversion>u003Cu002Fpu003Eu003Cpu003E <u002Fdependency>u003Cu002Fpu003Eu003Cpu003E <u002Fdependencies>u003Cu002Fpu003Eu003Cpu003E <build>u003Cu002Fpu003Eu003Cpu003E <plugins>u003Cu002Fpu003Eu003Cpu003E <plugin>u003Cu002Fpu003Eu003Cpu003E <groupId>org.apache.tomcat.maven<u002FgroupId>u003Cu002Fpu003Eu003Cpu003E <artifactId>tomcat7-maven-plugin<u002FartifactId>u003Cu002Fpu003Eu003Cpu003E <configuration>u003Cu002Fpu003Eu003Cpu003E <port>80<u002Fport>u003Cu002Fpu003Eu003Cpu003E <u002Fconfiguration>u003Cu002Fpu003Eu003Cpu003E <version>2.2<u002Fversion>u003Cu002Fpu003Eu003Cpu003E <u002Fplugin>u003Cu002Fpu003Eu003Cpu003E <u002Fplugins>u003Cu002Fpu003Eu003Cpu003E <u002Fbuild>u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E2、配置web.xml中的spring、springsecurity的加载、springmvc的DispatcherServletu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E<!– 前端控制器(加载classpath:springmvc.xml 服务器启动创建servlet) –>u003Cu002Fpu003Eu003Cpu003E <servlet>u003Cu002Fpu003Eu003Cpu003E <servlet-name>dispatcherServlet<u002Fservlet-name>u003Cu002Fpu003Eu003Cpu003E <servlet-class>org.springframework.web.servlet.DispatcherServlet<u002Fservlet-class>u003Cu002Fpu003Eu003Cpu003E <!– 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 –>u003Cu002Fpu003Eu003Cpu003E <init-param>u003Cu002Fpu003Eu003Cpu003E <param-name>contextConfigLocation<u002Fparam-name>u003Cu002Fpu003Eu003Cpu003E <param-value>classpath:spring-mvc.xml<u002Fparam-value>u003Cu002Fpu003Eu003Cpu003E <u002Finit-param>u003Cu002Fpu003Eu003Cpu003E <!– 服务器启动的时候,让DispatcherServlet对象创建 –>u003Cu002Fpu003Eu003Cpu003E <load-on-startup>1<u002Fload-on-startup>u003Cu002Fpu003Eu003Cpu003E <u002Fservlet>u003Cu002Fpu003Eu003Cpu003E <servlet-mapping>u003Cu002Fpu003Eu003Cpu003E <servlet-name>dispatcherServlet<u002Fservlet-name>u003Cu002Fpu003Eu003Cpu003E <url-pattern>u002F<u002Furl-pattern>u003Cu002Fpu003Eu003Cpu003E <u002Fservlet-mapping>u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003E还需要在web.xml中配置一个filter——springsecurity安全框架好,这个filter离不了!u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E<filter>u003Cu002Fpu003Eu003Cpu003E <filter-name>springSecurityFilterChain<u002Ffilter-name>u003Cu002Fpu003Eu003Cpu003E <filter-class>org.springframework.web.filter.DelegatingFilterProxy<u002Ffilter-class>u003Cu002Fpu003Eu003Cpu003E <u002Ffilter>u003Cu002Fpu003Eu003Cpu003E <filter-mapping>u003Cu002Fpu003Eu003Cpu003E <filter-name>springSecurityFilterChain<u002Ffilter-name>u003Cu002Fpu003Eu003Cpu003E <url-pattern>u002F*<u002Furl-pattern>u003Cu002Fpu003Eu003Cpu003E <u002Ffilter-mapping>u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E3、配置spring、springmvc、springsecurity的配置文件u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E ——此处配置省略spring、springmvc的内容,可以参见附件,重点说明springsecurity配置文件内容u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E<?xml version=”1.0″ encoding=”UTF-8″?>u003Cu002Fpu003Eu003Cpu003E<beans xmlns=”http:u002Fu002Fwww.springframework.orgu002Fschemau002Fbeans”u003Cu002Fpu003Eu003Cpu003E xmlns:security=”http:u002Fu002Fwww.springframework.orgu002Fschemau002Fsecurity”u003Cu002Fpu003Eu003Cpu003E xmlns:xsi=”http:u002Fu002Fwww.w3.orgu002F2001u002FXMLSchema-instance”u003Cu002Fpu003Eu003Cpu003E xsi:schemaLocation=”http:u002Fu002Fwww.springframework.orgu002Fschemau002Fbeansu003Cu002Fpu003Eu003Cpu003E [url=http:u002Fu002Fwww.springframework.orgu002Fschemau002Fbeansu002Fspring-beans.xsd]http:u002Fu002Fwww.springframework.orgu002Fschemau002Fbeansu002Fspring-beans.xsd[u002Furl]u003Cu002Fpu003Eu003Cpu003E [url=http:u002Fu002Fwww.springframework.orgu002Fschemau002Fsecurity]http:u002Fu002Fwww.springframework.orgu002Fschemau002Fsecurity[u002Furl]u003Cu002Fpu003Eu003Cpu003E [url=http:u002Fu002Fwww.springframework.orgu002Fschemau002Fsecurityu002Fspring-security.xsd]http:u002Fu002Fwww.springframework.orgu002Fs … spring-security.xsd[u002Furl]”>u003Cu002Fpu003Eu003Cpu003E <!– 配置不拦截的资源 –>u003Cu002Fpu003Eu003Cpu003E <security:http pattern=”u002Flogin.html” security=”none”u002F>u003Cu002Fpu003Eu003Cpu003E <security:http pattern=”u002Ffailer.html” security=”none”u002F>u003Cu002Fpu003Eu003Cpu003E <security:http auto-config=”true” use-expressions=”false”>u003Cu002Fpu003Eu003Cpu003E <!– 配置具体的拦截的规则 pattern=”请求路径的规则” access=”访问系统的人,必须有ROLE_ADMIN的角色” –>u003Cu002Fpu003Eu003Cpu003E <security:intercept-url pattern=”u002F**” access=”ROLE_ADMIN”u002F>u003Cu002Fpu003Eu003Cpu003E <!– 定义跳转的具体的页面 –>u003Cu002Fpu003Eu003Cpu003E <security:form-loginu003Cu002Fpu003Eu003Cpu003E login-page=”u002Flogin.html”u003Cu002Fpu003Eu003Cpu003E login-processing-url=”u002Flogin”u003Cu002Fpu003Eu003Cpu003E default-target-url=”u002Findex.html”u003Cu002Fpu003Eu003Cpu003E authentication-failure-url=”u002Ffailer.html”u003Cu002Fpu003Eu003Cpu003E always-use-default-target=”true”u003Cu002Fpu003Eu003Cpu003E u002F>u003Cu002Fpu003Eu003Cpu003E <!–关键点:remember-me配置后,启用RememberMeAuthenticationFilter过滤功能;u003Cu002Fpu003Eu003Cpu003E 首次登陆时,如果remember-me标记传入,则会将认证成功的信息(用户名、有效期、密码、安全框架提供的key)加密后回写到cookie;u003Cu002Fpu003Eu003Cpu003E 下次登陆时,请求中会将cookie带过去RememberMeAuthenticationFilter将其解析后,得到其中的用户名,查询用户信息后,再次将查询到的用户信息加密,将加密后的字符串对比cookie中的字符串u003Cu002Fpu003Eu003Cpu003E 如果一致,设置认证成功;如果不一致,认证失败,抛出异常–>u003Cu002Fpu003Eu003Cpu003E <security:remember-me remember-me-parameter=”remember-me” user-service-ref=”userService”u002F>u003Cu002Fpu003Eu003Cpu003E <!– 关闭跨域请求 –>u003Cu002Fpu003Eu003Cpu003E <security:csrf disabled=”true”u002F>u003Cu002Fpu003Eu003Cpu003E <!– 配置退出 –>u003Cu002Fpu003Eu003Cpu003E <security:logout invalidate-session=”true” logout-url=”u002Flogout” logout-success-url=”u002Flogin.html”u002F>u003Cu002Fpu003Eu003Cpu003E <u002Fsecurity:http>u003Cu002Fpu003Eu003Cpu003E <!–为认证管理器配置认证提供者:自定义userDetailService–>u003Cu002Fpu003Eu003Cpu003E <security:authentication-manager>u003Cu002Fpu003Eu003Cpu003E <security:authentication-provider user-service-ref=”userDetailService”>u003Cu002Fpu003Eu003Cpu003E <u002Fsecurity:authentication-provider>u003Cu002Fpu003Eu003Cpu003E <u002Fsecurity:authentication-manager>u003Cu002Fpu003Eu003Cpu003E <bean id=”userService” class=”com.itheima.security.service.UserDetailServiceImpl”u002F>u003Cu002Fpu003Eu003Cpu003E<u002Fbeans>u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E4、提供UserDetailService接口的实现类,采用写死数据完成认证用户的封装u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E 在上述配置文件中,有一个bean为UserDetailServiceImpl,要想完成自定义的用户认证功能,则需要让该类实现UserDetailService接口u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E@Service(“userDetailService”)u003Cu002Fpu003Eu003Cpu003Epublic class UserDetailServiceImpl implements UserDetailsService {u003Cu002Fpu003Eu003Cpu003E public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {u003Cu002Fpu003Eu003Cpu003E u002Fu002F此处写死用户名、密码和权限列表u003Cu002Fpu003Eu003Cpu003E DBUser dbUser = getDBUserByUsername(username);u003Cu002Fpu003Eu003Cpu003E u002Fu002F封装用户权限列表:写死为拥有ROLE_ADMIN角色u003Cu002Fpu003Eu003Cpu003E List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();u003Cu002Fpu003Eu003Cpu003E authorities.add(new SimpleGrantedAuthority(“ROLE_” + dbUser.getRole()));u003Cu002Fpu003Eu003Cpu003E u002Fu002F将封装好的User返回u003Cu002Fpu003Eu003Cpu003E return new User(dbUser.getUsername(),”{noop}”+dbUser.getPassword(),authorities);u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E u002F**u003Cu002Fpu003Eu003Cpu003E * 模拟根据用户名查询数据库中的用户数据——此处假设输入的用户名永远正确,密码都是123456u003Cu002Fpu003Eu003Cpu003E * @param usernameu003Cu002Fpu003Eu003Cpu003E * @returnu003Cu002Fpu003Eu003Cpu003E *u002Fu003Cu002Fpu003Eu003Cpu003E private DBUser getDBUserByUsername(String username){u003Cu002Fpu003Eu003Cpu003E return new DBUser(username,”123456″,”ADMIN”);u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E5、controller层提供类,提供查询返回当前登录人姓名的方法u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E@RestControlleru003Cu002Fpu003Eu003Cpu003Epublic class SecurityController {u003Cu002Fpu003Eu003Cpu003E @RequestMapping(“getUsername”)u003Cu002Fpu003Eu003Cpu003E public String getUsername(){u003Cu002Fpu003Eu003Cpu003E u002Fu002F得到当前认证对象的用户名并返回u003Cu002Fpu003Eu003Cpu003E return SecurityContextHolder.getContext().getAuthentication().getName();u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E6、准备登录页面login.html以及登录成功页面index.html和登录失败页面failer.htmlu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E u003Cstrongu003E 6.1 login.html:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E<!DOCTYPE html>u003Cu002Fpu003Eu003Cpu003E<html lang=”en”>u003Cu002Fpu003Eu003Cpu003E<head>u003Cu002Fpu003Eu003Cpu003E <meta charset=”UTF-8″>u003Cu002Fpu003Eu003Cpu003E <title>登录页面<u002Ftitle>u003Cu002Fpu003Eu003Cpu003E<u002Fhead>u003Cu002Fpu003Eu003Cpu003E<body>u003Cu002Fpu003Eu003Cpu003E <!–注意:action=”login” method为post–>u003Cu002Fpu003Eu003Cpu003E <form action=”login” method=”post”>u003Cu002Fpu003Eu003Cpu003E <!–注意:name=”username”–>u003Cu002Fpu003Eu003Cpu003E 用户名:<input name=”username” type=”text”u002F><br>u003Cu002Fpu003Eu003Cpu003E <!–注意:name=”password”–>u003Cu002Fpu003Eu003Cpu003E 密 码:<input name=”password” type=”password”u002F>u003Cu002Fpu003Eu003Cpu003E <hr>u003Cu002Fpu003Eu003Cpu003E <!–注意:name=”remember-me” 与后台remember-me标签配置的remember-me-parameter一致–>u003Cu002Fpu003Eu003Cpu003E <input type=”checkbox” name=”remember-me” value=”true”u002F>u003Cu002Fpu003Eu003Cpu003E <input type=”submit” value=”登录”u002F>u003Cu002Fpu003Eu003Cpu003E <u002Fform>u003Cu002Fpu003Eu003Cpu003E<u002Fbody>u003Cu002Fpu003Eu003Cpu003E<u002Fhtml>u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E6.2 index.html:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E<!DOCTYPE html>u003Cu002Fpu003Eu003Cpu003E<html lang=”en”>u003Cu002Fpu003Eu003Cpu003E<head>u003Cu002Fpu003Eu003Cpu003E <meta charset=”UTF-8″>u003Cu002Fpu003Eu003Cpu003E <title>欢迎页面<u002Ftitle>u003Cu002Fpu003Eu003Cpu003E <script src=”jquery-2.2.3.min.js”><u002Fscript>u003Cu002Fpu003Eu003Cpu003E<u002Fhead>u003Cu002Fpu003Eu003Cpu003E<body>u003Cu002Fpu003Eu003Cpu003E <span id=”nameId”><u002Fspan>济南欢迎您!u003Cu002Fpu003Eu003Cpu003E <a href=”logout”>退出<u002Fa>u003Cu002Fpu003Eu003Cpu003E <script>u003Cu002Fpu003Eu003Cpu003E $(function(){u003Cu002Fpu003Eu003Cpu003E u002Fu002F发送异步请求,获取当前登录用户名u003Cu002Fpu003Eu003Cpu003E $.ajax({u003Cu002Fpu003Eu003Cpu003E url:’getUsername’,u003Cu002Fpu003Eu003Cpu003E type:”post”,u003Cu002Fpu003Eu003Cpu003E success:function(data){u003Cu002Fpu003Eu003Cpu003E $(“#nameId”).html(data);u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E })u003Cu002Fpu003Eu003Cpu003E })u003Cu002Fpu003Eu003Cpu003E <u002Fscript>u003Cu002Fpu003Eu003Cpu003E<u002Fbody>u003Cu002Fpu003Eu003Cpu003E<u002Fhtml>u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E6.3 failer.htmlu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003E<!DOCTYPE html>u003Cu002Fpu003Eu003Cpu003E<html lang=”en”>u003Cu002Fpu003Eu003Cpu003E<head>u003Cu002Fpu003Eu003Cpu003E <meta charset=”UTF-8″>u003Cu002Fpu003Eu003Cpu003E <title>失败页面<u002Ftitle>u003Cu002Fpu003Eu003Cpu003E<u002Fhead>u003Cu002Fpu003Eu003Cpu003E<body>u003Cu002Fpu003Eu003Cpu003E 欢迎下次再来济南!u003Cu002Fpu003Eu003Cpu003E<u002Fbody>u003Cu002Fpu003Eu003Cpu003E<u002Fhtml>u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E7、测试“记住我”u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E ——第一次访问首页(index.html),会跳转到登录页面,在登录页面记得勾选那个复选框(remember-me)u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F9b0cfede3f9b4e6e9452aabddce61c12″ img_width=”804″ img_height=”209″ alt=”SpringSecurity记住我功能如何实现?含源码分析” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E ——将浏览器直接关闭,再打开后访问首页(index.html),,会发现,本次访问无需登录即可访问u003Cu002Fpu003Eu003Cpu003E==================================================================================u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E下面,我们来简单了解一下在配置文件中添加remember-me元素之后,springsecurity框架做的事情: u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E 1.在首次登录时,会经过remember-me对应的过滤器u003Cu002Fstrongu003ERememberMeAuthenticationFilter,u003Cstrongu003E在调用到userDetailService获取用户数据并认证成功之后,会经过TokenBasedRememberMeServices的onLoginSuccess方法u003Cu002Fstrongu003E,如下u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003Epublic void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {u003Cu002Fpu003Eu003Cpu003E String username = this.retrieveUserName(successfulAuthentication);u003Cu002Fpu003Eu003Cpu003E String password = this.retrievePassword(successfulAuthentication);u003Cu002Fpu003Eu003Cpu003E if (!StringUtils.hasLength(username)) {u003Cu002Fpu003Eu003Cpu003E this.logger.debug(“Unable to retrieve username”);u003Cu002Fpu003Eu003Cpu003E } else {u003Cu002Fpu003Eu003Cpu003E if (!StringUtils.hasLength(password)) {u003Cu002Fpu003Eu003Cpu003E u002Fu002F调用自定义的userDetailService,得到返回的UserDetail对象u003Cu002Fpu003Eu003Cpu003E UserDetails user = this.getUserDetailsService().loadUserByUsername(username);u003Cu002Fpu003Eu003Cpu003E password = user.getPassword();u003Cu002Fpu003Eu003Cpu003E if (!StringUtils.hasLength(password)) {u003Cu002Fpu003Eu003Cpu003E this.logger.debug(“Unable to obtain password for user: ” + username);u003Cu002Fpu003Eu003Cpu003E return;u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E int tokenLifetime = this.calculateLoginLifetime(request, successfulAuthentication);u003Cu002Fpu003Eu003Cpu003E long expiryTime = System.currentTimeMillis();u003Cu002Fpu003Eu003Cpu003E expiryTime += 1000L * (long)(tokenLifetime < 0 ? 1209600 : tokenLifetime);u003Cu002Fpu003Eu003Cpu003E u002Fu002F将时长、用户名、密码通过md5加密,得到签名字符串u003Cu002Fpu003Eu003Cpu003E String signatureValue = this.makeTokenSignature(expiryTime, username, password);u003Cu002Fpu003Eu003Cpu003E u002Fu002F新建cookie并将其写入到response中u003Cu002Fpu003Eu003Cpu003E this.setCookie(new String[]{username, Long.toString(expiryTime), signatureValue}, tokenLifetime, request, response);u003Cu002Fpu003Eu003Cpu003E if (this.logger.isDebugEnabled()) {u003Cu002Fpu003Eu003Cpu003E this.logger.debug(“Added remember-me cookie for user ‘” + username + “‘, expiry: ‘” + new Date(expiryTime) + “‘”);u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003E本次记录的签名值截图如下:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F3497fba0810e4e3f9d51c2858f889856″ img_width=”1096″ img_height=”146″ alt=”SpringSecurity记住我功能如何实现?含源码分析” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003Eu003Cstrongu003Ethis.setCookie方法会进一步将编码形成cookieValue:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003Eprotected void setCookie(String[] tokens, int maxAge, HttpServletRequest request, HttpServletResponse response) {u003Cu002Fpu003Eu003Cpu003E u002Fu002F将用户名、密码、签名组成的tokens编码后,得到cookieValueu003Cu002Fpu003Eu003Cpu003E String cookieValue = this.encodeCookie(tokens);u003Cu002Fpu003Eu003Cpu003E Cookie cookie = new Cookie(this.cookieName, cookieValue);u003Cu002Fpu003Eu003Cpu003E cookie.setMaxAge(maxAge);u003Cu002Fpu003Eu003Cpu003E cookie.setPath(this.getCookiePath(request));u003Cu002Fpu003Eu003Cpu003E u002Fu002F…省略部分代码u003Cu002Fpu003Eu003Cpu003E response.addCookie(cookie);u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003E保存的cookie值如下:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F0ed364d1c2bd41ca94db5dd7d0d55497″ img_width=”1065″ img_height=”277″ alt=”SpringSecurity记住我功能如何实现?含源码分析” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E也可以在浏览器抓包中看到:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002Fc1c4bfd2de2547cf89bf089dfbf0ad7d” img_width=”650″ img_height=”235″ alt=”SpringSecurity记住我功能如何实现?含源码分析” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E u003Cstrongu003E2.关闭浏览器再次打开时u003Cu002Fstrongu003E,u003Cstrongu003E该cookie在存活时间内,会跟着请求带到后台,再次经过RememberMeAuthenticationFilter过滤器,执行doFilter方法u003Cu002Fstrongu003E:u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003Epublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {u003Cu002Fpu003Eu003Cpu003E HttpServletRequest request = (HttpServletRequest)req;u003Cu002Fpu003Eu003Cpu003E HttpServletResponse response = (HttpServletResponse)res;u003Cu002Fpu003Eu003Cpu003E if (SecurityContextHolder.getContext().getAuthentication() == null) {u003Cu002Fpu003Eu003Cpu003E u002Fu002FautoLogin方法来读取cookie,校验cookie中保存的数据和数据库中查询的是否一致u003Cu002Fpu003Eu003Cpu003E Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);u003Cu002Fpu003Eu003Cpu003E u002Fu002F省略部分代码u003Cu002Fpu003Eu003Cpu003E chain.doFilter(request, response);u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003Eu003Cstrongu003E在autoLogin方法中进行校验:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003Epublic final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {u003Cu002Fpu003Eu003Cpu003E u002Fu002F获取cookieValueu003Cu002Fpu003Eu003Cpu003E String rememberMeCookie = this.extractRememberMeCookie(request);u003Cu002Fpu003Eu003Cpu003E u002Fu002F省略很多代码u003Cu002Fpu003Eu003Cpu003E u002Fu002F将cookieValue进行解析,得到其中的用户名、有效期以及签名u003Cu002Fpu003Eu003Cpu003E String[] cookieTokens = this.decodeCookie(rememberMeCookie);u003Cu002Fpu003Eu003Cpu003E u002Fu002F校验cookieValue中的签名u003Cu002Fpu003Eu003Cpu003E user = this.processAutoLoginCookie(cookieTokens, request, response);u003Cu002Fpu003Eu003Cpu003E this.userDetailsChecker.check(user);u003Cu002Fpu003Eu003Cpu003E this.logger.debug(“Remember-me cookie accepted”);u003Cu002Fpu003Eu003Cpu003E u002Fu002F经过验证后,将用户写如认证主体中u003Cu002Fpu003Eu003Cpu003E return this.createSuccessfulAuthentication(request, user);u003Cu002Fpu003Eu003Cpu003E u002Fu002F省略很多代码u003Cu002Fpu003Eu003Cpu003E this.cancelCookie(request, response);u003Cu002Fpu003Eu003Cpu003E return null;u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003E解析cookieValue之后的cookieTokens内容如下:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F640ea4203a964d88ae7daeb644070cbc” img_width=”732″ img_height=”238″ alt=”SpringSecurity记住我功能如何实现?含源码分析” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003Eu003Cstrongu003E进一步的验证在processAutoLoginCookie方法中进行:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003Eprotected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {u003Cu002Fpu003Eu003Cpu003E u002Fu002F省略部分代码u003Cu002Fpu003Eu003Cpu003E u002Fu002F根据解析出来的用户名查询数据库用户u003Cu002Fpu003Eu003Cpu003E UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(cookieTokens[0]);u003Cu002Fpu003Eu003Cpu003E u002Fu002F将有效期以及查询出的数据库用户用户名、密码再次通过MD5加密,得到新的一个签名(expectedTokenSignature)u003Cu002Fpu003Eu003Cpu003E String expectedTokenSignature = this.makeTokenSignature(tokenExpiryTime, userDetails.getUsername(), userDetails.getPassword());u003Cu002Fpu003Eu003Cpu003E u002Fu002F判断新的签名和通过cookieVlaue解析出来的签名是否一致u003Cu002Fpu003Eu003Cpu003E if (!equals(expectedTokenSignature, cookieTokens[2])) {u003Cu002Fpu003Eu003Cpu003E throw new InvalidCookieException(“Cookie token[2] contained signature ‘” + cookieTokens[2] + “‘ but expected ‘” + expectedTokenSignature + “‘”);u003Cu002Fpu003Eu003Cpu003E } else {u003Cu002Fpu003Eu003Cpu003E u002Fu002F如果一致,返回该用户u003Cu002Fpu003Eu003Cpu003E return userDetails;u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cpu003E }u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003E经过加密之后新的签名值截图如下:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F8856788ea2634f5496ed25564608a420″ img_width=”839″ img_height=”263″ alt=”SpringSecurity记住我功能如何实现?含源码分析” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003Eu003Cstrongu003E新的签名值和编码到cookieValue中的签名完全一致!u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E本次记住我功能配置使用的是较简单——仅通过cookie没有通过数据库记录的——功能实现,源码中挑选了其中几个关键点来加以说明“记住我”功能的实现过程,欢迎更深入的交流。u003Cu002Fpu003Eu003Cu002Fdivu003E”

原文始发于:SpringSecurity记住我功能如何实现?含源码分析

主题测试文章,只做测试使用。发布者:玩家L-,转转请注明出处:http://www.cxybcw.com/26862.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code