深入浅出JSON Web Token(JWT)认证机制:概念与具体实现demo
2024-08-19 14:10 阅读(298)

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就像是一个神奇的通行证,它携带着用户的身份信息,让用户可以自由地在我们的应用中畅游。但是,就像任何强大的工具一样,我们需要正确地使用它,充分发挥它的优势,同时避免潜在的风险。