上一篇文章, 使用python的Flask实现一个RESTful API服务器端  简单地演示了Flask实的现的api服务器,里面提到了因为无状态的原则,没有session cookies,如果访问需要验证的接口,客户端请求必需每次都发送用户名和密码。通常在实际app应用中,并不会每次都将用户名和密码发送。

这篇里面就谈到了产生token的方法。

完整的例子的代码

可以在github:REST-auth 上找到。作者欢迎大家上去跟他讨论。

创建用户数据库

这个例子比较接近真实的项目,将会使用Flask-SQLAlchemy (ORM)的模块去管理用户数据库。

user model 非常简单。每个用户只有 username 和 password_hash 两个属性。

class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(32), index = True)
password_hash = db.Column(db.String(128))

因为安全的原因,明文密码不可以直接存储,必需经过hash后方可存入数据库。如果数据库被脱了,也是比较难破解的。

密码永远不要明文存在数据库中。

Password Hashing

这里使用PassLib库对密码进行hash。

PassLib提供几种hash算法。custom_app_context模块是基于sha256_crypt加密算法,使用十分简单。

对User model增加密码hash和验证有两办法:

from passlib.apps import custom_app_context as pwd_context

class User(db.Model):
# ... def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password) def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)

当一个新的用户注册,或者更改密码时,就会调用hash_password()函数,将原始密码作为参数传入hash_password()函数。

当验证用户密码时就会调用verify_password()函数,如果密码正确,就返回True,如果不正确就返回False。

hash算法是单向的,意味着它只能hash密码,但是无法还原密码。但是这些算法是绝对可靠的,输入相同的内容,那么hash后的内容也会是一样的。通常注册或者验证时,对比的是hash后的结果。

用户注册

在这个例子里,客户端通过发送 POST 请求到 /api/users 上,并且请求的body部份必需是JSON格式,并且包含 username 和 password 字段。

Flask 实现的代码:

@app.route('/api/users', methods = ['POST'])
def new_user():
username = request.json.get('username')
password = request.json.get('password')
if username is None or password is None:
abort(400) # missing arguments
if User.query.filter_by(username = username).first() is not None:
abort(400) # existing user
user = User(username = username)
user.hash_password(password)
db.session.add(user)
db.session.commit()
return jsonify({ 'username': user.username }), 201, {'Location': url_for('get_user', id = user.id, _external = True)}

这个函数真是简单极了。只是用请求的JSON里面拿到 username 和 password 两个参数。

如果参数验证通过,一个User实例被创建,密码hash后,用户资料就存到数据库里面了。

请求响应返回的是一个JSON格式的对象,状态码为,并且在http header里面定义了Location指向刚刚创建的用户的URI。

注意:get_user函数没有在这里实现,具体查以查看github。

试试使用curl发送一个注册请求:

$ curl -i -X POST -H "Content-Type: application/json" -d '{"username":"ok","password":"python"}' http://127.0.0.1:5000/api/users
HTTP/1.0 CREATED
Content-Type: application/json
Content-Length:
Location: http://127.0.0.1:5000/api/users/1
Server: Werkzeug/0.9. Python/2.7.
Date: Thu, Nov :: GMT {
"username": "ok"
}

通常在正式的服务器里面,最好还是使用https通讯。这样的登录方式,明文通讯是很容易被截取的。

基于简单密码的认证

现在我们假设有一个API只向已经注册好的用户开放。接入点是/api/resource。

这里使用HTTP BASIC Authentication的方法来进行验证,我计划使用Flask-HTTPAuth这个扩展来实现这个功能。

导入Flask-HTTPAuth扩展模块后,为对应的函数添加login_required装饰器:

from flask.ext.httpauth import HTTPBasicAuth
auth = HTTPBasicAuth() @app.route('/api/resource')
@auth.login_required
def get_resource():
return jsonify({ 'data': 'Hello, %s!' % g.user.username })

那么Flask-HTTPAuth(login_required装饰器)需要知道如何验证用户信息,这就需要具体去实现安全验证的方法了。

有一种办法是十分灵活的,通过实现verify_password回调函数去验证用户名和密码,验证通过返回True,否则返回False。然后Flask-HTTPAuth再调用这个回调函数,这样就可以轻松自定义验证方法了。(注:Python修饰器的函数式编程)

具体实现代码如下:

@auth.verify_password
def verify_password(username, password):
user = User.query.filter_by(username = username).first()
if not user or not user.verify_password(password):
return False
g.user = user
return True

如果用户名与密码验证通过,user对像会被存储到Flask的g对像中。(注:对象 g 存储在应用上下文中而不再是请求上下文中,这意味着即使在应用上下文中它也是可访问的而不是只能在请求上下文中。)方便其它函数使用。

让我们使用已经注册的用户来请求看看:

$ curl -u ok:python -i -X GET http://127.0.0.1:5000/api/resource
HTTP/1.0 OK
Content-Type: application/json
Content-Length:
Server: Werkzeug/0.9. Python/2.7.
Date: Thu, Nov :: GMT {
"data": "Hello, ok!"
}

如果登录错误,会返回以下内容:

$ curl -u miguel:ruby -i -X GET http://127.0.0.1:5000/api/resource
HTTP/1.0 UNAUTHORIZED
Content-Type: text/html; charset=utf-
Content-Length:
WWW-Authenticate: Basic realm="Authentication Required"
Server: Werkzeug/0.9. Python/2.7.
Date: Thu, Nov :: GMT Unauthorized Access

再次重申,真实的API服务器最好在HTTPS下通讯。

基于Token的认证

因为需要每次请求都要发送用户名和密码,客户端需要把验证信息存储起来进行发送,这样十分不方便,就算在HTTPS下的传输,也是有风险存在的。

比前面的密码验证方法更好的是使用Token认证请求。

原理是第一次客户端与服务器交换过认证信息后得到一个认证token,后面的请求就使用这个token进行请求。

Token通常会给一个过期的时间,当超过这个时间后,就会变成无效,需要产生一个新的token。这样就算token泄漏了,危害也只是在有效的时间内。

好多种办法去实现token。一种简单的做法就是产生一个固定长度的随机序列字符与用户名和密码一同存储在数据库当中,有可能带上一个过期时间。这样token就变成了一串普通的字符,可以十分容易地和其它字符串验证对比,并且可以检查时间是否过期。

更复杂的实现办法是不需要服务器端进行存储token,而是使用数字签名信息作为token。这样做的好处是经过用户数字签名生成的token是可以防篡改的。

Flask使用与数字签名有些相似的办法去实现加密的cookies的,这里我们使用itsdangerous的库去实现。

生成token和验证token的方法可以附加到User model上实现:

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

class User(db.Model):
# ... def generate_auth_token(self, expiration = 600):
s = Serializer(app.config['SECRET_KEY'], expires_in = expiration)
return s.dumps({ 'id': self.id }) @staticmethod
def verify_auth_token(token):
s = Serializer(app.config['SECRET_KEY'])
try:
data = s.loads(token)
except SignatureExpired:
return None # valid token, but expired
except BadSignature:
return None # invalid token
user = User.query.get(data['id'])
return user

在generate_auth_token()函数中,token其实就是一个加密过的字典,里面包含了用户的id和默认为10分钟(600秒)的过期时间。

verify_auth_token()的实现是一个静态方法,因为token只是一次解码检索里面的用户id。获取用户id后就可以在数据库中取得用户资料了。

试试使用一个新的接入点,让客户端请求一个token:

@app.route('/api/token')
@auth.login_required
def get_auth_token():
token = g.user.generate_auth_token()
return jsonify({ 'token': token.decode('ascii') })

注意,这个接入点是被Flask-HTTPAuth扩展的auth.login_required装饰器保护的,请求需要提供用户名和密码。

上面返回的是一个token字符串,下面的请求将会包含这个token。

HTTP Basic Authentication协议没有具体要求必需使用用户名和密码进行验证,HTTP头可以使用两个字段去传输认证信息,对于token认证,只需要把token当成用户名发送即可,密码字段可以乎略。

综上所说,一些认证还是要使用用户名和密码认证,另外一部份直接使用获取的token认证。verify_password回调函数则需要包括两种验证的方式:

@auth.verify_password
def verify_password(username_or_token, password):
# first try to authenticate by token
user = User.verify_auth_token(username_or_token)
if not user:
# try to authenticate with username/password
user = User.query.filter_by(username = username_or_token).first()
if not user or not user.verify_password(password):
return False
g.user = user
return True

修改原来的verify_password回调函数,添加两种验证。开始用用户名字段当作token,如果不是token来的,就采用用户名和密码验证。

使用curl测试请求获取一个认证token:

$ curl -u ok:python -i -X GET http://127.0.0.1:5000/api/token
HTTP/1.0 OK
Content-Type: application/json
Content-Length:
Server: Werkzeug/0.9. Python/2.7.
Date: Thu, Nov :: GMT {
"token": "eyJhbGciOiJIUzI1NiIsImV4cCI6MTM4NTY2OTY1NSwiaWF0IjoxMzg1NjY5MDU1fQ.eyJpZCI6MX0.XbOEFJkhjHJ5uRINh2JA1BPzXjSohKYDRT472wGOvjc"
}

再试试使用token一访问受保护的API:

$ curl -u eyJhbGciOiJIUzI1NiIsImV4cCI6MTM4NTY2OTY1NSwiaWF0IjoxMzg1NjY5MDU1fQ.eyJpZCI6MX0.XbOEFJkhjHJ5uRINh2JA1BPzXjSohKYDRT472wGOvjc:unused -i -X GET http://127.0.0.1:5000/api/resource
HTTP/1.0 OK
Content-Type: application/json
Content-Length:
Server: Werkzeug/0.9. Python/2.7.
Date: Thu, Nov :: GMT {
"data": "Hello, ok!"
}

注意,请求里面带了unused字段。只是为了标识而已,替代密码的占位符。

OAuth 认证

谈到RESTful认证,通常会提到OAuth协议。

So what is OAuth?

通常是允许一个应用接入到另外一个应用的数据或者服务的验证方法。

举个例子,如果一个网站或者应用问你权限接入你的facebook账号,并且提交一些东西到你的时间轴上面。这个例子,你就是资源拥有者(你拥有你的facebook时间轴),第三方应用是消费者,facebook是提供者。如果你授权接入允许消费者写东西到你的时间轴上面,是不需要提供你的facebook登录信息的。

OAuth并不合适用在client/server的RESTful API上面,一般是用在你的RESTful API允许第三方应用(消费者)去接入。

上面的例子是,客户端/服务器端之间直接通讯并不需要去隐藏认证信息,客户端是直接发送认证请求信息到服务器端的。

原文来自:http://blog.miguelgrinberg.com/post/restful-authentication-with-flask

使用Flask设计带认证token的RESTful API接口[翻译]的更多相关文章

  1. Flask设计带认证token的RESTful API接口[翻译]

    上一篇文章, 使用python的Flask实现一个RESTful API服务器端  简单地演示了Flask实的现的api服务器,里面提到了因为无状态的原则,没有session cookies,如果访问 ...

  2. 使用Flask设计带认证token的RESTful API接口

    大数据时代 Just a record. 使用Flask设计带认证token的RESTful API接口[翻译] 上一篇文章, 使用python的Flask实现一个RESTful API服务器端  简 ...

  3. Spring Boot入门系列(二十一)如何优雅的设计 Restful API 接口版本号,实现 API 版本控制!

    前面介绍了Spring Boot 如何快速实现Restful api 接口,并以人员信息为例,设计了一套操作人员信息的接口.不清楚的可以看之前的文章:https://www.cnblogs.com/z ...

  4. Java 调用Restful API接口的几种方式--HTTPS

    摘要:最近有一个需求,为客户提供一些Restful API 接口,QA使用postman进行测试,但是postman的测试接口与java调用的相似但并不相同,于是想自己写一个程序去测试Restful ...

  5. Restful API 接口设计标准及规范

    Restful API 接口设计标准以及规范 RESTful概念 理解和评估以网络为基础的应用软件的架构设计,得到一个功能强.性能好.适宜通信的架构.REST指的是一组架构约束条件和原则." ...

  6. Spring Boot入门系列(二十)快速打造Restful API 接口

    spring boot入门系列文章已经写到第二十篇,前面我们讲了spring boot的基础入门的内容,也介绍了spring boot 整合mybatis,整合redis.整合Thymeleaf 模板 ...

  7. 用 shell 脚本做 restful api 接口监控

    问题的提出 基于历史原因,公司有一个"三无"采集服务--无人员.无运维.无监控--有能力做的部门不想接.接了的部门没能力.于是就一直这样裸奔,直到前几天一个依赖于这个采集服务的大数 ...

  8. Postman如何通过xmysql工具的Restful API 接口访问MySQL

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 导语 有时候用 Postman 接口测试需要获取MySQL的查询结果做接口输出的校验,这里介绍下 Postman 通过 R ...

  9. SpringMVC Restful api接口实现

    [前言] 面向资源的 Restful 风格的 api 接口本着简洁,资源,便于扩展,便于理解等等各项优势,在如今的系统服务中越来越受欢迎. .net平台有WebAPi项目是专门用来实现Restful ...

随机推荐

  1. iOS 之消息推送(个推)---个人小结

    前言:自从上个星期开始整这个推送,弄了差不多一个星期,今天终于给整好了,因此现在来记录这段"奇妙"的旅程. 我们公司使用的消息推送是用的第三方--个推,这里不得不说一下,个推的技术 ...

  2. Electron安装

    1.安装nodejs和npm 官网下载地址:https://nodejs.org/en/download/ 安装包:下载.msi 安装完成后: nodejs.npm都会安装好,path环境变量也自动设 ...

  3. 计算机程序的思维逻辑 (46) - 剖析PriorityQueue

    上节介绍了堆的基本概念和算法,本节我们来探讨堆在Java中的具体实现类 - PriorityQueue. 我们先从基本概念谈起,然后介绍其用法,接着分析实现代码,最后总结分析其特点. 基本概念 顾名思 ...

  4. Linux驱动学习 —— 在/sys下面创建目录示例

    有时我们需要在/sys下面创建一些目录, 下面给出了一个示例. 在加载驱动模块后, 在/sys下面会创建一个名为sysfs_demo的目录,并在其中在创建几个文件和目录. [root@tiny4412 ...

  5. SQL Tuning 基础概述02 - Explain plan的使用

    1.explain plan的使用 SQL> explain plan for delete from t_jingyu; Explained. SQL> select * from ta ...

  6. LinqToDB 源码分析——轻谈Linq查询

    LinqToDB框架最大的优势应该是实现了对Linq的支持.如果少了这一个功能相信他在使用上的快感会少了一个层次.本来笔者想要直接讲解LinqToDB框架是如何实现对Linq的支持.写到一半的时候却发 ...

  7. internet协议入门

    前言 劳于读书,逸于作文. 原文地址:internet协议入门 博主博客地址:Damonare的个人博客 博主之前写过一篇博客:网络协议分析,在这篇博客里通过抓包,具体的分析了不同网络协议的传送的数据 ...

  8. PreEmptive Dotfuscator and Analytics CE

    PreEmptive Dotfuscator and Analytics CE Dotfuscator 是领先的 .NET 模糊处理程序和压缩程序,有助于防止程序遭到反向工程,同时使程序更小更高效.D ...

  9. .NET Core 1.1 发布 文档下载资源汇总

    .NET Core 1.1 RTM 版2016/11/16 发布.对应发布 ASP.NET Core 1.1 .EF Core 1.1. 你可以通过Visual Studio 2015, Visual ...

  10. 【无私分享:ASP.NET CORE 项目实战(第四章)】Code First 创建数据库和数据表

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 本章我们来介绍下Asp.net Core 使用 CodeFirst 创建数据库和表,通过 控制台 和 dotnet ef 两种 ...