flask之token认证

flask之token认证

使用token的理由

为符合restful api风格,我们的服务器应该是无状态的,即不应该在服务端维持每个用户状态,不需要为每个登录用户维护一个会话,从而改进服务器的性能

这种无状态的前后端通信可以通过token实现,具体讲就是

  • 后端验证密码成功,用户成功登录之后,将用户的身份信息加密成有一定时效性的token返回前端
  • 前端后续每次请求都将token插入请求header当中,供后端认证请求者的身份
  • 后端得到任意请求后检查请求header中的token,解密token得到用户身份信息,然后进行后续操作

从头到尾,服务器都没有维持过用户的登录信息,服务器都不知道具体的某个用户是否是在登录状态,只有等到请求到达,检查出token合法,服务器才知道,这个用户是处于登录之中的

flask中封装token的package使用

flask在itsdangerous这个package中封装了TimedJSONWebSignatureSerializer类,即有一定时效性的json web token(简称jwt)

使用方法

  1. 构造实例时传入秘钥参数、有效时长参数’expires_in’(单位是s),这个秘钥应该由你自己设定,不建议使用明文写在代码、或者明文存在磁盘数据库中,为了安全起见,建议在程序运行时手动输入一个秘钥,由程序读取后存入内存,下次服务器程序再次启动时重新手动输入上次输入的秘钥,一次保证安全性

  2. 使用上一步构造的实例,调用TimedJSONWebSignatureSerializer类的dumps方法,传入一个对象以此创建一个带有加密用户信息、具有时效性的token,对象中的键值对记录了该用户的信息,比如 { id:‘1’, username:‘user’ } 就是一个合格的例子。创建好之后,将这个token放到response的body中,返回给前端

  3. 当有请求从前端过来,解析request中的header,获取其中的token字段,进行解密,判断是否有SignatureRxpired过期、BadSignature无法解密,如果都没有,就将解密的用户信息放入服务器内存,进行使用

具体代码如下:

# 步骤1代码:
s = Serializer(current_app.config['SECRET_KEY'], expires_in=3600)

# 步骤2代码:
return jsonify(data={
    'message':'Login successfully',
    'token':s.dumps({'idUser': user['idUser'], 'email': user['email']}).decode('utf-8')
})

#前端收到token后将token放入后续请求的头部:
get({headers={'Authorization':'加密后的token'},body={}})

# 步骤3代码
# 请求代码:在请求函数前加上login_required
@bp.route('/user/', methods=['GET'])
@auth.login_required
def get_info():
    pass

上面的代码中,我们看到,通过在请求处理函数之前加上修饰@auth.login_required就能够完成token验证(如果验证失败则通过另外的处理机制向前端返回报错),其实只有上面的代码还不够,我们还需要定义login_required函数来处理、解析token:

想要定义自己的login_required函数,可以这样做:

from flask_httpauth import HTTPTokenAuth
auth = HTTPTokenAuth()

@auth.verify_token
def verify_token(token):
    g.user = None
    s = Serializer(current_app.config['SECRET_KEY'])
    try:
        data = s.loads(token)
    except SignatureExpired:
        # token正确但是过期了
        return False
    except BadSignature:
        # token错误
        return False
    if 'idUser' in data:
        db = get_db()
        g.user = db.execute(
            'SELECT * FROM User WHERE idUser = ?', (data['idUser'],)
        ).fetchone()
        return True
    return False

当然,上面提到的处理验证失败的机制也是可以自定义的,可以这样:

@auth.error_handler
def error_handler():
    return jsonify({ code:401, message:'401 Unauthorized Access' })

实现原理探究

如果你只想知道怎么做能够完成flask的token认证,那么这篇教学到此为止,可以不用看下去了

但是作为一名合格的程序员,我们不应该满足于怎么做,还应该去探求为什么,所以我们是不是应该进一步知道为什么这样写可以呢:

想要知道为什么,最简单的方法就是翻源码,直接到flask_httpauth的github上看具体实现。

flask_httpauth.HTTPTokenAuth的实现,代码&解读如下:

# HTTPTokenAuth类继承HTTPAuth
class HTTPTokenAuth(HTTPAuth):
    def __init__(self, scheme='Bearer', realm=None):
        super(HTTPTokenAuth, self).__init__(scheme, realm)

        self.verify_token_callback = None

    def verify_token(self, f):
        self.verify_token_callback = f
        return f

    def authenticate(self, auth, stored_password):
        if auth:
            token = auth['token']
        else:
            token = ""
        if self.verify_token_callback:
            return self.verify_token_callback(token)
        return False
    
# 父类HTTPAuth
class HTTPAuth(object):
    def __init__(self, scheme=None, realm=None):
        self.scheme = scheme
        self.realm = realm or "Authentication Required"
        self.get_password_callback = None
        self.auth_error_callback = None

        def default_get_password(username):
            return None

        def default_auth_error():
            return "Unauthorized Access"

        self.get_password(default_get_password)
        self.error_handler(default_auth_error)
        
	# 认证错误处理机制
    def error_handler(self, f):
        @wraps(f)
        def decorated(*args, **kwargs):
            res = f(*args, **kwargs)
            res = make_response(res)
            if res.status_code == 200:
                res.status_code = 401
            if 'WWW-Authenticate' not in res.headers.keys():
                res.headers['WWW-Authenticate'] = self.authenticate_header()
            return res
        # 把auth_error_callback设置为自己,login_required认证失败之后调用自己,处理异常
        self.auth_error_callback = decorated
        return decorated
    
	# 登录需要
    def login_required(self, f):
        @wraps(f)
        def decorated(*args, **kwargs):
            auth = self.get_auth()
            if request.method != 'OPTIONS':  # pragma: no cover
                password = self.get_auth_password(auth)

                if not self.authenticate(auth, password):
                    request.data
                    return self.auth_error_callback()

            return f(*args, **kwargs)
        return decorated

为了抓住重点,我上面只是贴了重要部分的代码,完整代码参见flaskauth

其实你可能会疑惑,为什么完成token验证是在请求处理函数之前加上修饰@auth.login_required但是实际上定义自己的login_required函数是通过@auth.verify_token修改来完成的,下面就为你解答:

我们可以试着溯源,从请求到达后端开始分析,前端发来/user/,method=get的api过来,被flask通过路由@bp.route(’/user/’, methods=[‘GET’]),引导到get_info函数,然而在执行get_info函数之前,需要先执行login_required

login_required是哪来的呢,我们翻看HTTPTokenAuth,没有这个成员函数,再向前看,看到HTTPTokenAuth的父类HTTPAuth,终于找到HTTPTokenAuth.login_required函数

这个函数接受一个函数f作为参数,并对f进行修饰,其实在这里,get_info函数就是这个f,login_required中,先执行用户认证,认证通过再调用f,即get_info,否则调用auth_error_callback处理错误

  • 正常处理:

    • login_required中通过get_auth获得request.header.Authorization,这里面存的就是token

    • 然后调用authenticate函数进行认证,authenticate不是父类HTTPAuth的成员,所以找回HTTPTokenAuth,果然找到了

    • 在HTTPTokenAuth的authenticate中,调用了verify_token_callback

      • verify_token_callback又是哪来的呢:

        def verify_token(self, f):
            self.verify_token_callback = f
            return f
        
      • 即verify_token将输入的f,即我们自定义的那个verify_token赋值给self.verify_token_callback

        @auth.verify_token
        def verify_token(token):
        
      • 即在HTTPTokenAuth的authenticate中调用了我们自定义的verify_token

    • 上面authenticate调用了自定义的verify_token进行认证,如果不出错就将解密的用户信息从数据库载入服务器内存,否则返回false给login_required

    • login_required接到false的话就调用后面的‘认证不通过处理’函数

  • 认证不通过处理:

    • 如果正常处理中authenticate返回False,认证失败,那么将调用self.auth_error_callback进行认证失败处理
    • self.auth_error_callback来自于HTTPAuth类的error_handler,error_handler接受一个被修饰的函数f(我们可以通过@auth.error_handler来自定义这个f),在上面的代码中,被修饰的f就是我们自定义的error_handler,在这个error_handler中,我们直接返回身份认证失败的信息,错误码401
  • 到此为止,案子破了----接到请求后,login_required

    • 先通过get_auth获得request.header.Authorization中的token
    • 然后调用authenticate,进而调用我们自己定义的verify_token来解析token
      • 如果解析不出错,就将解密的用户信息从数据库载入服务器内存
      • 否则返回false
    • login_required接到false后调用error_handler进而调用自定义的error_handler,返回身份认证失败的信息,错误码401

总结

如果您只想知道用法,那么仅仅需要了解flask中封装token的package使用

如果您想要知道为什么能够做到认证,那么您还需要看实现原理探究

以上,足以讲清楚flask如何使用token认证用户身份这一技术,以及为什么能够这样做,让您能够知其然并且知其所以然。

最后感谢阅读

  • 15
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值