1. 引言
大家好!今天我们要聊一个在现代Web开发中非常流行的话题 - JSON Web Token,简称JWT。听起来很高大上,对吧?别担心,我们会把它掰开揉碎,让每个人都能轻松理解。
那么,JWT到底是个什么东西呢?
简单来说,JWT就像是一个神奇的通行证。想象你去一个主题公园玩,在入口处买了票,工作人员给了你一个特殊的手环。这个手环不仅证明你已经付了钱,还可能包含一些其他信息,比如你的姓名、年龄,甚至是你购买的是哪种套餐。在园区里的任何地方,工作人员只要看一眼你的手环,就知道你是谁,你可以玩哪些项目。这个手环,就相当于我们要讲的JWT。
在Web开发中,JWT主要用于身份认证和信息交换。当用户登录后,服务器不需要将用户信息存在服务器端(比如session),而是直接将用户信息安全地放在JWT中返回给客户端。客户端在后续的请求中带上这个JWT,服务器就能识别用户身份了。
2. JWT的结构
JWT看起来是一长串乱七八糟的字符,但其实它是有明确结构的。一个典型的JWT由三部分组成,每部分之间用点(.)分隔:
xxxxx.yyyyy.zzzzz
这三个部分分别是:
Header(头部)
Payload(负载)
Signature(签名)
让我们逐个来看看这些部分:
2.1 Header
Header通常由两部分组成:token的类型(即JWT)和使用的哈希算法(如HMAC SHA256或RSA)。
一个典型的Header可能长这样:
{
"alg": "HS256",
"typ": "JWT"
}
这个JSON会被Base64Url编码,形成JWT的第一部分。
2.2 Payload
这部分包含了声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。有三种类型的声明:registered, public 和 private claims。
一个Payload可能长这样:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
这个JSON同样会被Base64Url编码,形成JWT的第二部分。
2.3 Signature
要创建签名部分,你必须获取编码的header、编码的payload、一个秘钥,然后使用header中指定的算法进行签名。
签名的伪代码可能是这样的:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
这个签名用于验证消息在传输过程中没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方就是它所说的那个人。
3.JWT的工作原理
现在我们知道JWT长什么样子了,那它是怎么工作的呢?让我们通过一个简单的例子来说明:
用户使用用户名和密码请求登录
服务器验证用户凭证
服务器创建一个JWT并发送给客户端
客户端存储JWT(通常在本地存储中)
客户端在后续请求中发送JWT(通常在Authorization header中)
服务器验证JWT并返回受保护的资源
用代码来表示可能是这样的:
import jwt
# 登录并获取JWT
def login(username, password):
if check_credentials(username, password):
payload = {
"sub": username,
"exp": datetime.utcnow() + timedelta(minutes=30)
}
token = jwt.encode(payload, "secret", algorithm="HS256")
return token
else:
return "Invalid credentials"
# 使用JWT访问受保护的资源
def access_protected_resource(token):
try:
payload = jwt.decode(token, "secret", algorithms=["HS256"])
return "Welcome, " + payload["sub"]
except jwt.ExpiredSignatureError:
return "Token has expired"
except jwt.InvalidTokenError:
return "Invalid token"
# 使用示例
token = login("john", "password123")
print(access_protected_resource(token))
是不是感觉突然就明白了?JWT就是这样工作的!
4. JWT vs Session:两种身份认证方式的对比
现在我们了解了JWT是如何工作的,你可能会问:"为什么不继续使用传统的session呢?"好问题!让我们来比较一下这两种身份认证方式。
4.1 Session的工作方式
首先,让我们回顾一下传统session的工作方式:
用户登录成功后,服务器创建一个session,并将session_id存储在数据库或内存中。
服务器将session_id发送给客户端,通常以cookie的形式。
客户端在后续请求中携带这个cookie。
服务器接收到请求后,根据session_id查找对应的session数据。
服务器验证session是否有效,然后处理请求。
4.2 JWT vs Session:主要区别
现在,让我们来对比一下JWT和Session:
存储位置
Session:数据存储在服务器端。
JWT:数据存储在客户端。
可扩展性
Session:因为数据存储在服务器,在分布式系统中需要额外的同步机制。
JWT:天生适合分布式系统,因为所有数据都在token中。
性能
Session:每次请求都需要查询数据库或内存。
JWT:不需要查询数据库,但需要解码和验证token。
RESTful API
Session:不太符合RESTful的无状态原则。
JWT:非常适合RESTful API,因为它是无状态的。
安全性
Session:较为安全,因为数据存储在服务器。但需要防范session劫持。
JWT:安全性取决于如何使用。需要注意不要在JWT中存储敏感信息。
让我们用一个简单的比喻来总结:
想象你去健身房。使用Session就像是健身房给你一个储物柜钥匙,你的个人物品(数据)存在储物柜里(服务器)。而使用JWT,则像是健身房直接给你一个背包,你的所有物品(数据)都随身携带。
5. JWT的潜在风险和注意事项
但是,在使用JWT时,我们需要注意以下几点:
不要在JWT中存储敏感信息:JWT的payload是Base64编码的,而不是加密的。任何人都可以解码查看其中的内容。
使用HTTPS:虽然JWT本身是安全的,但如果通过不安全的通道传输,还是可能被截获。
设置合理的过期时间:JWT通常有一个过期时间,这个时间不应该设置得太长,以减少被盗用的风险。
妥善保管密钥:用于签名JWT的密钥必须妥善保管,一旦泄露,整个系统的安全性就会受到威胁。
考虑刷新机制:对于长期使用的应用,可以考虑使用刷新token的机制,而不是简单地延长JWT的有效期。
代码示例:
import jwt
from datetime import datetime, timedelta
# 创建一个带有刷新机制的JWT系统
def create_tokens(user_id):
access_token_payload = {
"user_id": user_id,
"exp": datetime.utcnow() + timedelta(minutes=15)
}
refresh_token_payload = {
"user_id": user_id,
"exp": datetime.utcnow() + timedelta(days=7)
}
access_token = jwt.encode(access_token_payload, "access_secret", algorithm="HS256")
refresh_token = jwt.encode(refresh_token_payload, "refresh_secret", algorithm="HS256")
return access_token, refresh_token
def refresh_access_token(refresh_token):
try:
payload = jwt.decode(refresh_token, "refresh_secret", algorithms=["HS256"])
new_access_token_payload = {
"user_id": payload["user_id"],
"exp": datetime.utcnow() + timedelta(minutes=15)
}
new_access_token = jwt.encode(new_access_token_payload, "access_secret", algorithm="HS256")
return new_access_token
except jwt.ExpiredSignatureError:
return "Refresh token has expired. Please log in again."
except jwt.InvalidTokenError:
return "Invalid refresh token"
# 使用示例
access_token, refresh_token = create_tokens(123)
print("Access Token:", access_token)
print("Refresh Token:", refresh_token)
# 假设access_token过期后
new_access_token = refresh_access_token(refresh_token)
print("New Access Token:", new_access_token)
6. JWT实现Demo
说了这么多理论,是时候动手实践一下了!让我们来实现一个简单的JWT认证系统。我们将使用Python和Flask框架来创建一个小型的Web应用,演示JWT的使用。
首先,确保你已经安装了必要的库:
pip install flask flask-jwt-extended
然后,让我们开始编写我们的应用:
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
app = Flask(__name__)
# 设置一个密钥,用于签名JWT
app.config['JWT_SECRET_KEY'] = 'super-secret' # 在实际应用中,请使用更安全的密钥!
jwt = JWTManager(app)
# 模拟的用户数据库
users = {
'alice': 'password123',
'bob': 'qwerty456'
}
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username', None)
password = request.json.get('password', None)
if username not in users or users[username] != password:
return jsonify({"message": "Bad username or password"}), 401
# 创建JWT Token
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token)
@app.route('/protected', methods=['GET'])
@jwt_required() # 这个装饰器要求请求必须带有有效的JWT
def protected():
# 获取当前用户的身份
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
if __name__ == '__main__':
app.run(debug=True)
这个简单的应用实现了两个端点:
/login:用户可以通过提供用户名和密码来获取JWT。
/protected:一个受保护的资源,只有携带有效JWT的请求才能访问。
让我们来测试一下这个应用。你可以使用curl或者Postman来发送请求:
# 登录并获取token
curl -H "Content-Type: application/json" -X POST \
-d '{"username":"alice","password":"password123"}' \
http://localhost:5000/login
# 使用获得的token访问受保护的资源
curl -H "Authorization: Bearer <your_token_here>" \
http://localhost:5000/protected
这个demo展示了JWT的基本使用流程:
用户提供凭证(用户名和密码)进行身份验证。
服务器验证凭证,并生成JWT。
客户端在后续请求中使用JWT来访问受保护的资源。
服务器验证JWT并允许访问。
这只是一个简单的示例,实际应用中你可能需要考虑更多的因素,比如token刷新、错误处理、更复杂的权限控制等。但是这个demo应该能让你对JWT的实际使用有一个基本的了解。
总结
好了,我们的JWT之旅到此结束了。我们了解了JWT是什么,它的结构是怎样的,它如何工作,以及使用它的优势和需要注意的事项。
记住,JWT就像是一个神奇的通行证,它携带着用户的身份信息,让用户可以自由地在我们的应用中畅游。但是,就像任何强大的工具一样,我们需要正确地使用它,充分发挥它的优势,同时避免潜在的风险。