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硬生生变成了有状态的,违背了初衷。
不对的地方大家多指教