先抛开JWT,回顾一下我们传统的网页,是通过Cookie的方式实现鉴权的,在用户登陆完成后,返回能识别该用户的信息到浏览器的Cookie中,下一次浏览器请求相同域名的时候,会自动把上次从服务器获取的Cookie提交上去,从而实现用户鉴权。使用Cookie的前提是需要同源,也就是渲染页面的域名必须和当前请求的域名相同,否则Cookie是不会生效的,举个例子,从百度服务器获取的Cookie,浏览器是不会提交到腾讯服务器的。传统的网页,使用Cookie是没有问题的,但是如果开发前后端分离项目,就会有一些问题了。前后端分离项目在开发和生产环境中,极大可能是部署在不同的服务器上,也就是域名都不一样,这时候用Cookie就不太合适。为了解决同源策略的问题,我们就需要使用JWT,下面再来说一下JWT的原理,你就能明白JWT相比Cookie在前后端分离项目中的优势。
JWT的原理其实非常简单。用户在登录网站后,我们将能识别该用户的信息,比如就用user_id,加上过期时间,以及加密盐,通过某种加密算法生成一个加密后的字符串,这个字符串就是JWT,然后返回给前端。下次前端再发送请求的时候,把这个JWT携带上来,然后服务器再把这个JWT进行解密,得到user_id和过期时间,如果在过期时间内,并且user_id是正确的,那么就能识别此请求是哪个用户了。通过JWT,前后端分离项目部署在不同服务器上,也可以正常通信。这里有个小细节,就是可以在生成JWT后,把JWT在服务器上也存储一份,这样可以实现单点登陆功能。
在Python中,有一个独立的JWT包,叫做PyJWT:https://pyjwt.readthedocs.io/,我们可以直接使用他来生成JWT和验证。但是在Flask中,我们可以通过Flask-JWT-Extended来实现JWT功能,因为他封装了使用方式,以及一些属性和装饰器,用起来更爽。
1. 安装Flask-JWT-Extended:
使用 pip 开始使用此扩展的最简单方法:
pip install flask-jwt-extended
如果要使用非对称(公钥/私钥)密钥签在这里插入代码片
名算法,可以使用包含asymmetric_crypto包的flask-jwt-extended。
pip install flask-jwt-extended[asymmetric_crypto]
请注意,如果你使用的是ZSH·(也可能是其他shell`),则需要对括号进行转义。
pip install flask-jwt-extended\[asymmetric_crypto\]
2. 基本使用:
2.1. 初始化jwt对象
首先从flask_jwt_extended中导入JWTManager,然后创建一个对象,代码如下:
# exts.py
from flask_jwt_extended import JWTManager
...
jwt = JWTManager()
接着在app.py中导入jwt对象,然后进行初始化,代码如下:
# app.py
from exts import jwt
...
app = Flask(__name__)
# 这一步一定要设置,会被flask-jwt-extended当做加密的盐
app.config['SECRET_KEY'] = "secret key"
...
jwt.init_app(app)
2.2. 生成jwt:
我们可以使用create_access_token来创建一个token,在创建的时候,需要传入能识别此用户的identity。示例代码如下:
from flask_jwt_extended import create_access_token
@app.route("/login", methods=["POST"])
def login():
username = request.json.get("username", None)
password = request.json.get("password", None)
if username != "test" or password != "test":
return jsonify({"msg": "用户名或密码错误"}), 401
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token)
2.3. 验证jwt:
如果某个视图函数必须要验证完jwt后才能访问,那么可以使用jwt_required装饰器。然后在视图函数中,使用get_jwt_identity获取之前创建jwt时候传入的identity参数。示例代码如下:
@app.route("/protected", methods=["GET"])
@jwt_required()
def protected():
# Access the identity of the current user with get_jwt_identity
username = get_jwt_identity()
return jsonify(logged_in_as=username), 200
http GET :5000/protected Authorization:"Bearer {{jwt}}"
上述命令,是使用httpie工具的http命令,往http://127.0.0.1:5000/protected发送了一个请求,并且在请求头中携带了Authorization参数,参数的值是Bearer jwt,其中Bearer也是一个固定的写法,我们只需要把服务器返回的jwt设置到Bearer后面即可。
当然,也可以把jwt设置到cookie中,Body中,甚至是请求URL的参数中,但设置在请求头中实际上是最方便的,只要在请求方法中封装好,调用起来非常方便。
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = datetime.timedelta(minutes=30)
另一种方法是使用你的访问令牌发出API请求,然后检查结果以查看它是否有效。如果请求的结果是一条错误消息,b表示JWT已过期,这时候我们使用一个刷新JWT生成新的普通JWT并使用新的普通JWT重做请求。无论前端的时钟如何,这种方法都可以工作,但用起来也确实麻烦一点。
当您的前端不是网站(移动、仅 api 等)时,推荐使用刷新令牌。示例代码如下:
from datetime import timedelta
from flask import Flask
from flask import jsonify
from flask_jwt_extended import create_access_token
from flask_jwt_extended import create_refresh_token
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret"
# 设置普通JWT过期时间
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
# 设置刷新JWT过期时间
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
jwt = JWTManager(app)
@app.route("/login", methods=["POST"])
def login():
access_token = create_access_token(identity="example_user")
refresh_token = create_refresh_token(identity="example_user")
return jsonify(access_token=access_token, refresh_token=refresh_token)
# 使用刷新JWT来获取普通JWT
@app.route("/refresh", methods=["POST"])
@jwt_required(refresh=True)
def refresh():
identity = get_jwt_identity()
access_token = create_access_token(identity=identity)
return jsonify(access_token=access_token)
@app.route("/protected", methods=["GET"])
@jwt_required()
def protected():
return jsonify(foo="bar")
if __name__ == "__main__":
app.run()
以后在遇到普通JWT过期了,那么可以再使用刷新JWT来重新获取普通JWT。
关于flask-jwt-extended的讲解就在这里,学会这些,您在前后端分离项目中的鉴权,将没有任何问题。读者如果还要了解更多,可以再仔细阅读flask-jwt-extended的官方文档:官方文档。