继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

从零开始学习JWT:入门教程

青春有我
关注TA
已关注
手记 1199
粉丝 205
获赞 1008
概述

JWT(JSON Web Token)是一种用于在网络应用中安全地传输信息的标准,主要用途是为用户身份验证和授权提供安全且方便的方式。文章详细介绍了JWT的组成部分、生成和验证方法,以及其在实际应用中的使用技巧和注意事项。

JWT简介

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用中安全地传输信息。JWT的主要用途是为用户身份验证和授权提供一个安全且方便的方式。通过JWT,我们可以轻松地在不同应用之间传递用户信息,并且信息可以被验证,确保其未被篡改。

什么是JWT

JWT是一种用于通信的压缩的、URL安全的字符串。它由三部分组成,每部分之间用点(.)分隔:

  • Header(头部):包含令牌的类型(通常是 JWT)和所使用的签名算法,默认是 HS256
  • Payload(有效载荷):包含声明(声明是关于实体(人、物等)的声明陈述)。例如,sub(主体)用于指代用户,iat(签发时间)用于指代签发时间等。
  • Signature(签名):用于验证消息的完整性。签名使用了头部中指定的算法,基于头和载荷生成。

JWT的工作原理

当用户登录成功后,服务器会生成一个JWT并返回给客户端。客户端在每次向服务器请求资源时都需要携带这个JWT,服务器会在收到请求后,检查JWT的有效性并验证签名,确保JWT未被篡改。

具体步骤如下:

  1. 认证:用户提交用户名和密码。
  2. 验证:服务器验证用户名和密码是否正确。
  3. 生成JWT:验证通过后,服务器生成JWT,包括用户信息和有效载荷。
  4. 返回JWT:服务器将JWT返回给客户端。
  5. 存储JWT:客户端可以存储JWT(通常存储在浏览器的 localStoragesessionStorage 中)。
  6. 请求资源:客户端在请求资源时,在HTTP请求头中包含JWT。
  7. 验证JWT:服务器接收请求后,验证JWT的有效性,包括签名。

JWT的优点和应用场景

JWT的优点包括:

  • 无状态:服务器不需要存储JWT信息,每个请求都需要包含JWT,增加了服务器的响应速度。
  • 安全性高:JWT使用HMAC算法,密钥由服务器生成,客户端无法伪造。
  • 易于扩展:可以通过添加新的声明来扩展JWT。

JWT的应用场景包括:

  • 身份验证:用户登录时生成JWT,后续请求携带JWT进行认证。
  • 授权:在JWT中嵌入用户角色信息,用于访问控制。
  • 单点登录:所有应用共享同一JWT,实现单点登录功能。

JWT的组成部分

Header(头部)

JWT的头部包含两个字段:

  • typ:令牌的类型,通常是 JWT
  • alg:签名算法,例如 HS256RS256 等。

示例如下:

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

Payload(载荷)

载荷是JWT的核心部分,包含声明信息。常见的标准声明如下:

  • iss:发行者。
  • sub:主题,通常表示用户的唯一标识。
  • aud:受众,指JWT的接收方。
  • exp:过期时间,表示JWT的过期时间。
  • nbf:非刷新时间,表示JWT的生效时间。
  • iat:签发时间,表示JWT的签发时间。
  • jti:JWT的唯一标识符,用于防止重放攻击。

示例如下:

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

Signature(签名)

签名用于验证JWT是否被篡改,保证了JWT的安全性。签名过程如下:

  1. 将头部和载荷用Base64编码后,合并成一个字符串,例如:encodedHeader.encodedPayload
  2. 使用密钥和算法,对上述字符串进行签名。
  3. 将签名结果附加到JWT的末尾,即 encodedHeader.encodedPayload.signature

示例如下:

import base64
import hmac
import hashlib

def generate_signature(header, payload, secret):
    message = f"{base64.urlsafe_b64encode(header.encode()).decode()}." \
              f"{base64.urlsafe_b64encode(payload.encode()).decode()}"
    signature = base64.urlsafe_b64encode(
        hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
    ).decode()
    return signature

header = {"typ": "JWT", "alg": "HS256"}
payload = {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
secret = 'mysecretkey'

print(generate_signature(json.dumps(header), json.dumps(payload), secret))

如何使用JWT

JWT的生成

生成JWT的基本步骤如下:

  1. 创建Header:定义JWT的类型和算法。
  2. 创建Payload:定义JWT的有效载荷。
  3. 生成签名:使用密钥和算法生成签名。

示例如下:

import json
import base64
import hmac
import hashlib
import time

def generate_jwt(header, payload, secret):
    encoded_header = base64.urlsafe_b64encode(json.dumps(header).encode()).decode()
    encoded_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode()
    message = f"{encoded_header}.{encoded_payload}"
    signature = base64.urlsafe_b64encode(
        hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
    ).decode()
    return f"{encoded_header}.{encoded_payload}.{signature}"

header = {"typ": "JWT", "alg": "HS256"}
payload = {"sub": "1234567890", "name": "John Doe", "iat": int(time.time())}
secret = 'mysecretkey'

print(generate_jwt(header, payload, secret))

JWT的验证

验证JWT的基本步骤如下:

  1. 解析JWT:拆分JWT为头部、载荷和签名。
  2. 验证签名:使用相同的密钥和算法重新计算签名,比较是否一致。
  3. 检查有效载荷:验证有效载荷中的声明是否合法。

示例如下:

import json
import base64
import hmac
import hashlib
import time

def generate_jwt(header, payload, secret):
    encoded_header = base64.urlsafe_b64encode(json.dumps(header).encode()).decode()
    encoded_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode()
    message = f"{encoded_header}.{encoded_payload}"
    signature = base64.urlsafe_b64encode(
        hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
    ).decode()
    return f"{encoded_header}.{encoded_payload}.{signature}"

def verify_jwt(token, secret):
    parts = token.split('.')
    if len(parts) != 3:
        return False

    encoded_header = parts[0]
    encoded_payload = parts[1]
    signature = parts[2]

    header = json.loads(base64.urlsafe_b64decode(encoded_header + '=='))
    payload = json.loads(base64.urlsafe_b64decode(encoded_payload + '=='))

    message = f"{encoded_header}.{encoded_payload}"
    calculated_signature = base64.urlsafe_b64encode(
        hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
    ).decode()

    return calculated_signature == signature and payload.get('exp', 0) > time.time()

header = {"typ": "JWT", "alg": "HS256"}
payload = {"sub": "1234567890", "name": "John Doe", "iat": int(time.time()), "exp": int(time.time()) + 3600}
secret = 'mysecretkey'

jwt_token = generate_jwt(header, payload, secret)
print(jwt_token)
print(verify_jwt(jwt_token, secret))

JWT的存储和使用

JWT通常存储在客户端的本地存储(如 localStoragesessionStorage),也可以通过Cookie传递。以下是一个简单的示例,展示如何在浏览器中使用JWT。

示例如下:

<!DOCTYPE html>
<html>
<head>
    <title>JWT Example</title>
    <script>
        function setToken(token) {
            localStorage.setItem('jwt', token);
        }

        function getToken() {
            return localStorage.getItem('jwt');
        }

        function verifyToken() {
            const token = getToken();
            if (token) {
                console.log('Token is valid:', verify_jwt(token, 'mysecretkey'));
            } else {
                console.log('No token found');
            }
        }

        // Example JWT from previous section
        const jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.J05M-A9O4r6fXW5Z3T2T92OuJ7u4hTdqjGk-SZpOsA";

        setToken(jwtToken);
        verifyToken();
    </script>
</head>
<body>
    <button onclick="verifyToken()">Verify Token</button>
</body>
</html>

JWT安全注意事项

密钥的安全性

在使用JWT时,密钥的安全性是至关重要的。密钥必须保密,否则任何人都可以伪造JWT。以下是一些确保密钥安全性的建议:

  • 不要硬编码密钥:不要在代码中直接硬编码密钥,而是使用环境变量或配置文件。
  • 定期更换密钥:定期更换密钥可以减少密钥泄露的风险。
  • 加密密钥存储:将密钥存储在加密的文件或数据库中。

示例如下:

import os

# 使用环境变量获取密钥
secret = os.getenv('JWT_SECRET', 'defaultsecretkey')

签名算法的选择

JWT支持多种签名算法,如 HS256RS256 等。选择合适的算法可以提高安全性。

  • HS256:对称加密算法,使用相同的密钥来加密和解密。
  • RS256:非对称加密算法,使用公钥来验证签名,私钥来生成签名。

示例如下:

import jwt

def generate_jwt(header, payload, secret):
    return jwt.encode(payload, secret, algorithm=header['alg'])

header = {"typ": "JWT", "alg": "HS256"}
payload = {"sub": "1234567890", "name": "John Doe", "iat": int(time.time())}
secret = 'mysecretkey'

token = generate_jwt(header, payload, secret)
print(token)

# 验证JWT
def verify_jwt(token, secret):
    try:
        return jwt.decode(token, secret, algorithms=[header['alg']])
    except jwt.ExpiredSignatureError:
        return "Token expired"
    except jwt.InvalidTokenError:
        return "Invalid token"

print(verify_jwt(token, secret))

有效时间的设置

有效时间(exp)是JWT中常见的声明,表示JWT的过期时间。合理设置有效时间可以防止JWT被滥用。

示例如下:

import time

payload = {
    "sub": "1234567890",
    "name": "John Doe",
    "iat": int(time.time()),
    "exp": int(time.time()) + 3600  # JWT将在1小时后过期
}

def generate_jwt(header, payload, secret):
    return jwt.encode(payload, secret, algorithm=header['alg'])

header = {"typ": "JWT", "alg": "HS256"}
secret = 'mysecretkey'

token = generate_jwt(header, payload, secret)
print(token)

# 验证JWT
def verify_jwt(token, secret):
    try:
        return jwt.decode(token, secret, algorithms=[header['alg']])
    except jwt.ExpiredSignatureError:
        return "Token expired"
    except jwt.InvalidTokenError:
        return "Invalid token"

print(verify_jwt(token, secret))

实际案例:JWT在登录验证中的应用

登录流程中JWT的使用

在用户登录时,服务器会验证用户的凭据(用户名和密码),然后生成JWT并返回给客户端。客户端在每次请求资源时,都需要携带JWT以进行认证。

示例如下:

import jwt
import time

def generate_jwt(header, payload, secret):
    return jwt.encode(payload, secret, algorithm=header['alg'])

def verify_jwt(token, secret):
    try:
        return jwt.decode(token, secret, algorithms=[header['alg']])
    except jwt.ExpiredSignatureError:
        return "Token expired"
    except jwt.InvalidTokenError:
        return "Invalid token"

header = {"typ": "JWT", "alg": "HS256"}
secret = 'mysecretkey'

# 模拟登录
def login(username, password):
    if username == 'john' and password == 'password':
        payload = {
            "sub": "1234567890",
            "name": "John Doe",
            "iat": int(time.time()),
            "exp": int(time.time()) + 3600
        }
        return generate_jwt(header, payload, secret)
    return None

# 模拟请求资源
def fetch_resource(token):
    if verify_jwt(token, secret) != "Invalid token":
        print("Resource fetched successfully")
    else:
        print("Invalid token")

token = login('john', 'password')
if token:
    fetch_resource(token)
else:
    print("Login failed")

登出时的处理方法

在用户登出时,客户端通常会删除存储的JWT,从而结束会话。服务器端也可以设置黑名单,阻止特定的JWT。

示例如下:

<!DOCTYPE html>
<html>
<head>
    <title>JWT Example</title>
    <script>
        function setToken(token) {
            localStorage.setItem('jwt', token);
        }

        function getToken() {
            return localStorage.getItem('jwt');
        }

        function logout() {
            localStorage.removeItem('jwt');
            console.log("Logged out");
        }

        function verifyToken() {
            const token = getToken();
            if (token) {
                console.log('Token is valid:', verify_jwt(token, 'mysecretkey'));
            } else {
                console.log('No token found');
            }
        }

        // Example JWT from previous section
        const jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.J05M-A9O4r6fXW5Z3T2T92OuJ7u4hTdqjGk-SZpOsA";

        setToken(jwtToken);
        verifyToken();
        logout();
        verifyToken();
    </script>
</head>
<body>
    <button onclick="logout()">Logout</button>
</body>
</html>

常见问题解答

JWT中常见的错误与解决方法

  1. Token过期:在请求资源时,如果JWT已过期,服务器会返回过期错误。

    • 解决方法:更新JWT或重新生成新的JWT。
  2. 签名错误:签名不匹配时,JWT可能被篡改。

    • 解决方法:确保密钥和算法一致,检查JWT的完整性和合法性。
  3. 无效JWT:JWT可能被篡改或伪造。
    • 解决方法:确保密钥的安全性,定期更换密钥,并检查JWT的有效载荷。

示例如下:

def verify_jwt(token, secret):
    try:
        return jwt.decode(token, secret, algorithms=[header['alg']])
    except jwt.ExpiredSignatureError:
        return "Token expired"
    except jwt.InvalidTokenError:
        return "Invalid token"

def refresh_jwt(token, secret):
    payload = jwt.decode(token, secret, algorithms=[header['alg']])
    payload['exp'] = int(time.time()) + 3600
    return jwt.encode(payload, secret, algorithm=header['alg'])

token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.J05M-A9O4r6fXW5Z3T2T92OuJ7u4hTdqjGk-SZpOsA"
print(verify_jwt(token, 'mysecretkey'))

refreshed_token = refresh_jwt(token, 'mysecretkey')
print(refreshed_token)
print(verify_jwt(refreshed_token, 'mysecretkey'))

JWT与其他认证机制的比较

JWT与传统的Session认证机制相比,具有以下优势:

  • 无状态:JWT的无状态性使得服务器不需要存储任何会话信息,减轻了服务器的压力。
  • 分布式友好:JWT可以直接传递,不需要特殊处理,这使得它非常适合分布式系统。
  • 安全:JWT使用HMAC算法,除非密钥泄露,否则无法伪造。

相比之下,传统的Session认证机制需要服务器存储Session数据,增加了服务器的负担。此外,Session数据可能被篡改或泄漏,安全性较低。

总结

通过以上内容,我们详细介绍了JWT的基本概念、组成部分、生成和验证方法,以及在实际应用中的使用技巧和注意事项。JWT作为一种轻量级、安全的认证机制,被广泛应用于各种网络应用中。希望本文能够帮助你更好地理解和使用JWT。如果你对JWT有更深入的需求,可以参考慕课网的相关课程进行进一步学习。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP