这一篇我们将用户的信息加密后作为令牌返回到客户端,客户端在访问服务器API时必须以HTTP Basic
的方式携带令牌,我们再读取令牌信息后,将用户信息存入到g变量中,共业务代码全局使用。
1. Token概述
一般登录方式有两种,网页下请求和API
的方式。
我们看下网页的请求。
我们在浏览器输入账号密码,然后发送到服务器,服务器验证通过后会将一个票据写入到cookie
中,最后将cookie
返回到浏览器中,由浏览器保存,下次再访问的时候就不再需要登录了。
我们再看下API
的方式。
相比于浏览器请求,我们使用API
的方式的时候需要自己管理票据而不是通过浏览器cookie
的方式了。
使用API
方式登录成功后,服务端将Token
发送给客户端,由客户端存储,在下次登录的时候,携带上这个Token
。这样我们就完成了用户的验证。
一般Token
都有几个标识:
- 有效期:一般安全考虑要设置一个有效期
- 可以标识用户身份:能够标识用户才能完成用户的登录,比如我们存储一个用户ID到
Token
中。 Token
要加密:这样才能防止被篡改
2. 生成Token令牌返回给客户端
我们已经知道API
的流程,现在我们首先需要在用户验证登录的时候生成一个Token
返回给客户端。
1 | from flask import current_app, jsonify |
上面代码调用代码
1 | # User模型下 |
AuthFailed
也是自定义的异常类。
1 | def generate_auth_token(uid, ac_type, scope=None, |
完成上面代码之后我们就生成了一个加密有时长的Token
了。
3. Token令牌的作用
我们在API
访问的时候,有些需要登陆才能访问的接口,不能总是让用户输入账号密码。我们可以在第一次登陆成功后将Token
返回给客户端,等以后访问的时候携带上Token
,通过验证Token
(是否合法,是否过期),当是有效Token
的时候我们就能获取到用户相关的信息。
4. @auth拦截器执行流程
如何验证客户端传来的Token
呢,在哪验证呢?在每个接口的地方都验证难免会重复。
这种需求一般我们都是使用装饰器来完成。Flask
已经帮我们设计好了这种装饰器。
我们可以使用HTTPBasicAuth
提供的装饰器来进行接口保护。
1 | from flask_httpauth import HTTPBasicAuth |
1 | from app.libs.token_auth import auth |
我们看下上面的代码,解释下执行流程。
首先我们实例化一个HTTPBasicAuth
对象,创建一个verify_password
方法,传入的参数是账户名和密码。并加上auth.verify_password
装饰器。然后我们在需要保护的API
接口视图函数上增加装饰器auth.login_required
。这样,当用户访问受保护的接口时候会先执行verify_password
方法,只有verify_password
返回True
的时候才能继续访问API
视图函数,否则将禁止继续访问。
5.HTTPBasicAuth的基本原理
有没有小伙伴有疑问 在方法verify_password
传入的账号,密码怎么传递呢?
这里的传递方式是通过在HTTP
的头信息中加入账号密码来传递的,而不是像之前使用Form
方式在HTTP
的body
中传递。
我们知道在HTTP
的头部信息都是键值对的形式,HTTPBasicAuth
规定的也是键值对的形式,不过稍微有所不同。
1 | Authorization: basic base64(account:password) |
我在上面写出来样式,详细探讨下。
键值对的键为Authorization
,值的开头是basic
+ 空格 + base64
加密过后的账号和密码。
注意: 账后和密码中间有冒号
上面就是在Postman中的使用。
有的小伙伴要说了,我们这里使用的是token
登录,不是账号密码,应该如何传递呢?
很简单,我们只需不要传密码就好了,将account
改为token
即可。
6. 验证token
我们既然已经知道如何生存token
,和获得token
了,下面就是验证token
的正确性了。
1 | from itsdangerous import TimedJSONWebSignatureSerializer \ |
调用验证方法
1 |
|
上面就是完成的API
模式下的登录验证。
文档1:https://flask-httpauth.readthedocs.io/en/latest/
文档2:http://www.pythondoc.com/flask-restful/third.html#id5
参考博客1:http://www.bjhee.com/flask-ext9.html
7. 重写first_or_404和get_or_404
之前我们有过重写filter_by
函数,现在我们重写下first_or_404
和get_or_404
。
先看下get_or_404
的源码:
1 | def get_or_404(self, ident): |
我们在调用abort
的地方改成我们自定义的异常抛出,而不是抛出HTTPException
异常。
1 | def get_or_404(self, ident): |
我们再看下first_or_404
的源码:
1 | def first_or_404(self): |
我们同样是在abort
的地方调用我们自己的异常。
1 | def first_or_404(self): |
其中的NotFound
为:
1 | class NotFound(APIException): |
这样我们在查询数据库的时候,无需再进行异常捕捉。
1 | user = User.query.filter_by(email=email).first_or_404() |