在之前的课程中,介绍过 Flask-Login 框架,它是基于 Session 和 Cookie 技术来实现用户授权和验证的,不过 Session 有很多的局限性,这一节介绍一种基于 token 的验证方式 —— JWT (JSON Web Token),除了对 JWT 的概念讲解之外,还有在 Flask 中简单实践 session 的局限性基于 Session 的验证过程大体是:服务器端有一个 Session 词典,当用户验证登录后,在词典中为该用户创建一个 Session 对象,在响应( response )中返回一个 Session id,当用户下次请求时,携带 Session id,服务器从 Session 词典中可以恢复出 Session 对象,以完成用户的验证,在用 Session id 从恢复出认证实体。 从 Session 验证过程可以看出一些局限性:
token 简介为了解决 Session 的问题,有了 token 的验证方式。 token 可以理解成票据,或者凭证,当用户得到服务器的认证后,由服务器颁发,在之后的请求时携带,免去频繁登录。 token 不同于 Session 的地方:
为了利用好 token 的验证机制,IEIT (互联网工程任务组),制定了基于 JSON 数据结构的网络认证方式 JWA(JSON Web Algorithms),还针对不同应用场景提出了具体协议,如 JWS、JWE、JWK 等,他们可以统称为 JWT,即 Javascript Web Token。 理解 JWAJWA 的全称是 JSON Web Algorithms JSON 是 Javascript 的语言的文本对象表示法,是一种独立语言环境的数据结构表示,可以用网络数据传输,在前面 RESTful 章节中,对 API 调用的返回数据格式就是 JSON。 Algorithms 本义是算法的意思,这里特指加密算法,也就是用 JSON 表示的数据,经过加密后在在服务器端和客户段之间传输。 有了数据结构和加密算法的基础,根据不同的应用场景,定义出了具体实现:
JWT(JSON Web Token)上面 JWS、JWE 和 JWK 的总称。 JWT 简介JWT Wiki 上的定义是:
大致意思是,JWT 是用基于 JSON 数据结构的生成包含了一些权限声明的网络访问凭证的网络标准 数据结构JWT 由 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJpc3MiOiJBdXRobGliIiwic3ViIjoiMTIzIiwibmFtZSI6ImJvYiJ9. cBo6e7Uss5__16mlqZECjHJSKJDdyisevDP5cUGvJms
Header用于指定采用的加密算法,以及 JWT 采用的形式类型,例如: { "alg" : "HS256", "typ" : "JWT" }
Payload用于携带一些信息,例如用户名,过期时间 等等,例如: { "sub": "1234567890", "name": "John Doe", "admin": true } JWT 标准定义了 7 个字段:
这些字段有实现这自由选取,也可以加入其他自定义字段 Signature首先,需要指定一个密钥(secret)。密钥很重要,需要严格保密 然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名: HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret ) 即先将
验证当客户端发送请求时将 token 送到服务器端,可以用和签名同样的方式,重新计算一次签名,如果和客户端送过来的签名一致,说明 token 没有被篡改,如果不一致,说明 token 已被篡改,不安全了。 由此可见,用于做签名的密钥 secret 很重要,一旦泄漏,将无法鉴别 token 的真伪 JWT 应用关于 Python 的 JWT 实现不止一个,不同的库,不同的实现方式层出不穷,今天要讲解的是 Python 的 Authlib 库,它是一个大而全的 Python Web 验证库支持多种 Python 框架 Authlib 的 JWTAuthlib 是构建 OAuth 和 OpenID 安全连接服务器的终极 Python 库,包括了 JWS, JWE, JWK, JWA, JWT Authlib 功能强大而丰富,今天我们只了解他的 JWT 部分,之后在介绍基于第三方认证的 OAuth 技术时还会进一步讲解 安装使用 pip 安装 pip install Authlib 如果一切正常,可以导入 Authlib 模板,例如,引入 jwt : >>> from authlib.jose import jwt >>> 小试牛刀JWT 是服务器端的机制,所以可以在命令行中做测试 生成 token>>> from authlib.jose import jwt >>> header = {'alg': 'HS256'} >>> payload = {'iss': 'Authlib', 'sub': '123', 'name': 'bob'} >>> secret = '123abc.' >>> token = jwt.encode(header, payload, secret) >>> print(token) b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJpc3MiOiJBdXRobGliIiwic3ViIjoiMTIzIiwibmFtZSI6ImJvYiJ9. cBo6e7Uss5__16mlqZECjHJSKJDdyisevDP5cUGvJms'
解码 token接上面的环境: >>> claims = jwt.decode(token, secret) >>> print(claims) {'iss': 'Authlib', 'sub': '123', 'name': 'bob'} >>> print(claims.header) {'alg': 'HS256', 'typ': 'JWT'} >>> claims.validate() >>>
虽然 JWT 理论很繁琐,但 Authlib 库提供了简洁的方法,让开发应用变得更高效 与客户端交互JWT 之所有流行,有个重要原因是可以支持多种客户端,例如 浏览器和 app,JWT 标准规定,一般情况下,客户端需要将 token 放在 Http 请求的 Header 中的 Authorization 字段中,举个例子: GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer mF_9.B5f-4.1JqM
除了通过 Http Header 类携带 token 之外,还可以通过 POST 请求主体,以及 URL 中的 querystring 来向服务器发送 token,这两种情况下,需要使用 access_token 字段来表示 token
Flask JWTAuthlib 主要的用途在打造一个 OAuth 应用,对于单独做 JWT 的实践有些麻烦,因此我们用 flask-jwt 框架,做 JWT 的实践。 flask-jwt 和之前讲述的 flask-login 用法很像,是基于 JWT 的认证的框架,提供和很多方便实践的特性 安装 flask-jwtpip install Flask-JWT 创建应用为了简单,将所有代码放在 app.py 中: from flask import Flask from flask_jwt import JWT, jwt_required, current_identity from werkzeug.security import safe_str_cmp
# User 类,用于模拟用户实体 class User(object): def __init__(self, id, username, password): self.id = id self.username = username self.password = password
def __str__(self): return "User(id='%s')" % self.id
# User 实体集合,用于模拟用户对象的缓存 users = [ User(1, 'user1', 'abcxyz'), User(2, 'user2', 'abcxyz'), ]
username_table = {u.username: u for u in users} userid_table = {u.id: u for u in users}
# 获取认证的回调函数,从 request 中得到登录凭证,返回凭证所代表的 用户实体 def authenticate(username, password): user = username_table.get(username, None) if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): return user
# 通过 token 获得认证主体的回调函数 def identity(payload): user_id = payload['identity'] return userid_table.get(user_id, None)
app = Flask(__name__) app.debug = True app.config['SECRET_KEY'] = 'super-secret'
jwt = JWT(app, authenticate, identity) # 用 JWT 初始化应用
@app.route('/protected', methods= ["GET", "POST"]) # 定义一个 endpoint @jwt_required() # 声明需要 token 才能访问 def protected(): return '%s' % current_identity # 验证通过返回 认证主体
if __name__ == '__main__': app.run() 运行: $ python app.py * Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 566-326-511 获取 access_tokenflask-jwt 默认的获取 token 的路由是 $ curl -X POST -H "Content-Type: application/json" localhost:5000/auth -d '{"username":"user1","password":"abcxyz"}' { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. eyJleHAiOjE...<省略>...VudGl0eSI6MX0. M-shnDPAVdu...<省略>...LaH1EMIbrWjPto" } 如果登录凭证正确,则返回 access_token,可以看到被 使用 access_tokenflask-jwt 默认通过 Header 传送 token,为了和 OAuth 生成的 JWT 做区分,默认使用 curl -H "Authorization: jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE...<省略>...VudGl0eSI6MX0.M-shnDPAVdu...<省略>...LaH1EMIbrWjPto" localhost:5000/protected User(id='1') 如果 token 有效,则返回 token 对应的认证实体,这个例子中打印出了 user 实体 总结本节课程讲解了基于 token 验证的 JWT,使用 Authlib 库对 JWT 做了实践练习,期望能帮助您更好的理解 JWT,最后通过 flask-jwt 模块,实践了 JWT 的验证方式,和使用方式。在后续的课程中还会对目前流行的第三方认证框架 OAuth 做介绍,敬请期待。 参考
|
|