SpringBoot 配合 SpringSecurity 实现自动登录功能的代码

自动登录是我们在软件开发时一个非常常见的功能,例如我们登录 QQ 邮箱:

很多网站我们在登录的时候都会看到类似的选项,毕竟总让用户输入用户名密码是一件很麻烦的事。

自动登录功能就是,用户在登录成功后,在某一段时间内,如果用户关闭了浏览器并重新打开,或者服务器重启了,都不需要用户重新登录了,用户依然可以直接访问接口数据

作为一个常见的功能,我们的 Spring Security 肯定也提供了相应的支持,本文我们就来看下 Spring Security 中如何实现这个功能。

一、加入 remember-me

为了配置方便,加入两个依赖即可:

配置类中添加如下代码:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Bean
  PasswordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("yolo")
        .password("123").roles("admin");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .and()
        .rememberMe()
        .and()
        .csrf().disable();
  }
}

大家看到,这里只需要添加一个 .rememberMe() 即可,自动登录功能就成功添加进来了。

接下来我们随意添加一个测试接口:

@RestController
public class HelloController {
  @GetMapping("/hello")
  public String hello(){
    return "Hello Yolo !!!";
  }
}

这个时候大家发现,默认的登录页面多了一个选项,就是记住我。我们输入用户名密码,并且勾选上记住我这个框,然后点击登录按钮执行登录操作。

可以看到,登录数据中,除了 username 和 password 之外,还有一个 remember-me,之所以给大家看这个,是想告诉大家,如果你你需要自定义登录页面,RememberMe 这个选项的 key 该怎么写。

登录成功之后,就会自动跳转到 hello 接口了。我们注意,系统访问 hello 接口的时候,携带的 cookie:

大家注意到,这里多了一个 remember-me,这就是这里实现的核心,关于这个 remember-me 我一会解释,我们先来测试效果。

接下来,我们关闭浏览器,再重新打开浏览器。正常情况下,浏览器关闭再重新打开,如果需要再次访问 hello 接口,就需要我们重新登录了。但是此时,我们再去访问 hello 接口,发现不用重新登录了,直接就能访问到,这就说明我们的 RememberMe 配置生效了(即下次自动登录功能生效了)。

二、原理分析

按理说,浏览器关闭再重新打开,就要重新登录,现在竟然不用等了,那么这个功能到底是怎么实现的呢?

首先我们来分析一下 cookie 中多出来的这个 remember-me,这个值一看就是一个 Base64 转码后的字符串,我们可以使用网上的一些在线工具来解码,可以自己简单写两行代码来解码:

@Test
  void contextLoads() {
    String s = new String(
        Base64.getDecoder().decode("eW9sbzoxNjAxNDczNTY2NTA1OjlmMGY5YjBjOTAzYmNjYmU3ZjMwYWM0NjVlZjEzNmQ5"));
    System.out.println("s = " + s);
  }

执行这段代码,输出结果如下:

s = yolo:1601473566505:9f0f9b0c903bccbe7f30ac465ef136d9

可以看到,这段 Base64 字符串实际上用 : 隔开,分成了三部分:

(1)第一段是用户名,这个无需质疑。
(2)第二段看起来是一个时间戳,我们通过在线工具或者 Java 代码解析后发现,这是一个两周后的数据。
(3)第三段我就不卖关子了,这是使用 MD5 散列函数算出来的值,他的明文格式是
username + ":" + tokenExpiryTime + ":" + password + ":" + key,最后的 key 是一个散列盐值,可以用来防治令牌被修改。

了解到 cookie 中 remember-me 的含义之后,那么我们对于记住我的登录流程也就很容易猜到了了。

在浏览器关闭后,并重新打开之后,用户再去访问 hello 接口,此时会携带着 cookie 中的 remember-me 到服务端,服务到拿到值之后,可以方便的计算出用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值,再将计算出的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效。

流程就是这么个流程,接下来我们通过分析源码来验证一下这个流程对不对。

三、源码分析

接下来,我们通过源码来验证一下我们上面说的对不对。

这里主要从两个方面来介绍,一个是 remember-me 这个令牌生成的过程,另一个则是它解析的过程。

1. 生成

生成的核心处理方法在:TokenBasedRememberMeServices#onLoginSuccess:

@Override
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
 Authentication successfulAuthentication) {
 String username = retrieveUserName(successfulAuthentication);
 String password = retrievePassword(successfulAuthentication);
 if (!StringUtils.hasLength(password)) {
 UserDetails user = getUserDetailsService().loadUserByUsername(username);
 password = user.getPassword();
 }
 int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
 long expiryTime = System.currentTimeMillis();
 expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);
 String signatureValue = makeTokenSignature(expiryTime, username, password);
 setCookie(new String[] { username, Long.toString(expiryTime), signatureValue },
  tokenLifetime, request, response);
}
protected String makeTokenSignature(long tokenExpiryTime, String username,
 String password) {
 String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
 MessageDigest digest;
 digest = MessageDigest.getInstance("MD5");
 return new String(Hex.encode(digest.digest(data.getBytes())));
}

(1)首先从登录成功的 Authentication 中提取出用户名/密码。
(2)由于登录成功之后,密码可能被擦除了,所以,如果一开始没有拿到密码,就再从 UserDetailsService 中重新加载用户并重新获取密码。
(3)再接下来去获取令牌的有效期,令牌有效期默认就是两周。
(4)再接下来调用 makeTokenSignature 方法去计算散列值,实际上就是根据 username、令牌有效期以及 password、key 一起计算一个散列值。如果我们没有自己去设置这个 key,默认是在 RememberMeConfigurer#getKey 方法中进行设置的,它的值是一个 UUID 字符串。
(5)最后,将用户名、令牌有效期以及计算得到的散列值放入 Cookie 中。

关于第四点,我这里再说一下。

由于我们自己没有设置 key,key 默认值是一个 UUID 字符串,这样会带来一个问题,就是如果服务端重启,这个 key 会变,这样就导致之前派发出去的所有 remember-me 自动登录令牌失效,所以,我们可以指定这个 key。指定方式如下:

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .and()
      .rememberMe()
      .key("yolo")
      .and()
      .csrf().disable();
}

如果自己配置了 key,即使服务端重启,即使浏览器打开再关闭,也依然能够访问到 hello 接口

这是 remember-me 令牌生成的过程。至于是如何走到 onLoginSuccess 方法的,这里可以给大家稍微提醒一下思路:

AbstractAuthenticationProcessingFilter#doFilter -> AbstractAuthenticationProcessingFilter#successfulAuthentication -> AbstractRememberMeServices#loginSuccess -> TokenBasedRememberMeServices#onLoginSuccess。

2. 解析

那么当用户关掉并打开浏览器之后,重新访问 /hello 接口,此时的认证流程又是怎么样的呢?

我们之前说过,Spring Security 中的一系列功能都是通过一个过滤器链实现的,RememberMe 这个功能当然也不例外。

Spring Security 中提供了 RememberMeAuthenticationFilter 类专门用来做相关的事情,我们来看下 RememberMeAuthenticationFilterdoFilter 方法:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
 throws IOException, ServletException {
 HttpServletRequest request = (HttpServletRequest) req;
 HttpServletResponse response = (HttpServletResponse) res;
 if (SecurityContextHolder.getContext().getAuthentication() == null) {
 Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
  response);
 if (rememberMeAuth != null) {
  rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
  SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
  onSuccessfulAuthentication(request, response, rememberMeAuth);
  if (this.eventPublisher != null) {
   eventPublisher
    .publishEvent(new InteractiveAuthenticationSuccessEvent(
     SecurityContextHolder.getContext()
      .getAuthentication(), this.getClass()));
  }
  if (successHandler != null) {
   successHandler.onAuthenticationSuccess(request, response,
    rememberMeAuth);
   return;
  }
  }
 chain.doFilter(request, response);
 }
 else {
 chain.doFilter(request, response);
 }
}

这个方法最关键的地方在于,如果从 SecurityContextHolder 中无法获取到当前登录用户实例,那么就调用 rememberMeServices.autoLogin 逻辑进行登录,我们来看下这个方法:

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

 throw cte;
 }
 cancelCookie(request, response);
 return null;
}

可以看到,这里就是提取出 cookie 信息,并对 cookie 信息进行解码,解码之后,再调用 processAutoLoginCookie 方法去做校验,processAutoLoginCookie 方法的代码我就不贴了,核心流程就是首先获取用户名和过期时间,再根据用户名查询到用户密码,然后通过 MD5 散列函数计算出散列值,再将拿到的散列值和浏览器传递来的散列值进行对比,就能确认这个令牌是否有效,进而确认登录是否有效。

四、总结

看了上面的文章,大家可能已经发现,如果我们开启了 RememberMe 功能,最最核心的东西就是放在 cookie 中的令牌了,这个令牌突破了 session 的限制,即使服务器重启、即使浏览器关闭又重新打开,只要这个令牌没有过期,就能访问到数据。

一旦令牌丢失,别人就可以拿着这个令牌随意登录我们的系统了,这是一个非常危险的操作。

但是实际上这是一段悖论,为了提高用户体验(少登录),我们的系统不可避免的引出了一些安全问题,不过我们可以通过技术将安全风险降低到最小

到此这篇关于SpringBoot 配合 SpringSecurity 实现自动登录功能的代码的文章就介绍到这了,更多相关SpringSecurity自动登录内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot结合SpringSecurity实现图形验证码功能

    本文介绍了SpringBoot结合SpringSecurity实现图形验证码功能,分享给大家,具体如下: 生成图形验证码 根据随机数生成图片 将随机数存到Session中 将生成的图片写到接口的响应中 生成图形验证码的过程比较简单,和SpringSecurity也没有什么关系.所以就直接贴出代码了 根据随机数生成图片 /** * 生成图形验证码 * @param request * @return */ private ImageCode generate(ServletWebRequest r

  • 详解使用Spring Security进行自动登录验证

    在之前的博客使用SpringMVC创建Web工程并使用SpringSecurity进行权限控制的详细配置方法 中,我们描述了如何配置一个基于SpringMVC.SpringSecurity框架的网站系统.在这篇博客中,我们将继续描述如何使用Spring Security进行登录验证. 总结一下Spring Security的登录验证关键步骤: 1.在数据库中建好三张表,即users.authorities和persistent_logins三个.注意字段的定义,不能少,可以多,名字必须按规定来.

  • Spring Security实现两周内自动登录"记住我"功能

    本文是Spring Security系列中的一篇.在上一篇文章中,我们通过实现UserDetailsService和UserDetails接口,实现了动态的从数据库加载用户.角色.权限相关信息,从而实现了登录及授权相关的功能.这一节就在此基础上新增,登录过程中经常使用的"记住我"功能,也就是我们经常会在各种网站登陆时见到的"两周内免登录","三天内免登录"的功能.该功能的作用就是:当我们登录成功之后,一定的周期内当我们再次访问该网站,不需要重新登

  • SpringBoot + SpringSecurity 短信验证码登录功能实现

    实现原理 在之前的文章中,我们介绍了普通的帐号密码登录的方式: SpringBoot + Spring Security 基本使用及个性化登录配置. 但是现在还有一种常见的方式,就是直接通过手机短信验证码登录,这里就需要自己来做一些额外的工作了. 对SpringSecurity认证流程详解有一定了解的都知道,在帐号密码认证的过程中,涉及到了以下几个类:UsernamePasswordAuthenticationFilter(用于请求参数获取),UsernamePasswordAuthentica

  • Spring security实现记住我下次自动登录功能过程详解

    一.原理分析 第一次登陆时,如果用户勾选了readme选项,登陆成功后springsecurity会生成一个cookie返回给浏览器端,浏览器下次访问时如果携带了这个cookie,springsecurity就会放行这次访问. 二.实现方式 2.1 简单实现方式 (1) 在springsecurity的配置文件中,http节点下增加一个remember-me配置 <security:http auto-config="true" use-expressions="fal

  • SpringBoot+SpringSecurity处理Ajax登录请求问题(推荐)

    最近在项目中遇到了这样一个问题:前后端分离,前端用Vue来做,所有的数据请求都使用vue-resource,没有使用表单,因此数据交互都是使用JSON,后台使用Spring Boot,权限验证使用了Spring Security,因为之前用Spring Security都是处理页面的,这次单纯处理Ajax请求,因此记录下遇到的一些问题.这里的解决方案不仅适用于Ajax请求,也可以解决移动端请求验证. 创建工程 首先我们需要创建一个Spring Boot工程,创建时需要引入Web.Spring S

  • SpringBoot 配合 SpringSecurity 实现自动登录功能的代码

    自动登录是我们在软件开发时一个非常常见的功能,例如我们登录 QQ 邮箱: 很多网站我们在登录的时候都会看到类似的选项,毕竟总让用户输入用户名密码是一件很麻烦的事. 自动登录功能就是,用户在登录成功后,在某一段时间内,如果用户关闭了浏览器并重新打开,或者服务器重启了,都不需要用户重新登录了,用户依然可以直接访问接口数据 作为一个常见的功能,我们的 Spring Security 肯定也提供了相应的支持,本文我们就来看下 Spring Security 中如何实现这个功能. 一.加入 remembe

  • SpringBoot基于SpringSecurity表单登录和权限验证的示例

    一.简介 上篇介绍了一个自己做的管理系统,最近空闲的时间自己在继续做,把之前登录时候自定义的拦截器过滤器换成了基于SpringSecurity来做,其中遇到了很多坑,总结下,大家有遇到类似问题的话就当是为大家闭坑吧. 二.项目实现功能和成果展示 首先来看下登录界面:这是我输入的一个正确的信息,点击登录后SpringSecurity会根据你输入的用户名和密码去验证是否正确,如果正确的话就去你定义的页面,我这里定义的是查询教师信息页面.来看下代码吧. 三.准备工作(前台页面.实体类) 实体类Teac

  • spring security实现下次自动登录功能过程解析

    这篇文章主要介绍了spring security实现记住我下次自动登录功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.原理分析 第一次登陆时,如果用户勾选了readme选项,登陆成功后springsecurity会生成一个cookie返回给浏览器端,浏览器下次访问时如果携带了这个cookie,springsecurity就会放行这次访问. 二.实现方式 2.1 简单实现方式 (1) 在springsecurity的配置文件中,http节

  • springboot配合Thymeleaf完美实现遍历功能

    一. 什么是Thymeleaf Thymeleaf是面向Web和独立环境的现代服务器端Java模板引擎. Thymeleaf的主要目标是为您的开发工作流程带来优雅的自然模板 - 可以正确显示在浏览器中的HTML,也可以作为静态原型工作,从而在开发团队中进行更强大的协作. 随着Spring框架的模块,与您最喜欢的工具的集成,以及插入自己的功能的能力,Thymeleaf是现代HTML5 JVM Web开发的理想选择,尽管它可以做的更多. 好吧,我承认刚才那段是Thymeleaf官方的说明,我只不过机

  • Yii2框架实现登录、退出及自动登录功能的方法详解

    本文实例讲述了Yii2框架实现登录.退出及自动登录功能的方法.分享给大家供大家参考,具体如下: 自动登录的原理很简单.主要就是利用cookie来实现的 在第一次登录的时候,如果登录成功并且选中了下次自动登录,那么就会把用户的认证信息保存到cookie中,cookie的有效期为1年或者几个月. 在下次登录的时候先判断cookie中是否存储了用户的信息,如果有则用cookie中存储的用户信息来登录, 配置User组件 首先在配置文件的components中设置user组件 'user' => [ '

  • java web实现自动登录功能

    本文实例为大家分享了java web实现自动登录功能的具体代码,供大家参考,具体内容如下 主要思路就是:当用户访问网站的首页时,浏览器端会先检擦浏览器中存在的cookie中是否又登录的用户的用户名,如果有,则直接跳转至用户登录好的界面,如果没有,则重定向至登录界面,在服务器端创建该用户登录的cookie,响应时,将创建的cookie返回至浏览器端保存. 一.用户访问首页时检查cookie是否存在. package ahpudong.com; import java.io.IOException;

  • JavaWeb使用Cookie模拟实现自动登录功能(不需用户名和密码)

    其中包含两个jsp文件,分别为login.jsp和index.jsp 代码如下: login.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "

  • PHP cookie,session的使用与用户自动登录功能实现方法分析

    本文实例讲述了PHP cookie,session的使用与用户自动登录功能实现方法.分享给大家供大家参考,具体如下: cookie的使用 //生成cookie //注释:setcookie() 函数必须位于 <html> 标签之前. //setcookie(name, value, expire, path, domain); //名称,值,过期时间,有效路径,有效域名 //path,可选:如果路径设置为 "/",那么 cookie 将在整个域名内有效.如果路径设置为 &q

  • python实现网站用户名密码自动登录功能

    一.概述 公司需要通过网页用户认证登录实现上网,网络设备判断当前帐号12小时没有没上网将会自动断开帐号上网,每天早上上班第一件事就是打开用户认证网页输入. 用户名与密码,有时候要家里通过teamview远程控制软件连接公司电脑,想让公司电脑24小时在线,最后通过python实现了自动登录. 代码: #_*_ coding:utf-8 _*_ import requests,time s=requests.session() data = [ ('opr', 'pwdLogin'), ('user

随机推荐