Python flask之token相关知识及HTTPBasicAuth的使用

目录

1、网站的用户登录流程:

2、API的用户流程:

3、token令牌的三个基本特征

4、代码实现

5、令牌的使用思路

6、编写验证token的装饰器

7、使用HTTPBasicAuth的方式发送账号密码

8、通过HTTPBasicAuth的方式发送token

9、验证token

验证token是否合法:

验证令牌是否过期:

读取令牌信息:

10、关于8和9的代码总览


下面的配置放在xxx.py文件中,而且conf文件夹是一个python的包文件,在IDEA中的图标是

COOKIE_EXPIRATION = 30 * 24 * 3600  # 秒(到期浏览器自动删除)
TOKEN_EXPIRATION = 30 * 24 * 3600  # 秒(到期报错SignatureExpired)
USERNAME = 'zhangsan'
PASSWORD = 'lisi'
SECRET_KEY = 'k#6@1%8)a'

 

 

 from flask import request, jsonify, Flask, make_response, flash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired
from flask_httpauth import HTTPBasicAuth
from commonUtils import _get_parameters
 
app = Flask(__name__)
app.config.from_object('conf.secure')  # 注意这里的路径要相对于flask核心对象的__name__来写相当路径
app.config.from_object('conf.setting')
 
auth = HTTPBasicAuth()
 
 
@app.route('/generate_token')
def generate_auth_token():
    """生成token令牌"""
    s = Serializer(app.config['SECRET_KEY'], expires_in=app.config['TOKEN_EXPIRATION'])
    temp = s.dumps({app.config['USERNAME']: app.config['PASSWORD']})
 
    # date = datetime.datetime.today() + datetime.timedelta(days=app.config['COOKIE_EXPIRATION'])
    response = make_response()
    response.set_cookie(temp, '', max_age=app.config['COOKIE_EXPIRATION'])
 
    return response  # eyJhbGciOiJIUzUxMiIsImlhdCI6MTU1MDIxNDUzNiwiZXhwIjoxNTUyODA2NTM2fQ.eyJpZCI6InpoYW5nc2FuIn0.c30V7Cid1_5s5QAj9-cQCDmLg1xbup41PsY99iVtX2D52r_we7cvja7KOl0MOILjWTQ3M3FlhBO6TgIwFI_H5g
 
 
@auth.verify_password
def verify_auth_token(token, password):  # 注意只能两个参数
    """验证token"""
    s = Serializer(app.config['SECRET_KEY'])
    try:
        data = s.loads(token)  # {'id': 'zhangsan'}
    except SignatureExpired:
        raise SignatureExpired('令牌已过期')
    except BadSignature:
        raise BadSignature('令牌不合法')
    if data.get(app.config['USERNAME']) == app.config['PASSWORD']:
        return True
    else:
        return False
 
 
@app.route('/')
@auth.login_required  # 通过Authorization value=token:''
def index():
    return "hello"

 

1、网站的用户登录流程:

  • 在网页中登录需要提供用户的身份信息(账号、密码)然后发送到网页后端之后,如果网页后端验证账号密码是正确的,它将把一个票据写入到cookie中,最后再把这个cookie返回到浏览器中,并且由浏览器存储住这个cookie,那么下一次我们使用浏览器访问我们的网站的时候呢,就不在需要登录了,因为浏览器有存储我们用户的票据,这个票据就是用户的一个身份标识,只要携带这个票据网页就会认为此次访问是合法的。

2、API的用户流程:

API的访问流程和网页是差不多的,不一样的是,第一我们不一定是使用网页去访问API,我们可以由小程序app甚至是Postman都可以去访问API,其次我们不一定是由浏览器去访问的,所以不一定有cookie,我们就没办法把票据保存在cookie中了。

和右边是差不多的,唯一的区别在API验证了用户的账号和密码之后,它会返回一个token,token我们叫做令牌,令牌简单理解呢就是一个加密的字符串,那么API把令牌返回给我们的客户端之后,客户端要做的一个事情就是,他必须自己去管理和存储这个token,不在是像我们之前利用浏览器的cookie的机制来存储用户的身份了。客户端存储令牌的方式有很多种,根据不同的客户端机制有不同的存储方式,比如我们的小程序就是把token存储在storage中的,换句话说我们的API只负责发放我们的令牌,不负责管理这个令牌,客户端在拿到了这个令牌之后,下一次他想以合法的身份去访问这个API的话,他依然是需要携带这个令牌的。

3、token令牌的三个基本特征

  • 有效期:比如说我们服务器在颁发一个令牌的时候规定有效期是2个小时,那么客户端在2个小时之后再用这个令牌去访问我们的服务器他就不可能被认为是一个合法的用户。
  • 一个token必须能标识出用户的身份来,这一点非常重要,我们使用的一个方式就是在token这个令牌中存储用户的ID号,这样当我们的服务器接收到一个令牌的时候就知道是哪个用户了,
  • token一定是要加密的

4、代码实现

登陆的这个方法我们叫做get_token(),是因为登陆就是在获取token,只要拿到了这个token就是已经完成了登陆,而外部网站这里可能就会叫login()了,对于API来说login的操作就是过去token的过程。

通常来说get_token()是要把http的动词设置成get的,但是由于我们这里是要传入账号和密码的,所以这里我们要小小的违背一下restful原则,我们把GET的操作也要定义成POST,因为POST的传参是相对于GET更安全的,如果我们用GET的方式来传递用户和密码的话,我们只能够把这两个参数放在URL后面的?里作为查询参数来传递。但是使用POST的话,我们可以把这两个参数放在HTTP的body里面来传递。

上图表示用户验证已经通过了,并且我们也拿到了用户的id号,下一步我们就要生成我们的token了。

本来生成令牌是一件比较复杂的事情,是要加密的,但是好在flask自带了一个itsdangerous库,这个库里面有很方便的方法去帮助我们去生成令牌。下图就是生成令牌的方法,我们之前讲过的。

点击下面的传送门去看之前关于生成加密字符串的相关知识。

Python Flask itsdangerous的使用

与上次不同的是,我们不仅仅要写入用户的uid号,还要写入用户的客户端种类ac_type,scope是权限作用域,expiration是过期时间,但是是秒,也就是2个小时。SECRET_KEY就相当于是钥匙,只有有这个钥匙的人才能解开令牌里面所写入的信息。用dumps方法把想写入的信息写入到令牌中去,最终return回去的是一个字符串,这个字符串就是我们生成的token令牌。

最后的代码如下。

因为我们可能经常调整令牌的过期时间,所以我们要把过期时间写入到配置文件中去。

因为我们在开发的时候经常要用,如果设置太短了很烦。所以这里是30天24小时3600秒

5、令牌的使用思路

在客户端拿到这个token令牌只有,有两个问题,

  • 这个令牌有什么用?
  • 这个令牌如何使用?

接口保护,即要保护我们的接口不被随意访问,又要保证用户每一次的身份都是合法的,而且不让他们频繁的输入账号密码。

token的来源就是用户输入了他的账号密码验证通过之后我们给他的一个凭证,只要在访问我们的接口的时候携带这个令牌,而且这令牌没有过期,那么我们就认为这个用户的身份是合法的,在生产中token要设置的很短,2个小时或者是1个小时,因为token存储在客户端是很危险的行为,有可能被其他的人给盗走了。

现在我们的思路就是:访问接口的时候携带令牌,并验证是否是合法的、是否过期,只要是,我们就可以从token中读取用户信息

6、编写验证token的装饰器

验证token的代码不应该写到每一个视图函数中,那我们就用装饰器的方式

flask不需要我们从头编写这样的装饰器,提供给我们一个比较好用的,我们直接使用就行了。

新建一个token_auth.py

from flask_httpauth import HTTPBasicAuth

实例化HTTPBasicAuth

auth = HTTPBasicAuth()

注意HTTPBasicAuth并不是一个装饰器,他是一个对象

我们在需要保护接口的文件中导入auth

在要保护的接口上面打上@auth.login_required

然后在保护接口之前要执行的方法上面打上@auth.verify_password

这样在客户端发送http请求访问保护接口之前就会先调用一下verify_password方法并把http请求中的token取出来传递到verify_password方法中,这样我们就可以在verify_password函数中来验证这个token了,verify_password方法名随意不一定非要叫这个。如果token验证是合法的我们就继续执行保护接口内的方法。如果token验证不通过我们就返回给客户端一个错误,返回错误之后我们后面的保护接口的方法就不会被执行了,从而就实现了保护接口的目的。

下面我们访问被保护的接口,发现显示如下,因为我们在verify_password方法里面什么都没做,也就是我们认为验证是没有通过的,所以是不会执行被保护的接口的。

下面我们把pass修改成return True

可以看到如下的访问结果

但是我们在调试代码的时候发现account和password的值是空的,这是因为我们没有传递进来,没有发送账号和密码。

7、使用HTTPBasicAuth的方式发送账号密码

发送账号和密码的方式有很多,这里我们用HTTPBasicAuth的方式,HTTPBasicAuth规定了发送账号密码必须要把账号和密码放到HTTP的header里面。header是一组一组key:value的键值对。key=Authorization,value=账号:密码

这是我们看一下发现还是空

这是因为规范还有一些附加条件。最终的规范就是下图所示啦

我们从网上找一个base64加密的网页

最后修改一下Postman

发现下图正确的得到了账号和密码

8、通过HTTPBasicAuth的方式发送token

解决这个问题要灵活一点,我们可以把token就当做账号,密码我们不传或者传一个空。这样问题就解决了。

实现本篇文章第4点,复制我们生成的令牌

首先介绍一个简单的Postman使用技巧来简化之前先转化成Base64加密再添加basic空格的方式,如下图

可以看到有很多的验证方式,不只是Basic Auth这一种,下图我们把复制的令牌填入进去,点击Send

可以看到变成了我们的令牌,这样就是实现了token的Basic Auth传递

9、验证token

我们之前的verify_password方法可以拿到客户端传过来的token,下面我们单独编写一个函数用来验证token的合法性

解密同样需要一个itsdangerous的序列化器。

关于token我们需要做两个验证,第一就是 是否合法。第二就是验证令牌是否过期

验证token是否合法:

如果解密的时候抛出BadSignature异常我们就认为是不合法的token。

是否合法可以通过捕捉一个特定的异常来检验这个token是否是合法的,我们通过itsdangerous导入BadSignature

如果产生了BadSignature错误的话就说明token是不合法的

验证令牌是否过期:

如果解密的时候抛出SignatureExpired异常我们就认为是不合法的token。同样需要通过itsdangerous导入一下

读取令牌信息:

可以看到之前我们定义了uid和type信息

所以代码如下:

但是发现return成元组不太好,读取要使用01序号读取,我们也可以返回字典,但是这样也不好,我们可以使用namedtuple快速的定义对象式的结构返回回去。

最后的验证token的代码如下图所示:下图应先验证是不是SignatureExpired后验证BadSignature异常

10、关于8和9的代码总览

 from collections import namedtuple
 
from flask import current_app, g, request
from flask_httpauth import HTTPBasicAuth
from itsdangerous import TimedJSONWebSignatureSerializer \
    as Serializer, BadSignature, SignatureExpired
 
from app.libs.error_code import AuthFailed, Forbidden
from app.libs.scope import is_in_scope
 
__author__ = 'jenrey'
 
auth = HTTPBasicAuth()
User = namedtuple('User', ['uid', 'ac_type', 'scope'])
 
 
@auth.verify_password
def verify_password(token, password):
    # token
    # HTTP 账号密码
    # header key:value
    # account  qiyue
    # 123456
    # key=Authorization
    # value =basic base64(qiyue:123456)
    user_info = verify_auth_token(token)
    if not user_info:
        return False
    else:
        # request
        g.user = user_info
        return True
 
 
def verify_auth_token(token):
    s = Serializer(current_app.config['SECRET_KEY'])
    try:
        data = s.loads(token)
    
    except SignatureExpired:
        raise AuthFailed(msg='token is expired',
                         error_code=1003)
    except BadSignature:
        raise AuthFailed(msg='token is invalid',
                         error_code=1002)
    uid = data['uid']
    ac_type = data['type']
    scope = data['scope']
    # request 视图函数
    allow = is_in_scope(scope, request.endpoint)
    if not allow:
        raise Forbidden()
    return User(uid, ac_type, scope)

 

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Flask 中实现 Token 机制,可以使用 JWT(JSON Web Tokens)库。下面是一个简单的示例代码: ```python from flask import Flask, request 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' app.config['JWT_EXPIRATION_DELTA'] = timedelta(days=1) class User: def __init__(self, id, username, password): self.id = id self.username = username self.password = password def __str__(self): return f"User(id='{self.id}')" users = [ User(1, 'user1', 'password1'), User(2, 'user2', 'password2'), ] def authenticate(username, password): user = next((user for user in users if user.username == username), None) if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): return user def identity(payload): user_id = payload['identity'] return next((user for user in users if user.id == user_id), None) jwt = JWT(app, authenticate, identity) @app.route('/protected') @jwt_required() def protected(): return f'Hello, {current_identity}!' if __name__ == '__main__': app.run() ``` 在上面的代码中,我们首先定义了一个 `User` 类来存储用户信息。然后定义了一个 `authenticate` 函数来用于用户认证,它会查找用户列表中是否存在指定的用户名和密码。如果存在,则返回该用户对象。 接下来,我们定义了一个 `identity` 函数,它会根据 JWT 中存储的用户 ID 来查找用户对象。最后,我们创建了一个 `jwt` 对象,并将其绑定到 Flask 中。 最后,我们定义了一个受保护的路由 `/protected`,并使用 `@jwt_required()` 装饰器来保护它。这意味着在访问该路由时,用户必须先提供有效的 JWT 才能继续访问。 当用户成功提供有效的 JWT 且通过身份验证时,`current_identity` 会返回该用户对象,我们可以在响应中使用它。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值