JWT就是这么简单

温故而知新,可以为师矣。

JWT在JavaEE的开发中比比皆是。是什么让JWT那么出众,对程序员来说无非就是几个特点。

  • 简单

  • 好用

  • 安全

有以上几个特点、不愁没人用

JWT是什么

你(我)真的理解JWT是什么了吗?

首先JWT是一种标准。它不是框架,这个地方有很多人就混了。认为JWT就是框架是不对的。

按照官网提供的标准(RFC 7519)做出来的东西就可以叫做是JWT。换言之,我们也可以自己写一个JWT标准的框架。
JWT的标准有两种实现一种叫做JWS(JSON Web Signature),第二种叫做JWE(JSON Web Encryption)。

JWS(JSON Web Signature)

JWS就是我们通常所用的也是官方推荐使用的一种。

JWS分为:头部(Header)、载荷(payLoad)、签名(signature)

头部(Header)

头部通常由令牌的类型(typ)和签名的算法(alg)组成。

这里有个点没有人说过,这个typ类型是媒介类型(Media Type),可以认为是媒介类型、介质类型、设备类型等等都可以,用户可以根据令牌类型做不同的操作。例如有个项目是APP端和WEB端。那么这里就可以设置typ为APP或WEB。而官网中写了typ的媒介类型是JWT

{
"alg": "HS256",
"typ": "APP/WEB"
}

由上面的JSON数据进行base64编码就是JWT的头部数据。

载荷(Payload)

载荷是数据的主体部分

iss: 签发者
sub: 主题
aud: 接收者
exp(expires): 过期时间
iat(issued at): 签发时间
nbf(not before): 早于某个时间不处理
jti(JWT ID): 唯一标识

主体部分可添加公开数据和用户可公开的数据。

这里就有个问题了,什么是用户不可公开的数据。我们举个栗子:用户的余额、用户的密码、用户的隐私数据(女性的年龄)等等都是不可公开的数据。

{
  "iss": "朕水真水",
  "sub": "审批",
  "aud": "大牛",
  "userName": "朕水真水",
  "approve":"通过",
  "iat":"1597000000",
  "exp": 1597222222
}

由上面的JSON数据进行base64编码就是JWT的载荷数据。

签名(Signature)

签名则是使用算法加密:头部数据.载荷数据

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), "密钥/盐")

通过算法加密后的结果就是签名。

将头部、载荷、签名用点(.)连接起来得到的结果就是JWS完整的数据

安全性问题

到这里是不是会用人疑惑。数据使用base64进行编码header和payload。只要用base64解码载荷就能得到载荷保存的数据了。为什么这个字符串会安全?

别着急,因为我们的数据是公开数据以及用户可公开的数据。即使有人拿到了这个token,也只能解析出公开的数据。并不能拿到私密的数据。

而篡改头部或者载荷的数据是不可能通过校验的。因为篡改头部和载荷之后的base64编码的字符串和原本的不一样,在校验token的时候会将头部和载荷进行加密,和签名进行比较。还记得签名是怎么生成的吗?是是由 [头部数据.载荷数据]经过算法加密生成的。这样你就知道了为什么不能篡改头部和载荷的数据了吧。

JWE(JSON Web Encryption)

JWE表示使用基于JSON的数据结构的加密内容

JWE分为5个部分:头部(Header)、内容加密的密钥(Content Encryption Key简称CEK)、密钥加密(JWE Encrypted Key)、初始化向量(JWE Initialization Vector)、内容加密(JWE Ciphertext)

头部(JOSE header)

通常alg填写密钥加密方式,enc中填写报文的加密方式

{"alg":"RSA","enc":"AES128","typ":"JWE"}
加密内容算法
报文对称算法
密钥加密非对称算法
内容加密的密钥(Content Encryption Key简称CEK)

这个密钥是256位的随机CEK

密钥加密(JWE Encrypted Key)

将CEK进行公钥加密(非对称加密),加密后得到的结果。

初始化向量(JWE Initialization Vector)

生成随机的128位的JWE初始化向量

内容加密

使用CEK + 初始化向量作为密钥,使用AES128算法对报文执行身份验证加密。

对于内容加密可以附加身份校验的加密,即CEK+初始化向量+附加密钥。

这个附加身份校验可以是受保护的头部(JOSE Header),如:头部设置的加密方式(alg、enc)。进行base64编码后加到内容加密当中。这样可以防止重要的头部数据被恶意篡改。

这5个部分进行base64编码后使用点(.)连接就是JWE最终的数据。

{
    "protected": "base64(header受保护部分)",
    "unprotected": {
        "typ":"JWE"
    },
    "header": {
		"alg":"RSA",
		"enc":"AES128",
		"typ":"JWE"
    },
    "encrypted_key": "base64(被加密后的CEK)",
    "iv": "base64(随机128位二进制)",
    "ciphertext": "base64(AES加密后的内容)",
    "tag": "base64(AES生成的128位的认证标记)"
}
解析

有人就要问了,JWE是怎么解析数据的呢?

其实我们可以推导出来。

  1. 将5个部分用点(.)拆开进行base64解码
  2. 将加密后的CEK进行(私钥)解密,得到CEK。
  3. 使用CEK+初始化向量(如果由附加身份校验则加上)对加密后的内容解密
  4. 得到内容数据
优点
  • 经过2次加密,数据更加安全

  • 直接对报文加密,数据准确

  • 密钥CEK是随机生成,每次签发都不同更难被破解

### JWT 简单实现方法 JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在网络应用环境间安全地传输信息。通过使用签名机制,JWT 可以确保数据的完整性和真实性,广泛应用于身份验证和信息交换场景。 #### 实现步骤 1. **定义 Header 和 Payload** JWT 的结构由三部分组成:Header、Payload 和 Signature。其中 Header 包含令牌类型和使用的加密算法;Payload 是有效载荷,包含声明(claims)。声明分为注册声明、公共声明和私有声明。例如: - `iss`(签发者) - `exp`(过期时间) - `sub`(主题) - 自定义字段如 `userId` 或 `userName` [^2] 示例代码如下: ```python import jwt from datetime import datetime, timedelta # 定义 header header = { "alg": "HS256", "typ": "JWT" } # 定义 payload payload = { "userId": "123", "userName": "zhangsan", "iss": "example.com", "exp": datetime.utcnow() + timedelta(hours=1), "iat": datetime.utcnow() } ``` 2. **生成签名** 使用指定的密钥对 Header 和 Payload 进行签名,生成最终的 JWT 字符串。在 Python 中可以使用 `PyJWT` 库来简化这一过程 [^2] ```python secret_key = 'your_secret_key' # 生成 token token = jwt.encode(payload, secret_key, algorithm='HS256', headers=header) print("Generated JWT:", token) ``` 3. **解析与验证 JWT** 在接收到客户端发送的 JWT 后,服务器需要对其进行解码并验证其有效性。这包括检查签名是否正确以及是否已过期等条件 [^1] ```python try: # 解析 token decoded_token = jwt.decode(token, secret_key, algorithms=['HS256']) print("Decoded JWT:", decoded_token) except jwt.ExpiredSignatureError: print("Token has expired") except jwt.InvalidTokenError: print("Invalid token") ``` 4. **集成到 Web 应用中** 将 JWT 集成到 Web 应用程序中通常涉及以下几个方面: - **登录接口**:用户提交用户名和密码后,系统验证成功则返回一个 JWT。 - **中间件校验**:对于受保护的资源,每次请求都需要携带有效的 JWT,并在校验通过后才允许访问相关资源 [^4] 下面是一个简单的 Flask 应用示例,展示如何创建登录接口并设置 JWT 校验中间件: ```python from flask import Flask, request, jsonify from functools import wraps app = Flask(__name__) SECRET_KEY = 'your_secret_key' def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = request.headers.get('Authorization') if not token: return jsonify({'message': 'Token is missing!'}), 403 try: data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) current_user_id = data['userId'] except Exception as e: return jsonify({'message': 'Token is invalid!', 'error': str(e)}), 403 return f(current_user_id, *args, **kwargs) return decorated @app.route('/login', methods=['POST']) def login(): auth = request.authorization if auth and auth.password == 'password': # 假设的认证逻辑 payload = { 'userId': auth.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30), 'iat': datetime.datetime.utcnow() } token = jwt.encode(payload, SECRET_KEY, algorithm='HS256') return jsonify({'token': token}) return jsonify({'message': 'Could not verify!', 'WWW-Authenticate': 'Basic realm="Login required!"'}), 401 @app.route('/protected', methods=['GET']) @token_required def protected_route(current_user_id): return jsonify({'message': f'This is only available for authenticated users! Current user ID: {current_user_id}'}) if __name__ == '__main__': app.run(debug=True) ``` #### 注意事项 - **安全性**:选择合适的签名算法(如 HS256 或 RS256),并且妥善保管好密钥或私钥。 - **时效性**:合理设置 `exp` 参数,避免长时间有效的令牌带来的风险。 - **刷新机制**:考虑引入刷新令牌机制,以便在不重新登录的情况下更新访问令牌 [^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值