前言
通过 JWT
配合 Spring Security OAuth2
使用的方式,可以避免 每次请求 都 远程调度 认证授权服务。资源服务器 只需要从 授权服务器 验证一次,返回 JWT
。返回的 JWT
包含了 用户 的所有信息,包括 权限信息。
正文
1. 什么是JWT
JSON Web Token
(JWT
)是一种开放的标准(RFC 7519
),JWT
定义了一种 紧凑 且 自包含 的标准,旨在将各个主体的信息包装为 JSON
对象。主体信息 是通过 数字签名 进行 加密 和 验证 的。经常使用 HMAC
算法或 RSA
(公钥/私钥 的 非对称性加密)算法对 JWT
进行签名,安全性很高。
-
紧凑型:数据体积小,可通过
POST
请求参数 或HTTP
请求头 发送。 -
自包含:
JWT
包含了主体的所有信息,避免了 每个请求 都需要向Uaa
服务验证身份,降低了 服务器的负载。
2. JWT的结构
JWT
的结构由三部分组成:Header
(头)、Payload
(有效负荷)和 Signature
(签名)。因此 JWT
通常的格式是 xxxxx.yyyyy.zzzzz
。
2.1. Header
Header
通常是由 两部分 组成:令牌的 类型(即 JWT
)和使用的 算法类型,如 HMAC
、SHA256
和 RSA
。例如:
{
"typ": "JWT",
"alg": "HS256"
}
将 Header
用 Base64
编码作为 JWT
的 第一部分,不建议在 JWT
的 Header
中放置 敏感信息。
2.2. Payload
第二部分 Payload
是 JWT
的 主体内容部分,它包含 声明 信息。声明是关于 用户 和 其他数据 的声明。
声明有三种类型: registered
、public
和 private
。
- Registered claims
JWT
提供了一组 预定义 的声明,它们不是 强制的,但是推荐使用。JWT
指定 七个默认 字段供选择:
注册声明 | 字段含义 |
---|---|
iss | 发行人 |
exp | 到期时间 |
sub | 主题 |
aud | 用户 |
nbf | 在此之前不可用 |
iat | 发布时间 |
jti | 用于标识JWT的ID |
-
Public claims:可以随意定义。
-
Private claims:用于在 同意使用 它们的各方之间 共享信息,并且不是 注册的 或 公开的 声明。
下面是 Payload
部分的一个示例:
{
"sub": "123456789",
"name": "John Doe",
"admin": true
}
将 Payload
用 Base64
编码作为 JWT
的 第二部分,不建议在 JWT
的 Payload
中放置 敏感信息。
2.3. Signature
要创建签名部分,需要利用 秘钥 对 Base64
编码后的 Header
和 Payload
进行 加密,加密算法的公式如下:
HMACSHA256(
base64UrlEncode(header) + '.' +
base64UrlEncode(payload),
secret
)
签名 可以用于验证 消息 在 传递过程 中有没有被更改。对于使用 私钥签名 的 token
,它还可以验证 JWT
的 发送方 是否为它所称的 发送方。
3. JWT的工作方式
客户端 获取 JWT
后,对于以后的 每次请求,都不需要再通过 授权服务 来判断该请求的 用户 以及该 用户的权限。在微服务系统中,可以利用 JWT
实现 单点登录。认证流程图如下:
4. 案例工程结构
-
eureka-server:作为 注册服务中心,端口号为
8761
。这里不再演示搭建。 -
auth-service:作为 授权服务,授权 需要用户提供 客户端 的
client Id
和Client Secret
,以及 授权用户 的username
和password
。这些信息 准备无误 之后,auth-service
会返回JWT
,该JWT
包含了用户的 基本信息 和 权限点信息,并通过RSA
私钥 进行加密。 -
user-service:作为 资源服务,它的 资源 被保护起来,需要相应的 权限 才能访问。
user-service
服务得到 用户请求 的JWT
后,先通过 公钥 解密JWT
,得到JWT
对应的 用户信息 和 用户权限信息,再通过Spring Security
判断该用户是否有 权限 访问该资源。
工程原理示意图如下:
5. 构建auth-service授权服务
- 新建一个
auth-service
项目模块,完整的pom.xml
文件配置如下:
<?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>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.github.ostenant.springcloud</groupId>
<artifactId>auth-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--防止jks文件被mavne编译导致不可用-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>