pd-tools-jwt模块的定位是对于jwt令牌相关操作进行封装,为认证、鉴权提供支撑。
提供的功能:生成jwt token、解析jwt token
10.1 认证机制介绍
10.1.1 HTTP Basic Auth
HTTP Basic Auth 是一种简单的登录认证方式,Web浏览器或其他客户端程序在请求时提供用户名和密码,通常用户名和密码会通过HTTP头传递。简单点说就是每次请求时都提供用户的username和password
这种方式是先把用户名、冒号、密码拼接起来,并将得出的结果字符串用Base64算法编码。
例如,提供的用户名是 bill
、口令是 123456
,则拼接后的结果就是 bill:123456
,然后再将其用Base64编码,得到 YmlsbDoxMjM0NTY=
。最终将Base64编码的字符串发送出去,由接收者解码得到一个由冒号分隔的用户名和口令的字符串。
优点:
基本上所有流行的网页浏览器都支持基本认证。
缺点:
由于用户名和密码都是Base64编码的,而Base64编码是可逆的,所以用户名和密码可以认为是明文。所以只有在客户端和服务器主机之间的连接是安全可信的前提下才可以使用。
10.1.2 Cookie-Session Auth
Cookie-session 认证机制是通过浏览器带上来Cookie对象来与服务器端的session对象匹配来实现状态管理。
第一次请求认证在服务端创建一个Session对象,同时在用户的浏览器端创建了一个Cookie对象;当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效。
优点:
相对HTTP Basic Auth更加安全。
缺点:
这种基于cookie-session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来。
10.1.3 OAuth
OAuth 是一个关于授权(authorization)的开放网络标准。允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。现在的版本是2.0版。
严格来说,OAuth2不是一个标准协议,而是一个安全的授权框架。它详细描述了系统中不同角色、用户、服务前端应用(比如API),以及客户端(比如网站或移动App)之间怎么实现相互认证。
OAuth流程如下图:

优点:
- 快速开发,代码量小,维护工作少。
- 如果API要被不同的App使用,并且每个App使用的方式也不一样,使用OAuth2是个不错的选择。
缺点:
OAuth2是一个安全框架,描述了在各种不同场景下,多个应用之间的授权问题。有海量的资料需要学习,要完全理解需要花费大量时间。OAuth2不是一个严格的标准协议,因此在实施过程中更容易出错。
10.1.4 Token Auth
基于token的认证鉴权机制类似于http协议,也是无状态的。这种方式不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
这个token必须要在每次请求时传递给服务端,它应该保存在请求头中,Token Auth 流程如下图:

优点:
- 支持跨域访问
- Token机制在服务端不需要存储session信息:Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息
- 去耦:不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可
- 更适用于移动应用:Cookie是不被客户端(iOS, Android,Windows 8等)支持的。
- 基于标准化:
API可以采用标准化的 JSON Web Token (JWT)。这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)
缺点:
- 占带宽
正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人会在 JWT 中存储的信息会更多
- 无法在服务端注销,因为服务端是无状态的,并没有保存客户端用户登录信息
- 对于有着严格性能要求的 Web 应用并不理想,尤其对于单线程环境
10.2 JWT
10.2.1 JWT介绍
JWT全称为JSON Web Token,是目前最流行的跨域身份验证解决方案。JWT是为了在网络应用环境间传递声明而制定的一种基于JSON的开放标准。
JWT特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可被加密。
10.2.2 JWT的数据结构
JWT其实就是一个很长的字符串,字符之间通过”.”分隔符分为三个子串,各字串之间没有换行符。每一个子串表示了一个功能块,总共有三个部分:**JWT头(header)、有效载荷(payload)、签名(signature)**,如下图所示:

10.2.2.1 JWT头
JWT头是一个描述JWT元数据的JSON对象,通常如下所示:
1
| {"alg": "HS256","typ": "JWT"}
|
alg:表示签名使用的算法,默认为HMAC SHA256(写为HS256)
typ:表示令牌的类型,JWT令牌统一写为JWT
最后,使用Base64 URL算法将上述JSON对象转换为字符串
10.2.2.2 有效载荷
有效载荷,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。
有效载荷部分规定有如下七个默认字段供选择:
1 2 3 4 5 6 7
| iss:发行人 exp:到期时间 sub:主题 aud:用户 nbf:在此之前不可用 iat:发布时间 jti:JWT ID用于标识该JWT
|
除以上默认字段外,还可以自定义私有字段。
最后,同样使用Base64 URL算法将有效载荷部分JSON对象转换为字符串
10.2.2.3 签名
签名实际上是一个加密的过程,是对上面两部分数据通过指定的算法生成哈希,以确保数据不会被篡改。
首先需要指定一个密码(secret),该密码仅仅保存在服务器中,并且不能向用户公开。然后使用JWT头中指定的签名算法(默认情况下为HMAC SHA256),根据以下公式生成签名哈希:
1
| HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
|
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用”.”分隔,就构成整个JWT对象。
10.2.3 JWT签名算法
JWT签名算法中,一般有两个选择:HS256和RS256。
HS256 (带有 SHA-256 的 HMAC )是一种对称加密算法, 双方之间仅共享一个密钥。由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密。
RS256 (采用SHA-256 的 RSA 签名) 是一种非对称加密算法, 它使用公共/私钥对: JWT的提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。
10.2.4 jjwt介绍
jjwt是一个提供JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。
jjwt的maven坐标:
1 2 3 4 5
| <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
|
10.4 jwt入门案例
本案例中会通过jjwt来生成和解析JWT令牌。
第一步:创建maven工程jwt_demo并配置pom.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.itcast</groupId> <artifactId>jwt_demo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.1.0</version> </dependency> </dependencies> </project>
|
第二步:编写单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
| package cn.itcast.test;
import cn.hutool.core.io.FileUtil; import io.jsonwebtoken.*; import org.junit.Test; import java.io.DataInputStream; import java.io.InputStream; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map;
public class JwtTest { @Test public void test1(){ Map<String, Object> headMap = new HashMap(); headMap.put("alg", "none"); headMap.put("typ", "JWT");
Map body = new HashMap(); body.put("userId","1"); body.put("username","xiaoming"); body.put("role","admin");
String jwt = Jwts.builder() .setHeader(headMap) .setClaims(body) .setId("jwt001") .compact(); System.out.println(jwt);
Jwt result = Jwts.parser().parse(jwt); Object jwtBody = result.getBody(); Header header = result.getHeader();
System.out.println(result); System.out.println(jwtBody); System.out.println(header); }
@Test public void test2(){ Map<String, Object> headMap = new HashMap(); headMap.put("alg", SignatureAlgorithm.HS256.getValue()); headMap.put("typ", "JWT");
Map body = new HashMap(); body.put("userId","1"); body.put("username","xiaoming"); body.put("role","admin");
String jwt = Jwts.builder() .setHeader(headMap) .setClaims(body) .setId("jwt001") .signWith(SignatureAlgorithm.HS256,"itcast") .compact(); System.out.println(jwt);
Jwt result = Jwts.parser().setSigningKey("itcast").parse(jwt); Object jwtBody = result.getBody(); Header header = result.getHeader();
System.out.println(result); System.out.println(jwtBody); System.out.println(header); }
@Test public void test3() throws Exception{ Map<String, Object> headMap = new HashMap(); headMap.put("alg", SignatureAlgorithm.RS256.getValue()); headMap.put("typ", "JWT");
Map body = new HashMap(); body.put("userId","1"); body.put("username","xiaoming"); body.put("role","admin");
String jwt = Jwts.builder() .setHeader(headMap) .setClaims(body) .setId("jwt001") .signWith(SignatureAlgorithm.RS256,getPriKey()) .compact(); System.out.println(jwt);
Jwt result = Jwts.parser().setSigningKey(getPubKey()).parse(jwt); Object jwtBody = result.getBody(); Header header = result.getHeader();
System.out.println(result); System.out.println(jwtBody); System.out.println(header); }
public PrivateKey getPriKey() throws Exception{ InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("pri.key"); DataInputStream dis = new DataInputStream(resourceAsStream); byte[] keyBytes = new byte[resourceAsStream.available()]; dis.readFully(keyBytes); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(spec); }
public PublicKey getPubKey() throws Exception{ InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("pub.key"); DataInputStream dis = new DataInputStream(resourceAsStream); byte[] keyBytes = new byte[resourceAsStream.available()]; dis.readFully(keyBytes); X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePublic(spec); }
@Test public void test4() throws Exception{ String password = "itcast";
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom secureRandom = new SecureRandom(password.getBytes()); keyPairGenerator.initialize(1024, secureRandom); KeyPair keyPair = keyPairGenerator.genKeyPair();
byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
FileUtil.writeBytes(publicKeyBytes, "d:\\pub.key"); FileUtil.writeBytes(privateKeyBytes, "d:\\pri.key"); } }
|
pd-tools-jwt底层是基于jjwt进行jwt令牌的生成和解析的。为了方便使用,在pd-tools-jwt模块中封装了两个工具类:JwtTokenServerUtils和JwtTokenClientUtils。
JwtTokenServerUtils主要是提供给权限服务的,类中包含生成jwt和解析jwt两个方法
JwtTokenClientUtils主要是提供给网关服务的,类中只有一个解析jwt的方法
需要注意的是pd-tools-jwt并不是starter,所以如果只是在项目中引入他的maven坐标并不能直接使用其提供的工具类。需要在启动类上加入pd-tools-jwt模块中定义的注解@EnableAuthServer或者@EnableAuthClient。
pd-tools-jwt使用的签名算法为RS256,需要我们自己的应用来提供一对公钥和私钥,然后在application.yml中进行配置即可。
具体使用过程:
第一步:创建maven工程myJwtApp并配置pom.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> </parent> <groupId>com.itheima</groupId> <artifactId>myJwtApp</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.itheima</groupId> <artifactId>pd-tools-jwt</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <exclusion> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
|
第二步:创建resources/keys目录,将通过RSA算法生成的公钥和私钥复制到此目录下

第三步: 创建application.yml文件
1 2 3 4 5 6 7 8
| server: port: 8080
authentication: user: expire: 3600 priKey: keys/pri.key pubKey: keys/pub.key
|
第四步:创建UserController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.itheima.controller;
import com.itheima.pinda.auth.server.utils.JwtTokenServerUtils; import com.itheima.pinda.auth.utils.JwtUserInfo; import com.itheima.pinda.auth.utils.Token; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@RestController @RequestMapping("/user") public class UserController { @Autowired private JwtTokenServerUtils jwtTokenServerUtils;
@GetMapping("/login") public Token login(){ String userName = "admin"; String password = "admin123";
JwtUserInfo jwtUserInfo = new JwtUserInfo(); jwtUserInfo.setName(userName); jwtUserInfo.setOrgId(10L); jwtUserInfo.setUserId(1L); jwtUserInfo.setAccount(userName); jwtUserInfo.setStationId(20L); Token token = jwtTokenServerUtils.generateUserToken(jwtUserInfo, null);
JwtUserInfo userInfo = jwtTokenServerUtils.getUserInfo(token.getToken()); System.out.println(userInfo); return token; } }
|
第五步:创建启动类
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.itheima;
import com.itheima.pinda.auth.server.EnableAuthServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @EnableAuthServer public class MyJwtApplication { public static void main(String[] args) { SpringApplication.run(MyJwtApplication.class,args); } }
|
启动项目,访问地址:http://localhost:8080/user/login

可以看到jwt令牌已经生成了。