spring security 学习三-rememberMe

功能:登录时的“记住我”功能

原理:

rememberMeAuthenticationFilter在security过滤器链中的位置,在请求走认证流程是,当前边的filter都不通过时,会走rememberMeAuthenticationFilter

代码:

html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>自定义登录页面</title>
</head>
<body><h2>自定义登录页面</h2><form action="/authentication/form" method="POST"><table><tr><td>用户名:</td><td><input type="text" name="username"></td></tr><tr><td>密码:</td><td><input type="password" name="password"></td></tr><tr><td>图形验证码:</td><td><input type="text" name="imageCode"><img src="/code/image?width=200" alt=""></td></tr><tr><td colspan="2"><!-- 其中的name值 remember-me 是固定不变的,security默认 --><input type="checkbox" name="remember-me" value="true">记住我</td></tr><tr><td colspan="2"><button type="submit">登录</button></td></tr></table></form></body>
</html>

security配置:

  @Autowiredprivate DataSource dataSource;//datasource 用的是springboot默认的application.yml中的配置@Autowiredprivate UserDetailsService userDetailsService;@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//自动创建相关的token表jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}@Overrideprotected void configure(HttpSecurity http) throws Exception {//用户可自定义、亦可以使用mysecurity默认的登录页String loginPage = securityProperties.getBrowser().getLoginPage();http.formLogin().loginPage("/authentication/require").loginProcessingUrl("/authentication/form").successHandler(custAuthenticationSuccessHandler).failureHandler(custAuthenticationFailerHandler).and().rememberMe().tokenRepository(persistentTokenRepository())//用于将token信息存储到数据库中.tokenValiditySeconds(3600).userDetailsService(userDetailsService)//用于登录.and().authorizeRequests().antMatchers("/authentication/require", loginPage, "/code/image").permitAll().anyRequest().authenticated();http.csrf().disable();//暂时设为disable 防止跨站请求伪造的功能}

启动项目:(因配置了dbcTokenRepository.setCreateTableOnStartup(true)  所以会自动创建下表用于存储用户token信息)

页面:点击记住我后登录

登录成功之后会在persistent_login表中插入一条数据:

重启项目,直接访问资源路径:

http://localhost:8080/user/1

不需要跳转到登录页面,直接返回具体信息

源码:

UserNamePasswordAuthenticationFilter.class文件中的attempAuthentication方法

 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {String username = this.obtainUsername(request);String password = this.obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);this.setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}

查看其父类:

AbstractAuthenticationProcessingFilter类,this.attempAuthentication方法使用的是子类方法,也就是usernamepasswordauthenticationFilter类中的方法(上边的)

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;if (!this.requiresAuthentication(request, response)) {chain.doFilter(request, response);} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Request is to process authentication");}Authentication authResult;try {authResult = this.attemptAuthentication(request, response);if (authResult == null) {return;}this.sessionStrategy.onAuthentication(authResult, request, response);} catch (InternalAuthenticationServiceException var8) {this.logger.error("An internal error occurred while trying to authenticate the user.", var8);this.unsuccessfulAuthentication(request, response, var8);return;} catch (AuthenticationException var9) {this.unsuccessfulAuthentication(request, response, var9);return;}if (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}this.successfulAuthentication(request, response, chain, authResult);}}

attempauthentication方法执行完之后,回到successfulAuthentication方法(也是AbstractAuthenticationProcessingFilter类),会有remberMeService类

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {if (this.logger.isDebugEnabled()) {this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);}SecurityContextHolder.getContext().setAuthentication(authResult);this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}this.successHandler.onAuthenticationSuccess(request, response, authResult);}

loginSuccess方法会进入AbstractRememberMeService抽象类中:

    public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {if (!this.rememberMeRequested(request, this.parameter)) {this.logger.debug("Remember-me login not requested.");} else {this.onLoginSuccess(request, response, successfulAuthentication);}}

onLoginSuccess方法会进入PersistentTokenBaseRememberMeService类中(AbstractRememberMEService的子类)

    protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {String username = successfulAuthentication.getName();this.logger.debug("Creating new persistent login for user " + username);PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());try {this.tokenRepository.createNewToken(persistentToken);this.addCookie(persistentToken, request, response);} catch (Exception var7) {this.logger.error("Failed to save persistent token ", var7);}}

这里的这个tokenRepository就是在配置文件中定义的persistentTokenRepository,addCookie方法会将token写到浏览器的cookie中,等下次请求的时候回自动带着token

下一次访问会进入到RememberAuthenticationFilter过滤器中:

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;//判断context中是否已经有了一个认证的Authentication对象if (SecurityContextHolder.getContext().getAuthentication() == null) {//是否可以自动登录Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);if (rememberMeAuth != null) {try {rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);//将一个已经认证的authentication对象放到context中SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);this.onSuccessfulAuthentication(request, response, rememberMeAuth);if (this.logger.isDebugEnabled()) {this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");}if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));}if (this.successHandler != null) {this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);return;}} catch (AuthenticationException var8) {if (this.logger.isDebugEnabled()) {this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);}this.rememberMeServices.loginFail(request, response);this.onUnsuccessfulAuthentication(request, response, var8);}}chain.doFilter(request, response);} else {if (this.logger.isDebugEnabled()) {this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");}chain.doFilter(request, response);}}

autoLogin方法会进入到AbstractRememberMeService抽象类中:

    public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {String rememberMeCookie = this.extractRememberMeCookie(request);if (rememberMeCookie == null) {return null;} else {this.logger.debug("Remember-me cookie detected");if (rememberMeCookie.length() == 0) {this.logger.debug("Cookie was empty");this.cancelCookie(request, response);return null;} else {UserDetails user = null;try {String[] cookieTokens = this.decodeCookie(rememberMeCookie);user = this.processAutoLoginCookie(cookieTokens, request, response);this.userDetailsChecker.check(user);this.logger.debug("Remember-me cookie accepted");return this.createSuccessfulAuthentication(request, user);} catch (CookieTheftException var6) {。。。。、。。。this.cancelCookie(request, response);return null;}}}

processAutoLoginCookie方法会走实现类PersistentTokenBasedRememberMeServices类中的方法:

    protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {if (cookieTokens.length != 2) {throw new InvalidCookieException("Cookie token did not contain 2 tokens, but contained '" + Arrays.asList(cookieTokens) + "'");} else {String presentedSeries = cookieTokens[0];String presentedToken = cookieTokens[1];PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);if (token == null) {throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);} else if (!presentedToken.equals(token.getTokenValue())) {this.tokenRepository.removeUserTokens(token.getUsername());throw new CookieTheftException(this.messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));} else if (token.getDate().getTime() + (long)this.getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {throw new RememberMeAuthenticationException("Remember-me login has expired");} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'");}PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), this.generateTokenData(), new Date());try {this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());this.addCookie(newToken, request, response);} catch (Exception var9) {this.logger.error("Failed to update token: ", var9);throw new RememberMeAuthenticationException("Autologin failed due to data access problem");}return this.getUserDetailsService().loadUserByUsername(token.getUsername());}}}

posted @ 2019-05-04 19:32 巡山小妖N 阅读(...) 评论(...) 编辑 收藏

spring security 学习三-rememberMe相关推荐

  1. spring security 学习二

    spring security 学习二 doc:https://docs.spring.io/spring-security/site/docs/ 基于表单的认证(个性化认证流程): 一.自定义登录页 ...

  2. spring security 学习一

    spring security 学习一 1.配置基本的springboot web项目,加入security5依赖,启动项目 浏览器访问,即可出现一个默认的登录页面 2.什么都没有配置 登录页面哪里来 ...

  3. Spring Security 中取得 RememberMe 的 cookie 值

    为什么80%的码农都做不了架构师?>>>    Spring Security 中的 RememberMe 对应的 cookie 名称是可配置的--相信一般情况下大家也不会使用那个默 ...

  4. Spring security 学习 (自助者,天助之!)

    自己努力,何必要强颜欢笑的求助别人呢?  手心向下不求人! Spring security学习有进展哦: 哈哈! 1.页面都是动态生产的吧! 2.设置权限:  a:pom.xml配置jar包 b:cr ...

  5. SpringBoot整合Spring Security——第三章异常处理

    文章目录 一.常见异常 二.源码分析 三.处理异常 四.拓展spring security authenticationProvider用法及关闭不隐藏UserNotFoundException的解决 ...

  6. 6.Spring security中的rememberMe

    文章目录 *RememberMe* *6.1RememberMe简介* *6.2RememberMe基本用法* *6.3持久化令牌* *6.4二次校验* *6.5原理分析* *`AbstractRem ...

  7. Spring Security学习总结

    2019独角兽企业重金招聘Python工程师标准>>> 1.Spring Security介绍  一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权 ...

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

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

  9. SpringBoot + Spring Security 学习笔记(一)自定义基本使用及个性化登录配置

    官方文档参考,5.1.2 中文参考文档,4.1 中文参考文档,4.1 官方文档中文翻译与源码解读 SpringSecurity 核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) ...

最新文章

  1. 简单的VC 操作XML 文件的的方法
  2. QQ互联OAuth2.0 .NET SDK 发布以及网站QQ登陆示例代码
  3. 2.2.3 动量梯度下降法
  4. Android访问数据库(SQL Server 和 MySQL)
  5. 基于Spring Boot 的统一错误处理
  6. 转:为 setuptools 开路搭桥
  7. [转载]Git安装以及使用Git 管理个人文档
  8. [转载]函数getopt(),及其参数optind
  9. 学习笔记:工厂方法模式及简单工厂模式的对比
  10. Vue实现百度离线地图(v2.0)
  11. 以太网详解(一)-MAC/PHY/MII/RMII/GMII/RGMII基本介绍
  12. 超高频RFID R200系列远距离一体化读写器开发测试
  13. 20160218.CCPP体系详解(0028天)
  14. Python编码错误的解决办法SyntaxError: Non-ASCII character '\xe5' in file
  15. 安装Office365后版本信息显示为Office2016
  16. EXCEL2016学生表两列名字按相同排序,对比期中期末考试选出进步学生
  17. 如何快速打造一个高权重的短视频账号?短视频运营推广日记(2)
  18. U盘文件无损进行格式转换
  19. 博弈论——非合作博弈 什么是纳什均衡
  20. Apache Kafka Consumer 消费者集

热门文章

  1. 原生APP与web APP的区别
  2. rabbitmq安装与配置(windows)亲测有效!!!
  3. android crop 大图,Android-CropView
  4. php curl title,PHP中使用CURL获取页面title例子
  5. CentOS7中yum安装mysql_CentOS7中YUM 安装mysql
  6. qpython3l_介绍一下手机里能敲Python代码的软件,QPython3L和Pydroid3,顺便用有道翻译一下后者...
  7. C语言位于30到100之间的一个奇数,《帮你度过C语言新手阶段》系列之三
  8. 【Nginx】浏览器请求URL遇到错误:no-referrer-when-downgrade
  9. 5.0安装没有costom mysql_Zabbix5.0监控mysql配置
  10. 2017计算机应用+简答,2017计算机应用基础试题及答案