JWT | 01.JWT基础学习-SpringBoot整合

0.前言

参考视频:

【极简入门】15分钟学会JWT的使用

参考文章:

利用Springboot实现Jwt认证

JSON Web Token 入门教程

SpringBoot整合JWT

1. 引入:解决跨域认证问题

互联网服务离不开用户认证。一般流程是下面这样。

  • 用户向服务器发送用户名和密码。

  • 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

  • 服务器向用户返回一个 session_id,写入用户的 Cookie。

  • 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

  • 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

2.JWT概述

2.1.概念

JSON Web Token,通过数字签名的方式,以JSON字符串为载体,在不同的服务终端之间进行更安全的信息传输

2.2.组成

JWT由三个部分组成,用.拼接

image-20200221230910489

  • Header(头部)

    里面包含两个信息,分别是类型(typ),另一个是算法名称(alg)

    {
        "typ": "JWT",
        "alg": "HS256"
    }
    

    通过base64进行编码,便得到token中的第一部分

  • Payload(载荷)

    里面存储这个令牌的有效信息,

    包括标准注册的(签发时间、过期时间等)和自定义的(下方的stuNo、stuName、admin)

    {
        "stuNo": "3119******",
        "stuName": "xyx",
        "admin": true
    }
    

    通过base64进行编码,便得到token中的第二部分

    注意:第二部分并未进行加密,因此该部分是可以被直接解码得到其中内容的,因此JWT并不适合存储敏感信息(如密码),只适合存储用户id、用户名等信息

  • Signature(签名)

    从以下代码可以看出,

    原理是将Header、Payload用base64编码后,再用.拼接(第一部分.第二部分)

    再将拼接结果通过Header中声明的算法(HS256)用密钥加密,得到第三部分

    var signingKey = "abcdefg1234567"
    var encodedString = 
        base64UrlEncode(header) + "." + base64UrlEncode(Payload);
    
    var signature = HMACSHA256(encodingString, signingKey);
    

    注意:第三部分是前面两部分根据加密算法(HS256)和"密钥"加密后得到的。因此如果没有"密钥",第三部分就无法被伪造。服务端的验证也正是利用了这个原理来防止用户伪造信息,确保安全性

3.JWT的工作流程

3.1.登录阶段

用户首次登录时,若成功登录,就会将一些附带信息封装成一个JWT字符串,并返回给客户端。

image-20200221234215011

这个JWT字符串里的附带信息一般是:用户id、用户权限、有效期。

下次用户登录的时候,必须把这个令牌也一起带上,否则将被要求重新登录。

3.2.认证阶段

登录阶段时,用户在客户端获取到了自己的JWT字符串

此后前端的每次请求,都要将这个JWT字符串放在请求头的某一位置一并发送过来,后端接受到请求后,解析JWT,验证该JWT是否合法(是否被伪造)、是否过期等

image-20200221234900958

3.3.补充内容

3.3.1.关于有效期

由于jwt是直接给用户的,只要能验证成功的jwt都可以被视作登录成功,所以,如果不给jwt设置一个过期时间的话,用户只要存着这个jwt,就相当于永远登录了,而这是不安全的,因为如果这个令牌泄露了,那么服务器是没有任何办法阻止该令牌的持有者访问的(因为拿到这个令牌就等于随便冒充你身份访问了),所以往往jwt都会有一个有效期,通常存在于载荷部分,下面是一段生成jwt的java代码:

 return JWT.create().withAudience(userId)
         .withIssuedAt(new Date())        <---- 发行时间
         .withExpiresAt(expiresDate)     <---- 有效期
         .withClaim("sessionId", sessionId)
         .withClaim("userName", userName)
         .withClaim("realName", realName)
         .sign(Algorithm.HMAC256(userId+"HelloLehr"));

在实际的开发中,令牌的有效期往往是越短越安全,因为令牌会频繁变化,即使有某个令牌被别人盗用,也会很快失效。但是有效期短也会导致用户体验不好(总是需要重新登录),所以这时候就会出现另外一种令牌—refresh token刷新令牌。刷新令牌的有效期会很长,只要刷新令牌没有过期,就可以再申请另外一个jwt而无需登录(且这个过程是在用户访问某个接口时自动完成的,用户不会感觉到令牌替换),对于刷新令牌的具体实现这里就不详细讲啦(其实因为我也没深入研究过XD…)

3.3.2.对比session

在传统的session会话机制中,服务器识别用户是通过用户首次访问服务器的时候,给用户一个sessionId,然后把用户对应的会话记录放在服务器这里,以后每次通过sessionId来找到对应的会话记录。这样虽然所有的数据都存在服务器上是安全的,但是对于分布式(一个项目部署到多个服务器中)的应用来说,就需要考虑session共享的问题了,不然同一个用户的sessionId的请求被自动分配到另外一个服务器上就等于失效了

而Jwt不但可以用于登录认证,也把相应的数据返回给了用户(就是载荷里的内容),通过签名来保证数据的真实性,该应用的各个服务器上都有统一的验证方法,只要能通过验证,就说明你的令牌是可信的,我就可以从你的令牌上获取你的信息,知道你是谁了,从而减轻了服务器的压力,而且也对分布式应用更为友好。(毕竟就不用担心服务器session的分布式存储问题了)

3.3.3.如何确保安全

image-20200221230910489

如图,前面已讲到JWT共被分成三部分

  • 头部header:只经过base64编码,可伪造
  • 载荷payload:只经过base64编码,可伪造
  • 签名signature:前面两部分进行拼接,根据密钥按规定的算法(HS256)加密

签名部分由于密钥的存在,用户在不得知密钥的情况下,是无法伪造出一个有效的签名的。正是这个第三部分的存在,确保了JWT的安全性。

4.代码实现

主要参考文章:SpringBoot整合JWT

4.1.JwtUtil.java

在util包中创建JwtUtil.java

调用Jwts.builder()生成一个JWT字符串:

  • 设置头部header:

    .setHeaderParam(K, V)

  • 设置载荷payload:

    .claim(K, V) 自定义内容

    .setExpiration(expireDate) 过期时间

  • 设置签名signature:

    .signWith(SignatureAlgorithm.JS256, signingKey)

image-20210723204603434

package com.eshang.jwt.learning.util;

import io.jsonwebtoken.*;

import java.util.Date;

/**
 * @author xyx-Eshang
 */
public class JwtUtil {
	/**
	 * 密钥
	 */
	private static String signingKey = "abcdefghijklmn";
	/**
	 * 过期的分钟
	 */
	private static Integer expireMinutes = 60;

	public static String createToken(Integer stuNo, Boolean whetherAdmin) {
		Date expireDate = new Date(System.currentTimeMillis() + 1000 * 60 * expireMinutes);
		return Jwts.builder()
				//header
				.setHeaderParam("typ", "JWT")
				.setHeaderParam("alg", "HS256")
				//payload
				.claim("stuNo", stuNo)
				.claim("admin", whetherAdmin)
				.setExpiration(expireDate)
				//signature
				.signWith(SignatureAlgorithm.HS256, signingKey)
				//拼接起来
				.compact();
	}

	public static Claims parseToken(String token) {
		JwtParser jwtParser = Jwts.parser();
		return jwtParser
				.setSigningKey(signingKey)
				.parseClaimsJws(token)
				.getBody();
	}
}

4.2.JwtLearningApplicationTests.java

编写测试类JwtLearningApplicationTests.java

image-20210723204826110

package com.eshang.jwt.learning;

import com.eshang.jwt.learning.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class JwtLearningApplicationTests {

	@Test
	void contextLoads() {
		//生成JWT:学号为1,非管理员
		String jwt = JwtUtil.createToken(1, false);

		//打印这个JWT
		System.out.println("===打印这个JWT===" + "\n" + jwt);

		//解析
		System.out.println("===解析这个JWT===");
		Claims claims = JwtUtil.parseToken(jwt);
		System.out.println("stuNo:\t" + claims.get("stuNo"));
		System.out.println("admin:\t" + claims.get("admin"));
	}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值