Spring Security基于散列加密方案实现自动登录功能

目录
  • 前言
  • 一. 自动登录简介
    • 1. 为什么要自动登录
    • 2. 自动登录的实现方案
  • 二. 基于散列加密方案实现自动登录
    • 1. 配置加密令牌的key
    • 2. 配置SecurityConfig类
    • 3. 添加测试接口
    • 4. 启动项目测试
  • 三. 散列加密方案实现原理
    • 1. cookie的加密原理分析
    • 2. cookie的解码原理分析
    • 3. 自动登录的源码分析
      • 3.1 令牌生成的源码分析
      • 3.2 令牌解析的源码分析

前言

在前面的2个章节中,一一哥 带大家实现了在Spring Security中添加图形验证码校验功能,其实Spring Security的功能不仅仅是这些,还可以实现很多别的效果,比如实现自动登录,注销登录等

有的小伙伴会问,我们为什么要实现自动登录啊?这个需求其实还是很常见的,因为对于用户来说,他可能经常需要进行登录以及退出登录,你想想,如果用户每次登录时都要输入自己的用户名和密码,是不是很烦,用户体验是不是很不好?

所以为了提高项目的用户体验,我们可以在项目中添加自动登录功能,当然也要给用户提供退出登录的功能。接下来就跟着 一一哥 来学习如何实现这些功能吧!

一. 自动登录简介

1. 为什么要自动登录

我们在访问网站或app时,一般都会要求我们注册一个账号,包含用户名和密码信息,其中密码还会有长度及取值范围的限制。很多时候,我们在不同的网站上注册的账号,可能密码也不同,这就导致我们必须记住这些不同网站上的用户信息。那么在下次登录时,因为我们的密码太多了,很有可能会记不起这些账号密码。所以在几次尝试登录失败之后,很多人都会选择找回密码,从而再次陷入如何设置密码的循环里。

为了尽可能减少用户重新登录的频率,提高用户的使用体验,我们可以提供自动登录这样一个会给用户带来便利,同时也会给用户带来风险的体验性功能。

2. 自动登录的实现方案

了解了自动登录出现的背景及作用后,那么我们该怎么实现自动登录呢?

首先我们知道,自动登录是将用户的登录信息保存在用户浏览器的cookie中,当用户下次访问时,自动实现校验并建立登录状态的一种机制。

所以基于上面的原理,Spring Security 就为我们提供了两种比较好的实现自动登录的方案:

  • 基于散列加密算法机制:加密用户必要的登录信息,并生成令牌来实现自动登录,利用TokenBasedRememberMeServices类来实现。
  • 基于数据库等持久化数据存储机制:生成持久化令牌来实现自动登录,利用PersistentTokenBasedRememberMeServices来实现。

我上面提到的2个实现类,其实都是AbstractRememberMeServices的子类,如下图所示:

了解了这些核心API之后,我们就可以利用这两个API来实现自动登录了。

二. 基于散列加密方案实现自动登录

我先带各位利用第1种实现方案,即基于散列加密方案来实现自动登录。

首先我们还是在之前的案例基础之上进行开发,具体的项目创建过程略过,请参考之前的章节内容。

1. 配置加密令牌的key

首先我们创建一个application.yml文件,在其中添加数据库配置,以及一个用来加密令牌的key字符串,字符串的值随便自定义就行

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db-security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
    username: root
    password: syc
  security:
    remember-me:
      key: yyg

2. 配置SecurityConfig类

跟之前的案例一样,我还是要创建一个SecurityConfig类,在其中的configure(HttpSecurity http)方法中,通过JdbcTokenRepositoryImpl关联我们的数据库,并且通过rememberMe()方法开启“记住我”功能,另外还要把我们前面在配置文件中的rememberKey配置进来,作为散列加密的key

@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.remember-me.key}")
    private String rememberKey;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //利用JdbcTokenRepositoryImpl关联数据源
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);

        http.authorizeRequests()
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .hasRole("USER")
                .antMatchers("/app/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
            	//开启“记住我”功能
                .rememberMe()
                .userDetailsService(userDetailsService)
                //配置散列加密用的key
                .key(rememberKey)
                .and()
                .csrf()
                .disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        //不对登录密码进行加密
        return NoOpPasswordEncoder.getInstance();
    }

}

3. 添加测试接口

为了方便后续的测试,我随便编写一个测试用的web接口。

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("hello")
    public String hello() {

        return "hello, user";
    }

}

4. 启动项目测试

然后我们把项目启动起来进行测试,当然你别忘了编写项目入口类,这里我就不粘贴相关代码了。

我们访问一下/user/hello接口,会先重定向到/login接口,这时候会发现在默认的登录页面上多了一个“记住我”功能。

此时如果我们打开 开发者调试工具,并且勾选“记住我”,然后发起请求,这时候我们会在控制台看到remember-me的cookie信息,说明Spring Security已经自动生成了remember-me这个cookie,且表单中的remember-me参数也处于了“on”状态。

也就是说,我们利用简单的几行代码,就实现了基于散列加密方案的自动登录。

三. 散列加密方案实现原理

你可能会很好奇,散列加密方案到底是怎么实现自动登录的呢?别急,接下来 壹哥就为你分析一下散列加密的实现原理。

1. cookie的加密原理分析

我在前面给各位说过,自动登录其实就是将用户的登录信息保存在用户浏览器的cookie中,当用户下次访问时,自动实现校验并建立登录状态的一种机制。所以在自动登录后,肯定会生成代表用户的cookie信息,但是为了安全,这个cookie肯定不会明文存储,需要把这个cookie进行加密处理,当然也会解码处理。所以接下来我就给各位分析一下这个cookie的加密和解码过程。

首先 壹哥 给各位解释一下所谓的散列加密算法,其实质就是把 username、expirationTime、password等字段,再加上自定义的key字段合并起来,在每个字段之间用 ":" 分隔,最后利用md5算法进行哈希运算这样就可以得到一个加密后的字符串。Spring Security把这个加密的字符串存储到cookie中,作为用户已登录的标识信息。

然后 壹哥 带你看看TokenBasedRememberMeServices源码类中的makeTokenSignature()方法,你会看到散列加密算法的具体加密实现过程,源码如下图所示:

2. cookie的解码原理分析

上面利用MD5进行了加密,用户在下次登录后,肯定需要进行信息的比对,以判断用户信息是否一致。Spring Security是先对cookie中的信息进行解码,然后与之前记录的登录信息进行比对,以此判断用户是否已登录。

Spring Security是在AbstractRememberMeServices类的decodeCookie()方法中,利用Base64对cookie进行解码,如下图所示:

对于以上2个源码方法,我们可以简化抽取出如下两行代码:

//对各字段进行散列加密
hashInfo=md5Hex(username +":"+expirationTime +":"password+":"+key)

//利用base64进行解码
rememberCookie=base64(username+":"+expirationrime+":"+hashInfo)

其中,expirationTime是指本次自动登录的有效期,key是自己指定的一个散列盐值,用于防止令牌被修改。利用以上两个

分析完源码之后,壹哥给各位简单总结一下cookie的生成验证原理:

  • 首先利用上面的源码生成cookie,并保存在浏览器中;
  • 在浏览器关闭并重新打开之后,用户再去访问 /user/hello 接口时,此时就会携带remember-me这个cookie到服务端;
  • 服务器端拿到cookie之后,利用Base64进行解码,计算出用户名和过期时间,再根据用户名查询到用户密码;
  • 最后还要通过 MD5 散列函数计算出散列值,并将计算出的散列值和浏览器传递来的散列值进行对比,以此确认这个令牌是否有效。

3. 自动登录的源码分析

上面分析完cookie信息的加密和解码之后,接下来我再结合源码,从两个方面来介绍自动登录的实现过程,一个是 remember-me 令牌的生成的过程,另一个则是该令牌的解析过程

3.1 令牌生成的源码分析

我们要想知道源码中是如何生成remember-me自动登录令牌的,首先得知道Spring Security是如何进入到该令牌所在代码的,这个代码的执行与我们前一章节所讲的Spring Security的认证授权有关,请进入到前面查看。

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

这个令牌生成的核心处理方法定义在: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();
 	}

    //获取登录过期时间,默认是2周
 	int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
 	long expiryTime = System.currentTimeMillis();
 	expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);

    //生成remember-me签名信息
 	String signatureValue = makeTokenSignature(expiryTime, username, password);

    //保存cookie
 	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 字符串。但是如果服务端重启,这个默认的 key 是会变的,这样就导致之前派发出去的所有 remember-me 自动登录令牌失效,所以我们可以指定这个 key。
  5. 最后,将用户名、令牌有效期以及计算得到的散列值放入 Cookie 中并随response返回。

3.2 令牌解析的源码分析

对于RememberMe 这个功能,Spring Security提供了 RememberMeAuthenticationFilter 这个过滤器类来处理相关功能,我们来看下 RememberMeAuthenticationFilter 的 doFilter() 方法:

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) {
				// Attempt authenticaton via AuthenticationManager
				try {
					rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

					// Store to SecurityContextHolder
					SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

					onSuccessfulAuthentication(request, response, rememberMeAuth);

					if (logger.isDebugEnabled()) {
						logger.debug("SecurityContextHolder populated with remember-me token: '"
								+ SecurityContextHolder.getContext().getAuthentication()
								+ "'");
					}

					// Fire event
					if (this.eventPublisher != null) {
						eventPublisher
								.publishEvent(new InteractiveAuthenticationSuccessEvent(
										SecurityContextHolder.getContext()
												.getAuthentication(), this.getClass()));
					}

					if (successHandler != null) {
						successHandler.onAuthenticationSuccess(request, response,
								rememberMeAuth);

						return;
					}

				}
				catch (AuthenticationException authenticationException) {
					if (logger.isDebugEnabled()) {
						logger.debug(
								"SecurityContextHolder not populated with remember-me token, as "
										+ "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
										+ rememberMeAuth
										+ "'; invalidating remember-me token",
								authenticationException);
					}

					rememberMeServices.loginFail(request, response);

					onUnsuccessfulAuthentication(request, response,
							authenticationException);
				}
			}

			chain.doFilter(request, response);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'");
			}

			chain.doFilter(request, response);
		}
	}

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

    @Override
	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);
		}
		......

		cancelCookie(request, response);
		return null;
	}

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

至此,壹哥 就结合着源码和底层原理,给大家讲解了基于散列加密方案实现了自动登录,并且在本案例中给大家介绍了散列加密算法,你掌握的怎么样呢?请在评论区给 一一哥 留言,说说你的感受吧!下一篇文章中,壹哥 会给各位讲解 基于持久化令牌方案实现自动登录,敬请期待哦!

到此这篇关于Spring Security基于散列加密方案实现自动登录功能的文章就介绍到这了,更多相关Spring Security散列加密方案实现自动登录内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

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

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

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

  • Spring Security学习之rememberMe自动登录的实现

    前言 自动登录是将用户的登录信息保存在用户浏览器的cookie中,当用户下次访问时,自动实现校验并建立登录态的一种机制. Spring Security提供了两种非常好的令牌: 散列算法加密用户必要的登录信息并生成令牌 数据库等持久性数据存储机制用的持久化令牌 散列加密方案 在Spring Security中加入自动登录的功能非常简单: @Override protected void configure(HttpSecurity http) throws Exception { http.au

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

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

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

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

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

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

  • Spring Security基于散列加密方案实现自动登录功能

    目录 前言 一. 自动登录简介 1. 为什么要自动登录 2. 自动登录的实现方案 二. 基于散列加密方案实现自动登录 1. 配置加密令牌的key 2. 配置SecurityConfig类 3. 添加测试接口 4. 启动项目测试 三. 散列加密方案实现原理 1. cookie的加密原理分析 2. cookie的解码原理分析 3. 自动登录的源码分析 3.1 令牌生成的源码分析 3.2 令牌解析的源码分析 前言 在前面的2个章节中,一一哥 带大家实现了在Spring Security中添加图形验证码

  • SpringSecurity基于散列加密方案实现自动登录

    目录 前言 一. 自动登录简介 1. 为什么要自动登录 2. 自动登录的实现方案 二. 基于散列加密方案实现自动登录 1. 配置加密令牌的key 2. 配置SecurityConfig类 3. 添加测试接口 4. 启动项目测试 三. 散列加密方案实现原理 1. cookie的加密原理分析 2. cookie的解码原理分析 3. 自动登录的源码分析 前言 在前面的2个章节中,一一哥 带大家实现了在Spring Security中添加图形验证码校验功能,其实Spring Security的功能不仅仅

  • Spring Security基于过滤器实现图形验证码功能

    目录 前言 一. 验证码简介 二. 基于过滤器实现图形验证码 1. 实现概述 2. 创建新模块 3. 添加依赖包 4. 创建Producer对象 5. 创建生成验证码的接口 6. 自定义异常 7. 创建拦截验证码的过滤器 8. 编写SecurityConfig 9. 编写测试页面 10. 代码结构 11. 启动项目测试 前言 在前两个章节中,一一哥 带大家学习了Spring Security内部关于认证授权的核心API,以及认证授权的执行流程和底层原理.掌握了这些之后,对于Spring Secu

  • PHP实现的单向散列加密操作示例

    本文实例讲述了PHP实现的单向散列加密操作.分享给大家供大家参考,具体如下: 1.加密文件 <?php //sha1_en.php header("content-type:text/html;charset=utf-8"); $str = "我是张三,能给我个人数据吗"; $salt="123456";//我是唯一不变的salt $sha1=sha1($str.$salt);//或者$sha1=md5($str.$salt); echo

  • Spring Security基于自定义的认证提供器实现图形验证码流程解析

    目录 前言 一. 认证提供器简介 1. 认证提供器AuthenticationProver 2. WebAuthenticationDetails类介绍 二. 实现图形验证码 1. 添加依赖包 2. 创建Producer对象 3. 创建生成验证码的接口 4. 自定义异常 5. 自定义WebAuthenticationDetails 6. 自定义AuthenticationDetailsSource 7. 自定义DaoAuthenticationProver 8. 添加SecurityConfig

  • Spring Security 基于URL的权限判断源码解析

    目录 1. FilterSecurityInterceptor 源码阅读 2. 自定义基于url的授权 1. FilterSecurityInterceptor 源码阅读 org.springframework.security.web.access.intercept.FilterSecurityInterceptor 通过过滤器实现对HTTP资源进行安全处理. 该安全拦截器所需的 SecurityMetadataSource 类型为 FilterInvocationSecurityMetad

  • 一个注解搞定Spring Security基于Oauth2的SSO单点登录功能

    目录 一.说明 二.原理说明 2.1. 同域单点登录 2.2. 跨域单点登录 2.3. 基于Oauth2的跨域单点登录流程 三.Spring Security实现 四.demo下载地址 一.说明 单点登录顾名思义就是在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统,免除多次登录的烦恼.本文主要介绍 同域 和 跨域 两种不同场景单点登录的实现原理,并使用 Spring Security 来实现一个最简单的跨域 SSO客户端 . 二.原理说明 单点登录主流都是基于共享 cookie

  • Spring Security代码实现JWT接口权限授予与校验功能

    通过笔者前两篇文章的说明,相信大家已经知道JWT是什么,怎么用,该如何结合Spring Security使用.那么本节就用代码来具体的实现一下JWT登录认证及鉴权的流程. 一.环境准备工作 建立Spring Boot项目并集成了Spring Security,项目可以正常启动 通过controller写一个HTTP的GET方法服务接口,比如:"/hello" 实现最基本的动态数据验证及权限分配,即实现UserDetailsService接口和UserDetails接口.这两个接口都是向

  • 基于Ajax技术实现无刷新用户登录功能

    代码如下: // JScript 文件 function usersLogon() { var userName = document.getElementById("txtuserName").value; var password = document.getElementById("txtpassword").value; var checkCode = document.getElementById("txtCheckCode").val

随机推荐