spring security 学习三-rememberMe
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());}}}
spring security 学习三-rememberMe相关推荐
- spring security 学习二
spring security 学习二 doc:https://docs.spring.io/spring-security/site/docs/ 基于表单的认证(个性化认证流程): 一.自定义登录页 ...
- spring security 学习一
spring security 学习一 1.配置基本的springboot web项目,加入security5依赖,启动项目 浏览器访问,即可出现一个默认的登录页面 2.什么都没有配置 登录页面哪里来 ...
- Spring Security 中取得 RememberMe 的 cookie 值
为什么80%的码农都做不了架构师?>>> Spring Security 中的 RememberMe 对应的 cookie 名称是可配置的--相信一般情况下大家也不会使用那个默 ...
- Spring security 学习 (自助者,天助之!)
自己努力,何必要强颜欢笑的求助别人呢? 手心向下不求人! Spring security学习有进展哦: 哈哈! 1.页面都是动态生产的吧! 2.设置权限: a:pom.xml配置jar包 b:cr ...
- SpringBoot整合Spring Security——第三章异常处理
文章目录 一.常见异常 二.源码分析 三.处理异常 四.拓展spring security authenticationProvider用法及关闭不隐藏UserNotFoundException的解决 ...
- 6.Spring security中的rememberMe
文章目录 *RememberMe* *6.1RememberMe简介* *6.2RememberMe基本用法* *6.3持久化令牌* *6.4二次校验* *6.5原理分析* *`AbstractRem ...
- Spring Security学习总结
2019独角兽企业重金招聘Python工程师标准>>> 1.Spring Security介绍 一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权 ...
- 【Spring Security】的RememberMe功能流程与源码详解
文章目录 前言 原理 基础版 搭建 初始化sql 依赖引入 配置类 验证 源码分析 进阶版 集成 源码分析 疑问1 疑问2 鉴权 升级版 集成 初始化sql 配置类 验证 源码分析 鉴权 流程 扩展版 ...
- SpringBoot + Spring Security 学习笔记(一)自定义基本使用及个性化登录配置
官方文档参考,5.1.2 中文参考文档,4.1 中文参考文档,4.1 官方文档中文翻译与源码解读 SpringSecurity 核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) ...
最新文章
- 简单的VC 操作XML 文件的的方法
- QQ互联OAuth2.0 .NET SDK 发布以及网站QQ登陆示例代码
- 2.2.3 动量梯度下降法
- Android访问数据库(SQL Server 和 MySQL)
- 基于Spring Boot 的统一错误处理
- 转:为 setuptools 开路搭桥
- [转载]Git安装以及使用Git 管理个人文档
- [转载]函数getopt(),及其参数optind
- 学习笔记:工厂方法模式及简单工厂模式的对比
- Vue实现百度离线地图(v2.0)
- 以太网详解(一)-MAC/PHY/MII/RMII/GMII/RGMII基本介绍
- 超高频RFID R200系列远距离一体化读写器开发测试
- 20160218.CCPP体系详解(0028天)
- Python编码错误的解决办法SyntaxError: Non-ASCII character '\xe5' in file
- 安装Office365后版本信息显示为Office2016
- EXCEL2016学生表两列名字按相同排序,对比期中期末考试选出进步学生
- 如何快速打造一个高权重的短视频账号?短视频运营推广日记(2)
- U盘文件无损进行格式转换
- 博弈论——非合作博弈 什么是纳什均衡
- Apache Kafka Consumer 消费者集
热门文章
- 原生APP与web APP的区别
- rabbitmq安装与配置(windows)亲测有效!!!
- android crop 大图,Android-CropView
- php curl title,PHP中使用CURL获取页面title例子
- CentOS7中yum安装mysql_CentOS7中YUM 安装mysql
- qpython3l_介绍一下手机里能敲Python代码的软件,QPython3L和Pydroid3,顺便用有道翻译一下后者...
- C语言位于30到100之间的一个奇数,《帮你度过C语言新手阶段》系列之三
- 【Nginx】浏览器请求URL遇到错误:no-referrer-when-downgrade
- 5.0安装没有costom mysql_Zabbix5.0监控mysql配置
- 2017计算机应用+简答,2017计算机应用基础试题及答案