什么是JWT及使用

编程/开发
368
0
0
2023-05-03

HTTP

1.在认识JWT之前,大家要先了解一下HTTP协议

2.HTTP(Hypertext Transfer Protocol)也叫 超文本传输协议,是一个用于传输超媒体文档(例如: HTML, CSS, JS)的应用层协议。HTTP是在Web 应用程序之间传递数据的基础,HTTP通常基于 TCP/IP协议来传输数据。默认为80端口

3.HTTP协议以客户端 和 服务端架构为基础, 客户端 向服务器发送请求,服务器响应请求并返回所请求的数据。HTTP客户端通常是web 浏览器,HTTP服务端则可以是 WEB 服务器 或者 应用 服务器, 如: Apache 和 Nginx 等

特定:

        1.简单快速:客户端向服务器请求服务时,只需要传送请求方法和路径,请求方法常用的有 GET, POST, PUT , DELETE 等

        2.灵活: HTTP允许传输任意类型的数据,客户端和服务器直接的传输不受限制,HTTP还允许通过插件和扩展添加新的功能

        3.无连接: HTTP采用无连接方式,每次请求都需要重新建立连接,完成之后即断开连接,这样可以节省传输时间

            * 为什么HTTP无连接可以节省传输时间?

                    HTTP是一种基于 请求和响应模式的应用层协议,每个请求都需要进行连接,请求,响应和断开等操作,在HTTP 1.1 之前,每个请求都需要建立一次 TCP连接,完成请求后再断开连接,因此存在很多TCP建立和断开的开销,增加了网络传输的负担,也降低了网路传输的效率
                    HTTP 1,1 采用了持久连接,即一次TCP连接中,可以发送多个HTTP请求,避免 多次建立和断开连接的开销,节省了网络传输时间,提高了网络传输效率。此外 HTTP2进一步优化了网络传输效率,使用了多路复用技术,允许多个请求在同一个TCP连接中进行并发传输,大大提高了网络传输效率

        4.无状态: HTTP 协议是无状态协议,它不会记录每次通信的状态,每个请求直接是相互独立的

什么是 Session认证

1.在HTTP介绍中了解到 HTTP协议是无状态的 ,那就代表 当有用户 向系统使用账户和密码进行认证之后,在下一次 因为并没有状态记录 所以需要再一次进行用户认证,因为我们不能通过HTTP 协议知道是哪个用户发出的请求,如果需要知道是哪个用户发出的请求,那就要在认证成功之后在服务器保存一份用户信息(保存到session中),并且返回一个cookie值传递给浏览器,在下一次用户进行请求时就可以带上cookie值,服务器就可以识别是哪个用户发送的请求,验证是否已经验证, 是否登录,这就是传统的session认证

2.什么是Session:Session 是指在WEB应用程序中一种记录客户端状态的机制,通常在服务端存储和管理客户端的信息,来维持用户在应用中的回话状态,当用户在第一次访问Web访问应用时,服务端会创建一个 session对象,然后生成一个session ID,并将该ID通过cookie 或者URL参数的方式发送给客户端,客户端在后面的请求值 都会携带该ID,服务端通过该ID获取该用户的Session对象,进行 读取,更新,删除操作

3.什么是cookie: Cookie 是一种小型文本文件,存储浏览器上(更准确的说是 是由网站服务器在网站访问者的计算机硬盘上创建并存储 )当访问同一个网站时,该网站可以读取该Cookie,Cookie只能存储文本信息,因此通常用于存储小型的数据,而不是用于存储大量的数据

4.为什么Cookie的存储数据量有限制?
        Cookie 大概只能存储4KB的左右的数据量,因为Cookie是通过HTTP头信息传递的,HTTP头信息的大小也是有限制的,因此Cookie的大小也有限制

5.Session 和Cookie的区别:
        1,存储的地方:Session 是一种服务器存储技术,而Cookie是一种客户端存储技术
        2,安全: Session 数据存储在服务器端,因此相对安全一点,而Cookie存储在客户端,因此相对不安全(恶意篡改)
        Seeion认证的缺点:如果分布式部署应用的话,会出现session不能共享的问题,很难扩展

什么是Token认证

Token认证和Session认证的流程差不多,token 一般是一个随机字符,不同的是 Token是保存一个值到Redis, 并且设置一个过期时间,然后将该Token返回给客户端,在使用Token认证的情况下,一般采用以下步骤进行验证:

    1.客户端向服务器发送请求时,将Token放在请求头部或请求参数中。

    2.服务器接收到请求后,从请求头部或请求参数中获取Token。

    4.服务器根据Token进行身份验证(如果Token存储在Redis中,查询该Token是否存在,如果存在返回成功,不存在返回失败,重新登录),验证通过则返回请求结果,否则返回身份验证失败的信息。

1.Token认证和Session 认证不同:

    1.存储位置:Session认证将用户的登录状态信息存储在服务器端,而Token认证将用户的登录状态信息存储在客户端。

    2.通信方式:Session认证需要在客户端和服务器端之间建立一次连接,并且需要在每次请求时向服务器端发送Session ID,而Token认证只需要在用户登录时获取一次Token,在后续请求中携带Token即可完成认证,不 需要频繁地向服务器发送认证信息。

    3.有效期:Session认证的有效期通常较短,需要经常更新Session ID,而Token认证的有效期通常较长,可以设置Token的过期时间。

    4.可扩展性:Token认证的机制使得它更容易扩展到不同的应用程序和服务之间,而Session认证则需要在服务器端维护多个Session ID。

    5.安全性:Token认证的机制相对Session认证更为安全,因为Token本身是加密过的,并且不存储在服务器端,而Session ID容易被窃取并冒充他人。

2.Token认证的优缺点:

    优点是多台服务器都是使用 redis 来存取 token,不存在不共享的问题,所以容易扩展。
    缺点是每次请求都需要查一下redis,会造成 redis 的压力,还有增加了请求的耗时,每个已登录的用户都要保存一个 token 在 redis,也会消耗 redis 的存储空间。

什么是JWT认证

什么是JWT:
    JWT(JSON Web Token)是一种基于JSON数据格式的轻量级的身份验证和授权规范,它通过在客户度和服务端之间传递一段经过签名的JSON数据来实现身份认证和授权

JWT的数据格式
    JWT 一般是一个字符串,分为三部分, 以 "."进行隔开

    xxxxx.yyyyy.zzzzz

    三部分分别代表 Header(头部), Payload(载荷) , Signature(签名)
Header

    JWT 第一部分是头部,它是一个描述 JWT 元数据的JSON对象,如下

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

    alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为HS256), typ属性表示令牌的类型, JWT 统一写完JWT。使用Base64 经上述JSON对象进行编码

    常用的算法:

    HMAC SHA256: 使用 HMAC-SHA256 算法进行签名,需要共享一个密钥

    RS256:使用 RSA 算法进行签名,使用私钥进行签名,使用公钥进行验证。

    RS384:使用 RSA 算法进行签名,使用私钥进行签名,使用公钥进行验证。

    RS512:使用 RSA 算法进行签名,使用私钥进行签名,使用公钥进行验证。

Plyload

    JWT 第二部分是Payload,也是一个JSON对象, 除了包含需要传递的数据,还要七个默认的字段进行选择

    -   iss (issuer):签发人/发行人
    -   sub (subject):主题
    -   aud (audience):用户
    -   exp (expiration time):过期时间
    -   nbf (Not Before):生效时间,在此之前是无效的
    -   iat (Issued At):签发时间
    -   jti (JWT ID):用于标识该 JWT

    如果是自定义字段

        { "sub": "1234567890", "name": "John Doe", "iat": 1682499592, "exp": 1682585992, "my_namespace:my_field": "custom data" }

    Payload也用 Base64进行编码

    Payload 中不要存放敏感信息在里面,因为默认JWT是未加密的,JSON对象是使用了Base64进行编码,是可以反向编码回原样的

Signayure

    JWT 第三部分是签名,是这样生成的,实现需要指定一个 secret,该secret仅仅保存在服务器中,这个部分需要 经过 Base64编码的 Header 和Payload 是用 . 连接组成的字符串,然后通过 Header中生命的算法进行 加盐secret组合加密,然后就得到一个签名哈希,也就是 Signayure,且无法反向解密

JTW的优点:

    1.轻量级:JWT 是一种轻量级的认证方式,传输的数据量小,可以被轻松地在网络上传输。

    2.跨语言:JWT 是一种标准的认证方式,多种编程语言都有支持库,可以轻松地进行实现。

    3.无状态:JWT 不需要在服务端存储会话信息或用户状态,每个请求都包含了足够的信息,使得服务端可以进行认证和授权。

    4.可扩展性:JWT 的 payload 中可以包含自定义字段,使得 JWT 可以应用于多种场景,例如单点登录、API 认证等。

    5.安全性:JWT 可以使用加密算法对令牌进行签名,确保令牌的真实性和完整性。同时,由于 JWT 只在客户端保存,没有存储在服务端,也避免了被窃取的风险。

Flask中使用JWT

在 Flask 中使用 JWT 可以通过 Flask-JWT-Extended 扩展来实现
from flask import Flask, jsonify
from flask_jwt_extended import (
    JWTManager, create_access_token, jwt_required, get_jwt_identity
)

app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'super-secret'  # 设置 JWT 密钥
jwt = JWTManager(app)  # 初始化 JWT 扩展


@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')

    # 检查用户名和密码是否正确,如果正确则生成令牌并返回
    if username == 'admin' and password == 'admin123':
        access_token = create_access_token(identity=username)
        return jsonify({'access_token': access_token}), 200
    else:
        return jsonify({'error': 'Invalid username or password'}), 401


@app.route('/protected', methods=['GET'])
# JWT # @jwt_required 装饰器则用于装饰需要进行 JWT 认证的视图函数,该装饰器会检查 JWT 是否有效并且载荷是否包含所需的字段。如果验证通过,则视图函数会被执行;否则返回 401 状态码。

@jwt_required  # 装饰器需要验证 
def protected():
    # 获取当前用户身份并返回
    current_user = get_jwt_identity()
    return jsonify(logged_in_as=current_user), 200


if __name__ == '__main__':
    app.run(debug=True)
Flask-JWT-Extended 除了提供了基本的 JWT 认证机制外,还提供了自定义载荷和自定义认证等高级功能。

自定义载荷可以通过 create_access_token 和 create_refresh_token 函数的 additional_claims 参数来设置,该参数接受一个字典,可以在其中自定义需要的字段和值。

自定义认证可以通过继承 flask_jwt_extended.JWTManager 类并覆盖其中的方法来实现,比如可以自定义认证逻辑和响应信息等。

自定义响应信息:

1.需要导入 jwt_required 装饰器和 current_identity 函数,这样才能在回调函数中使用。

2.current_identity:是一个全局变量,用于在当前请求期间存储已经通过 JWT 验证的用户身份信息。它可以在视图函数中使用,以访问经过身份验证的用户的信息,例如用户 ID,用户名等。

当客户端在请求头中发送有效的 JWT 令牌时,该库会自动将 JWT 令牌解码,并使用其 payload 中包含的用户信息填充 current_identity。因此,可以方便地使用 current_identity 来获得与当前请求关联的经过身份验证的用户信息。

2.定义一个回调函数

```python
from flask_jwt import jwt_required, current_identity

def custom_response_handler(access_token, identity):
    return {
        'access_token': access_token.decode('utf-8'),
        'user_id': current_identity.id,
        'email': current_identity.email
    }

```



最后,在创建 Flask JWT 实例时,通过传入 jwt_response_handler 参数来指定这个回调函数:
from flask_jwt import JWT, jwt_required, current_identity

app = Flask(__name__)

app.config['SECRET_KEY'] = 'super-secret'
app.config['JWT_AUTH_URL_RULE'] = '/login'
app.config['JWT_EXPIRATION_DELTA'] = timedelta(days=1) # 设置 JWT 令牌的过期时间,默认情况下,它的值为 15 分钟

jwt = JWT(app, authenticate, identity)
jwt.jwt_response_handler(custom_response_handler)

JWT自定义验证失败返回信息

在 JWT 认证过程中,如果验证失败,可以通过 Flask-JWT 提供的 JWT_INVALID_TOKEN_CALLBACK 回调函数来自定义返回信息。该回调函数可以被覆盖并且传入一个错误信息作为参数,可以根据错误信息来返回特定的响应内容。
from flask import Flask, jsonify
from flask_jwt import JWT, jwt_required, current_identity
from werkzeug.security import safe_str_cmp

app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-secret'

def authenticate(username, password):
    if username == 'user' and password == 'pass':
        return username

def identity(payload):
    user_id = payload['identity']
    return {'user_id': user_id}

def invalid_token_callback(error):
    return jsonify({'message': 'Invalid token. Please log in again.'}), 401

jwt = JWT(app, authenticate, identity)
app.config['JWT_EXPIRATION_DELTA'] = timedelta(seconds=300)
#JWT_INVALID_TOKEN_CALLBACK 回调函数被设置为 invalid_token_callback,并且如果 JWT 验证失败,就会返回一个 JSON 格式的错误信息,内容为 {'message': 'Invalid token. Please log in again.'}
app.config['JWT_INVALID_TOKEN_CALLBACK'] = invalid_token_callback  
JWT 常用配置
app.config.setdefault("JWT_ACCESS_COOKIE_NAME", "access_token_cookie")
app.config.setdefault("JWT_ACCESS_COOKIE_PATH", "/")
app.config.setdefault("JWT_ACCESS_CSRF_COOKIE_NAME", "csrf_access_token")
app.config.setdefault("JWT_ACCESS_CSRF_COOKIE_PATH", "/")
app.config.setdefault("JWT_ACCESS_CSRF_FIELD_NAME", "csrf_token")
app.config.setdefault("JWT_ACCESS_CSRF_HEADER_NAME", "X-CSRF-TOKEN")
app.config.setdefault("JWT_ALGORITHM", "HS256")
app.config.setdefault("JWT_COOKIE_CSRF_PROTECT", True)
app.config.setdefault("JWT_COOKIE_DOMAIN", None)
app.config.setdefault("JWT_COOKIE_SAMESITE", None)
app.config.setdefault("JWT_COOKIE_SECURE", False)
app.config.setdefault("JWT_CSRF_CHECK_FORM", False)
app.config.setdefault("JWT_CSRF_IN_COOKIES", True)
app.config.setdefault("JWT_CSRF_METHODS", ["POST", "PUT", "PATCH", "DELETE"])
app.config.setdefault("JWT_DECODE_ALGORITHMS", None)
app.config.setdefault("JWT_DECODE_AUDIENCE", None)
app.config.setdefault("JWT_DECODE_ISSUER", None)
app.config.setdefault("JWT_DECODE_LEEWAY", 0)
app.config.setdefault("JWT_ENCODE_AUDIENCE", None)
app.config.setdefault("JWT_ENCODE_ISSUER", None)
app.config.setdefault("JWT_ERROR_MESSAGE_KEY", "msg")
app.config.setdefault("JWT_HEADER_NAME", "Authorization")
app.config.setdefault("JWT_HEADER_TYPE", "Bearer")
app.config.setdefault("JWT_IDENTITY_CLAIM", "sub")
app.config.setdefault("JWT_JSON_KEY", "access_token")
app.config.setdefault("JWT_PRIVATE_KEY", None)
app.config.setdefault("JWT_PUBLIC_KEY", None)
app.config.setdefault("JWT_QUERY_STRING_NAME", "jwt")
app.config.setdefault("JWT_QUERY_STRING_VALUE_PREFIX", "")
app.config.setdefault("JWT_REFRESH_COOKIE_NAME", "refresh_token_cookie")
app.config.setdefault("JWT_REFRESH_COOKIE_PATH", "/")
app.config.setdefault("JWT_REFRESH_CSRF_COOKIE_NAME", "csrf_refresh_token")
app.config.setdefault("JWT_REFRESH_CSRF_COOKIE_PATH", "/")
app.config.setdefault("JWT_REFRESH_CSRF_FIELD_NAME", "csrf_token")
app.config.setdefault("JWT_REFRESH_CSRF_HEADER_NAME", "X-CSRF-TOKEN")
app.config.setdefault("JWT_REFRESH_JSON_KEY", "refresh_token")
app.config.setdefault("JWT_REFRESH_TOKEN_EXPIRES", datetime.timedelta(days=30))
app.config.setdefault("JWT_SECRET_KEY", None)
app.config.setdefault("JWT_SESSION_COOKIE", True)
app.config.setdefault("JWT_TOKEN_LOCATION", ("headers",))
app.config.setdefault("JWT_ENCODE_NBF", True)
JWT的缺点:

    1.需要额外存储:JWT 作为一种无状态认证机制,需要将用户信息存储在 token 中,而这个 token 需要被客户端和服务端存储,可能会占用较多的存储空间。

    2.安全问题:JWT 使用 Base64 算法编码,但并没有进行加密,如果被黑客截获,便可轻易解密获取 token 中的信息。在 JWT 中,如果不使用 HTTPS,那么 token 也有被中间人攻击(Man-in-the-Middle)获取的风险。

    3.无法主动失效:由于 JWT 的有效性只由 token 自身决定,服务端无法主动使 token 失效,只能等待 token 过期或者客户端请求注销。

    4.信息不能太多:由于 token 会在每次请求时传递,因此为了减少传输的数据量,需要避免将过多的信息存储在 token 中。

    -   续签问题。当签发的 jwt 保存在客户端,客户端一直在操作页面,按道理应该一直为客户端续长有效时间,否则当 jwt有效期到了就会导致用户需要重新登录。那么怎么为 jwt 续签呢?最简单粗暴就是每次签发新的 jwt,但是由于过于暴力,会影响性能。如果要优雅一点,又要引入 Redis 解决,但是这又把无状态的 jw t硬生生变成了有状态的,违背了初衷。

不对的地方大家多指教

学海无涯,只求悟出此道。