JWT | 一分钟掌握JWT | 概念及实例

JWT是一种基于RFC7519标准的安全信息传输方式,常用于授权和信息交换。它由Header、Payload和Signature三部分组成,通过Base64编码和签名确保数据安全。JWT的优点包括可扩展性和符合RESTfulAPI的无状态原则,但其不适用于存储敏感数据,且一旦签发无法修改。文章还介绍了JWT的Java实现和开源框架jjwt。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是JWT

JWT的全称是Json Web Token。是基于RFC 7519开放标准的,它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息可以用作验证和相互信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSAECDSA 的公钥/私钥对进行签名。

哪里能用JWT

以下是JWT两种使用场景:

  • 授权:这是使用 JWT 的最常见的使用场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用。
  • 信息交换:JWT是在各方之间安全传输信息的比较便捷的方式。由于 JWT 可以签名(例如,使用公钥/私钥对),因此可以确定发送者是否是在您的授权范围之内。并且,由于签名是使用标头和有效负载计算的,因此还可以验证内容是否未被篡改。

JWT的组成

这是一个JWT的token串:


java

复制代码

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

很复杂,看不懂是不是?其实这一串是经过加密之后的密文字符串,中间通过.来分割。每个.之前的字符串分别表示JWT的三个组成部分:HeaderPayloadSignature

Header(头信息)

Header的主要作用是用来标识。通常是两部分组成:

  • typ:type 的简写,令牌类型,也就是JWT。
  • alg:Algorithm 的简写,加密签名算法。一般使用HS256,jwt官网提供了12种的加密算法,截图如下:

Header的明文示例:


json

复制代码

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

经过Base64编码之后的明文,变为:


java

复制代码

eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9

也就是第一个.之前的密文串。以下是Header部分常用部分的声明:

keyname说明
typ令牌类型如果存在,则必须将其设置为已注册的 IANA 媒体类型。
cty内容类型如果使用嵌套签名或加密,建议将其设置为 ;否则,请省略此字段。
alg消息身份验证代码算法发行者可以自由设置算法来验证令牌上的签名。但是,某些受支持的算法不安全。
kid密钥标识指示客户端用于生成令牌签名的密钥的提示。服务器将此值与文件上的密钥匹配,以验证签名是否有效以及令牌是否真实。
x5cx.509 证书链RFC4945 格式的证书链,对应于用于生成令牌签名的私钥。服务器将使用此信息来验证签名是否有效以及令牌是否真实。
x5ux.509 证书链网址服务器可在其中检索与用于生成令牌签名的私钥对应的证书链的 URL。服务器将检索并使用此信息来验证签名是否真实。
crit危急服务器必须理解的标头列表,以便接受令牌为有效令牌

Payload(负载信息)

也称为JWT claims,放置需要传输的信息,有三类:

  • 保留claims:主要包括iss发行者、exp过期时间、sub主题、aud用户等。
  • 公共claims:定义新创的信息,比如用户信息和其他重要信息。
  • 私有claims:用于发布者和消费者都同意以私有的方式使用的信息。

以下是Payload的官方定义内容:

keyname说明
iss发送者标识颁发 JWT 的发送主体。
sub主题标识 JWT 的主题。
aud接收者标识 JWT 所针对的接收者。每个在处理 JWT 的主体都必须使用受众声明中的值来标识自己。如果处理的主体在存在此声明时未将自己标识为声明中的值,则必须拒绝 JWT。
exp到期时间标识不得接受 JWT 进行处理的过期时间。该值必须是日期类型,而且是1970-01-01 00:00:00Z 之后的日期秒。
nbfjwt的开始处理的时间标识 JWT 开始接受处理的时间。该值必须是日期。
iatjwt发出的时间标识 JWT 的发出的时间。该值必须是日期。
jtijwt id令牌的区分大小写的唯一标识符,即使在不同的颁发者之间也是如此。

Payload明文示例:


json

复制代码

{ "sub": "12344321", "name": "Mars酱", // 私有claims "iat": 1516239022 }

经过Base64加密之后的明文,变为:


java

复制代码

eyJzdWIiOiIxMjM0NDMyMSIsIm5hbWUiOiJNYXJz6YWxIiwiaWF0IjoxNTE2MjM5MDIyfQ

也就是第一个.和第二个. 之间的密文串内容。

Signatrue(签名信息)

Signature 部分是对Header和Payload两部分的签名,作用是防止 JWT 被篡改。这个部分的生成规则主要是是公式(伪代码)是:


java

复制代码

Header中定义的签名算法( base64编码(header) + "." + base64编码(payload), secret )

secret是存放在服务端加密使用到的盐。

得到签名之后,把Header的密文、Payload的密文、Signatrue的密文按顺序拼接成为一个字符串,中间通过.来连接并分割,整个串就是JWT了。

JWT实例

概念讲完了,我们来个实例吧,先来一个jwt的编码:


java

复制代码

public static String encodeJWT(String key) { // 1. 定义header部分内容 Map headerMap = new HashMap(); headerMap.put("alg", SignatureAlgorithm.HS256.getValue()); headerMap.put("typ", "JWT"); // 2. 定义payload部分内容 Map payloadMap = new HashMap(); payloadMap.put("sub", "mars酱让你爽一分钟"); payloadMap.put("iat", UUID.randomUUID()); payloadMap.put("exp", System.currentTimeMillis() + 24 * 60 * 60 * 1000); payloadMap.put("name", "Mars酱"); payloadMap.put("role", "酱油王"); // 3.生成token String jwtToken = Jwts.builder() .setHeaderParams(headerMap) .setClaims(payloadMap) .signWith(SignatureAlgorithm.HS256, key) .compact(); // 拼接header + payload // System.out.println(jwtToken); return jwtToken; }

我们在main函数中调用这个,运行得到结果:

很长的密文字符串,就不截完整了。其中key是盐,jsonwebtoken的jar包规定,key必须字节数要大于等于你所用的加密算法的最小字节数,mars酱这里使用的是HS256,最小的key长度规定的就是256:

因此,key值我这里传入了一个超长的key:


java

复制代码

String key = "marsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmars";

如果你的key没这么长,你可能会报这样错误:

下面再来一个解密jwt的代码,入参为加密后的jwt和盐key:


java

复制代码

public static void decodeJWT(String jwtToken, String key) { try { Claims claims = Jwts.parser() .setSigningKey(key) .parseClaimsJws(jwtToken) .getBody(); Object sub = claims.get("sub"); Object name = claims.get("name"); Object role = claims.get("role"); Object exp = claims.get("exp"); System.out.println("sub:" + sub + "\nname:" + name.toString() + "\nrole:" + role + "\nexp:" + exp + "\n失效了?" + ((System.currentTimeMillis() - (Long)exp) > 0)); } catch (ExpiredJwtException e) { System.out.println("mars酱提醒您:token已过期"); } }

在main中调用后,得到结果:

把生成的jwt字符串放入官网( jwt.io )的解密界面,和程序解密的结果一样,是不是很完美?

优缺点

好了,概念说完了,实例也给了,想想jwt有什么优缺点?我总结了一下:

优点:

  1. 可扩展性好 应用程序分布式部署的情况下,如果使用session机制,那就要要做多台机器的数据共享,通常可以存在数据库或者redis里面。而使用jwt不需要共享。
  2. jwt是无状态的 jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。

缺点:

  1. 安全性 由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。
  2. 一次性 无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。

jwt开源框架

mars酱这里使用的jwt是io.jsonwebtoken的,它需要在pom中引入依赖:


xml

复制代码

<!-- jwt api定义 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.10.2</version> </dependency> <!-- jwt api impl实现 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.10.2</version> </dependency> <!-- jwt json --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.10.2</version> </dependency>

其余的jwt框架全在jwt官网列举了。基本覆盖了全语言

好好享受jwt的校验过程吧

作者:Mars酱
链接:https://juejin.cn/post/7232550589964140602

### 集成 JWT 身份验证到 Django Rest Framework #### 安装必要的依赖项 为了在 Django Rest Framework 中使用 JWT 进行身份验证,需要先安装 `djangorestframework` 和 `django-rest-framework-jwt` 的相关包。可以通过 pip 来完成这些操作[^1]。 ```bash pip install djangorestframework pip install django-rest-framework-jwt ``` #### 修改项目的 settings.py 文件 在项目的 `settings.py` 文件中,添加以下配置来启用 JWT 认证: - 将 DRF 添加到已安装的应用程序列表中。 - 设置默认的身份验证类为 `JSONWebTokenAuthentication`。 以下是具体的配置示例: ```python INSTALLED_APPS = [ ... 'rest_framework', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 启用JWT认证 ), } ``` 此外,在同一文件中还需要定义一些与 JWT 相关的参数,例如令牌的有效期、签名算法等。可以参考如下设置[^3]: ```python import datetime JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300), # Token有效期设为5分钟 'JWT_ALLOW_REFRESH': True, # 是否允许刷新token 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), # 刷新token的最大期限 'JWT_SECRET_KEY': SECRET_KEY # 使用Django的SECRET_KEY作为加密密钥 } ``` #### 创建视图处理登录请求 创建一个视图函数或者 APIView 类型的视图用来接收用户的登录请求并返回相应的 JWT。通常情况下可以直接利用 rest_framework_jwt 自带的功能[^2]。 下面是一个简单的例子展示如何通过用户名密码获取 token: ```python from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import AllowAny from rest_framework_jwt.settings import api_settings class ObtainJWTView(APIView): permission_classes = (AllowAny,) def post(self, request, *args, **kwargs): serializer = YourCustomSerializer(data=request.data) if serializer.is_valid(): user = serializer.object.get('user') or request.user jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return Response({'token': token}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) ``` 注意这里假设存在名为 `YourCustomSerializer` 的序列化器负责校验输入数据以及提取用户实例。 #### 处理 Token 黑名单功能 如果希望进一步提升系统的安全性,则可以在登出时将使用的 tokens 加入黑名单防止重复使用。这一步骤涉及数据库模型的设计和额外逻辑编写。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值