最近在逛网站看到 flask + vue.js 前后端分离,就开始做了个小 demo , 考虑到交互安全性的问题,就使用 Flask 设计 RESTful 的认证和前端交互数据,废话不哆嗦,如下看:
数据库
为了让给出的示例看起来像真实的项目,这里我将使用 Flask-SQLAlchemy 来构建用户数据库模型并且存储到数据库中(这里我用的是 MYSQL ,你也可以用其他的!)
用户的数据库模型是十分简单的。对于每一个用户,username 和 password 将会被存储:
出于安全原因,用户的原始密码将不被存储,密码在注册时被散列后存储到数据库中。使用散列密码的话,如果用户数据库不小心落入恶意攻击者的手里,他们也很难从散列中解析到真实的密码。
密码散列
为了创建密码散列,我将会使用 werkzeug.security库,一个专门用于密码散列的 Python 包。
from werkzeug.security import generate_passwod_hash, check_passwod_hash
password() 函数接受一个明文的密码作为参数并且存储明文密码的散列。当一个新用户注册到服务器或者当用户修改密码的时候,这个函数将被调用。
verify_password() 函数接受一个明文的密码作为参数并且当密码正确的话返回 True 或者密码错误的话返回 False。这个函数当用户提供和需要验证凭证的时候调用。
你可能会问如果原始密码散列后如何验证原始密码的?
散列算法是单向函数,这就是意味着它们能够用于根据密码生成散列,但是无法根据生成的散列逆向猜测出原密码。然而这些算法是具有确定性的,给定相同的输入它们总会得到相同的输出。
用户注册
Flask 中的路由实现
参数 username 和 password 是从请求中携带的 JSON 数据中获取,接着验证它们。
如果参数通过验证的话,新的 User 实例被创建。username 赋予给 User,接着使用 hash_password 方法散列密码。用户最终被写入数据库中。
用户注册这一块在命令行出现了问题,找不到问题所在,就用来 Postman 注册了
返回的信息是成功的!
基于密码的认证
现在我们假设存在一个资源通过一个 API 暴露给那些必须注册的用户。这个资源是通过 URL: /api/resource 能够访问到。
为了保护这个资源,我们将使用 HTTP 基本身份认证,但是不是自己编写完整的代码来实现它,而是让 Flask-HTTPAuth 扩展来为我们做。
使用 Flask-HTTPAuth,通过添加 login_required 装饰器可以要求相应的路由必须进行认证:
但是,Flask-HTTPAuth 需要给予更多的信息来验证用户的认证,当然 Flask-HTTPAuth 有着许多的选项,它取决于应用程序实现的安全级别。
能够提供最大自由度的选择就是选用 verify_password 回调函数,这个回调函数将会根据提供的 username 和 password 的组合的,返回 True(通过验证) 或者 Flase(未通过验证)。Flask-HTTPAuth 将会在需要验证 username 和 password 对的时候调用这个回调函数。
verify_password 回调函数的实现如下:
这里是用 curl 请求只允许注册用户获取的保护资源:
如果登录失败的话,会得到下面的内容:
基于令牌的认证
每次请求必须发送 username 和 password 是十分不方便,即使是通过安全的 HTTP 传输的话还是存在风险,因为客户端必须要存储不加密的认证凭证,这样才能在每次请求中发送。
一种基于之前解决方案的优化就是使用令牌来验证请求。
我们的想法是客户端应用程序使用认证凭证交换了认证令牌,接下来的请求只发送认证令牌。
令牌是具有有效时间,过了有效时间后,令牌变成无效,需要重新获取新的令牌。令牌的潜在风险在于生成令牌的算法比较弱,但是有效期较短可以减少风险。
令牌的生成以及验证将会被添加到 User 模型中,其具体实现如下:
generate_auth_token() 方法生成一个以用户 id 值为值,’id’ 为关键字的字典的加密令牌。令牌中同时加入了一个过期时间,默认为十分钟(600 秒)。
验证令牌是在 verify_auth_token() 静态方法中实现的。静态方法被使用在这里,是因为一旦令牌被解码了用户才可得知。如果令牌被解码了,相应的用户将会被查询出来并且返回。
API 需要一个获取令牌的新函数,这样客户端才能申请到令牌:
注意:这个函数是使用了 auth.login_required 装饰器,也就是说需要提供 username 和 password。
剩下来的就是决策客户端怎样在请求中包含这个令牌。
HTTP 基本认证方式不特别要求 usernames 和 passwords 用于认证,在 HTTP 头中这两个字段可以用于任何类型的认证信息。基于令牌的认证,令牌可以作为 username 字段,password 字段可以忽略。
这就意味着服务器需要同时处理 username 和 password 作为认证,以及令牌作为 username 的认证方式。verify_password 回调函数需要同时支持这两种方式:
新版的 verify_password 回调函数会尝试认证两次。首先它会把 username 参数作为令牌进行认证。如果没有验证通过的话,就会像基于密码认证的一样,验证 username 和 password。
如下的 curl 请求能够获取一个认证的令牌:
现在可以使用令牌获取资源:
需要注意的是这里并没有使用密码。
OAuth 认证
当我们讨论 RESTful 认证的时候,OAuth 协议经常被提及到。
那么什么是 OAuth?
OAuth 可以有很多的含义。最通常就是一个应用程序允许其它应用程序的用户的接入或者使用服务,但是用户必须使用应用程序提供的登录凭证。我建议阅读者可以浏览 OAuth 了解更多知识。
好了, 一点点的理解,希望可以帮到你!