MK
摩柯社区 - 一个极简的技术知识社区
AI 面试
基于JWT的用户权限管理系统
2022-06-276.7k 阅读

基于 JWT 的用户权限管理系统

JWT 基础

JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。这些信息可以被验证和信任,因为它们是经过数字签名的。JWT 通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

头部

头部通常由两部分组成:令牌的类型(即 JWT)和所使用的签名算法,例如 HMAC SHA256 或 RSA。以下是一个头部的示例 JSON 对象:

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

这个 JSON 对象会使用 Base64Url 编码,形成 JWT 的第一部分。

载荷

载荷是 JWT 的第二部分,它包含声明(claims)。声明是关于实体(通常是用户)和其他数据的陈述。有三种类型的声明:注册声明(如 iss、exp、sub 等)、公共声明和私有声明。

以下是一个载荷的示例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

同样,这个 JSON 对象会被 Base64Url 编码,形成 JWT 的第二部分。

签名

要创建签名部分,需要使用编码后的头部、编码后的载荷、一个密钥(secret)和头部中指定的签名算法。例如,如果使用 HMAC SHA256 算法,签名将按如下方式创建:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证消息在传输过程中没有被更改,并且在使用私钥签名的情况下,还可以验证 JWT 的发送者的身份。

在后端开发中使用 JWT 进行用户认证

生成 JWT

在大多数后端语言中,都有相应的库来处理 JWT。以 Python 为例,使用 PyJWT 库可以轻松生成 JWT。首先,确保安装了 PyJWT

pip install PyJWT

以下是生成 JWT 的代码示例:

import jwt
import datetime

# 密钥,应该保密
SECRET_KEY = "your_secret_key"

def generate_token(user_id):
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

在上述代码中,我们定义了一个 generate_token 函数,它接受用户 ID 作为参数,并生成一个包含用户 ID、签发时间(iat)和过期时间(exp)的 JWT。

验证 JWT

验证 JWT 同样重要,以确保接收到的令牌是有效的。继续以 Python 为例:

def verify_token(token):
    try:
        data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return data
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

verify_token 函数中,我们尝试解码接收到的 JWT。如果令牌过期,会抛出 ExpiredSignatureError 异常;如果令牌无效,会抛出 InvalidTokenError 异常。如果解码成功,返回解码后的数据。

用户权限管理

基于角色的权限管理(RBAC)

在许多应用程序中,基于角色的权限管理(RBAC)是一种常见的权限管理方式。在基于 JWT 的系统中,我们可以在 JWT 的载荷中添加角色信息。

例如,假设用户有“admin”和“user”两种角色,生成 JWT 时可以这样添加角色信息:

def generate_token(user_id, role):
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
        "role": role
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

在验证令牌后,我们可以根据角色来判断用户是否有权限执行某些操作。例如,只有“admin”角色的用户才能删除用户数据:

def can_delete_user(token):
    data = verify_token(token)
    if data and data.get("role") == "admin":
        return True
    return False

基于资源的权限管理(RBAC)

除了基于角色的权限管理,还可以基于资源进行权限管理。在这种情况下,我们可以在 JWT 载荷中添加用户对特定资源的权限信息。

例如,假设用户对文章资源有“read”和“write”权限,生成 JWT 时可以这样添加权限信息:

def generate_token(user_id, article_permissions):
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
        "article_permissions": article_permissions
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

在验证令牌后,我们可以根据权限信息来判断用户是否有权限对文章执行相应操作:

def can_write_article(token):
    data = verify_token(token)
    if data and "write" in data.get("article_permissions", []):
        return True
    return False

整合到 Web 应用程序

Flask 示例

Flask 是一个流行的 Python Web 框架。以下是如何在 Flask 应用程序中整合基于 JWT 的用户权限管理系统。

首先,安装必要的库:

pip install Flask PyJWT

然后,创建一个简单的 Flask 应用程序:

from flask import Flask, request, jsonify
import jwt
import datetime

app = Flask(__name__)
SECRET_KEY = "your_secret_key"

def generate_token(user_id, role):
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
        "role": role
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

def verify_token(token):
    try:
        data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return data
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    user_id = data.get('user_id')
    role = data.get('role')
    if user_id and role:
        token = generate_token(user_id, role)
        return jsonify({"token": token})
    return jsonify({"message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    token = request.headers.get('Authorization')
    if not token:
        return jsonify({"message": "Token is missing"}), 401
    token = token.replace('Bearer ', '')
    data = verify_token(token)
    if not data:
        return jsonify({"message": "Invalid token"}), 401
    role = data.get('role')
    if role == "admin":
        return jsonify({"message": "This is a protected route for admin"})
    return jsonify({"message": "This is a protected route for regular user"})

if __name__ == '__main__':
    app.run(debug=True)

在上述示例中,我们创建了一个 /login 路由,用于生成 JWT。用户提供 user_idrole 进行登录,成功后返回 JWT。/protected 路由用于验证 JWT,并根据用户角色返回不同的消息。

Node.js Express 示例

在 Node.js 中,Express 是一个广泛使用的 Web 框架。以下是如何在 Express 应用程序中整合基于 JWT 的用户权限管理系统。

首先,安装必要的库:

npm install express jsonwebtoken

然后,创建一个简单的 Express 应用程序:

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const SECRET_KEY = "your_secret_key";

app.use(express.json());

function generateToken(user_id, role) {
    const payload = {
        sub: user_id,
        iat: new Date().getTime(),
        exp: new Date().getTime() + 1000 * 60 * 30,
        role: role
    };
    return jwt.sign(payload, SECRET_KEY, { algorithm: 'HS256' });
}

function verifyToken(token) {
    try {
        return jwt.verify(token, SECRET_KEY);
    } catch (error) {
        if (error.name === 'TokenExpiredError') {
            return null;
        }
        return null;
    }
}

app.post('/login', (req, res) => {
    const { user_id, role } = req.body;
    if (user_id && role) {
        const token = generateToken(user_id, role);
        res.json({ token });
    } else {
        res.status(401).json({ message: 'Invalid credentials' });
    }
});

app.get('/protected', (req, res) => {
    const token = req.headers.authorization;
    if (!token) {
        return res.status(401).json({ message: 'Token is missing' });
    }
    const cleanToken = token.replace('Bearer ', '');
    const data = verifyToken(cleanToken);
    if (!data) {
        return res.status(401).json({ message: 'Invalid token' });
    }
    const role = data.role;
    if (role === 'admin') {
        res.json({ message: 'This is a protected route for admin' });
    } else {
        res.json({ message: 'This is a protected route for regular user' });
    }
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在这个 Express 示例中,同样创建了 /login 路由用于生成 JWT,/protected 路由用于验证 JWT 并根据用户角色返回不同的消息。

JWT 的安全性考虑

密钥管理

JWT 的安全性在很大程度上依赖于密钥的保密性。密钥应该足够长且随机,并且只能在服务器端存储,绝对不能暴露在客户端代码中。建议定期更换密钥,以增加安全性。

防止重放攻击

虽然 JWT 本身不具备防止重放攻击的机制,但可以通过在令牌中添加唯一标识符(如 jti 声明)并在服务器端维护一个已使用令牌的黑名单来实现。每次验证令牌时,检查该令牌是否在黑名单中。

过期时间设置

合理设置 JWT 的过期时间非常重要。如果过期时间过长,一旦令牌泄露,攻击者将有较长时间利用该令牌。如果过期时间过短,用户可能会频繁需要重新登录,影响用户体验。应根据应用程序的安全需求和用户使用场景来合理设置过期时间。

跨域问题

在现代 Web 应用程序中,跨域是一个常见的问题。当使用 JWT 进行用户认证和权限管理时,也需要考虑跨域问题。

CORS 配置

在后端服务器中,可以通过配置 CORS(Cross - Origin Resource Sharing)来允许跨域请求。以 Flask 为例,可以使用 flask_cors 库:

pip install flask_cors

然后在 Flask 应用程序中进行如下配置:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

在 Express 中,可以使用 cors 中间件:

npm install cors

然后在 Express 应用程序中进行如下配置:

const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());

跨域传递 JWT

当进行跨域请求时,需要确保 JWT 能够正确传递。一种常见的方式是将 JWT 放在请求头中,例如 Authorization: Bearer <token>。在前端,需要确保在跨域请求中包含这个请求头。

例如,在使用 fetch 进行跨域请求时:

const token = localStorage.getItem('token');
fetch('http://api.example.com/protected', {
    method: 'GET',
    headers: {
        'Authorization': `Bearer ${token}`
    }
})
.then(response => response.json())
.then(data => console.log(data));

性能优化

缓存 JWT 验证结果

在高流量的应用程序中,频繁验证 JWT 可能会带来性能开销。可以考虑缓存 JWT 的验证结果。例如,在 Python 中使用 functools.lru_cache 对验证函数进行缓存:

import jwt
import datetime
from functools import lru_cache

SECRET_KEY = "your_secret_key"

@lru_cache(maxsize=128)
def verify_token(token):
    try:
        data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return data
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

在 Node.js 中,可以使用 node - cache 库来实现类似的缓存功能:

const jwt = require('jsonwebtoken');
const NodeCache = require('node - cache');

const myCache = new NodeCache();
const SECRET_KEY = "your_secret_key";

function verifyToken(token) {
    const cachedResult = myCache.get(token);
    if (cachedResult!== undefined) {
        return cachedResult;
    }
    try {
        const data = jwt.verify(token, SECRET_KEY);
        myCache.set(token, data);
        return data;
    } catch (error) {
        if (error.name === 'TokenExpiredError') {
            myCache.set(token, null);
            return null;
        }
        myCache.set(token, null);
        return null;
    }
}

批量处理 JWT 验证

在某些情况下,可以批量处理 JWT 验证,而不是逐个验证。例如,如果有多个请求都需要验证 JWT,可以将这些请求的 JWT 收集起来,一次性进行验证,减少验证的次数。

与其他系统集成

与 OAuth 2.0 集成

OAuth 2.0 是一种授权框架,常用于授权第三方应用访问用户资源。可以将 JWT 与 OAuth 2.0 集成。例如,在 OAuth 2.0 的授权码模式中,授权服务器在颁发访问令牌时,可以生成 JWT 作为访问令牌。

以下是一个简单的示例,展示如何在 Python 中使用 flask - oauthlib 库实现与 OAuth 2.0 的集成,并生成 JWT 作为访问令牌:

pip install flask - oauthlib PyJWT
from flask import Flask
from flask_oauthlib.provider import OAuth2Provider
import jwt
import datetime

app = Flask(__name__)
oauth = OAuth2Provider(app)
SECRET_KEY = "your_secret_key"

# 模拟用户和客户端数据
users = {
    "user1": "password1"
}
clients = {
    "client1": {
        "client_secret": "secret1",
        "redirect_uris": ["http://localhost:3000/callback"]
    }
}

@oauth.clientgetter
def get_client(client_id):
    return clients.get(client_id)

@oauth.grantgetter
def get_grant(client_id, code):
    # 这里应该从数据库中获取授权码相关信息
    pass

@oauth.grantsetter
def set_grant(client_id, code, request, *args, **kwargs):
    # 这里应该将授权码相关信息保存到数据库中
    pass

@oauth.tokengetter
def get_token(access_token=None, refresh_token=None):
    # 这里应该从数据库中获取令牌相关信息
    pass

@oauth.tokensetter
def set_token(token, request, *args, **kwargs):
    user = request.user
    user_id = user["username"]
    role = "user"  # 假设用户角色为 user
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
        "role": role
    }
    token["access_token"] = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    # 这里应该将令牌相关信息保存到数据库中
    return token

# 其他 OAuth 2.0 相关路由和逻辑
#...

if __name__ == '__main__':
    app.run(debug=True)

与单点登录(SSO)集成

单点登录(SSO)允许用户使用一组凭据访问多个相关的应用程序。可以将基于 JWT 的用户权限管理系统与 SSO 集成。例如,在 SSO 服务器认证成功后,生成包含用户信息和权限的 JWT,并将其传递给各个应用程序。各个应用程序通过验证 JWT 来确认用户身份和权限。

在这种集成中,需要确保 SSO 服务器和各个应用程序之间的密钥一致性,以保证 JWT 的正确验证。

总结

基于 JWT 的用户权限管理系统在后端开发中具有诸多优势,它提供了一种紧凑、自包含且安全的方式来传输用户认证和权限信息。通过合理的设计和实现,结合不同的权限管理策略,如 RBAC 和基于资源的权限管理,可以满足各种应用程序的安全需求。同时,在实际应用中,需要充分考虑安全性、性能优化、跨域问题以及与其他系统的集成,以构建健壮且高效的用户权限管理系统。无论是小型的 Web 应用还是大型的企业级系统,JWT 都为用户权限管理提供了一种可靠的解决方案。