一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

使用Flask設計帶認證token的RESTful API接口[翻譯]

 quasiceo 2014-12-25

使用Flask設計帶認證token的RESTful API接口[翻譯]

上一篇文章, 使用python的Flask實現(xiàn)一個RESTful API服務器端  簡單地演示了Flask實的現(xiàn)的api服務器,里面提到了因為無狀態(tài)的原則,沒有session cookies,如果訪問需要驗證的接口,客戶端請求必需每次都發(fā)送用戶名和密碼。通常在實際app應用中,并不會每次都將用戶名和密碼發(fā)送。

這篇里面就談到了產(chǎn)生token的方法。

完整的例子的代碼

可以在github:REST-auth 上找到。作者歡迎大家上去跟他討論。

創(chuàng)建用戶數(shù)據(jù)庫

這個例子比較接近真實的項目,將會使用Flask-SQLAlchemy (ORM)的模塊去管理用戶數(shù)據(jù)庫。

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))

因為安全的原因,明文密碼不可以直接存儲,必需經(jīng)過hash后方可存入數(shù)據(jù)庫。如果數(shù)據(jù)庫被脫了,也是比較難破解的。

密碼永遠不要明文存在數(shù)據(jù)庫中。

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)
復制代碼

當一個新的用戶注冊,或者更改密碼時,就會調(diào)用hash_password()函數(shù),將原始密碼作為參數(shù)傳入hash_password()函數(shù)。

 當驗證用戶密碼時就會調(diào)用verify_password()函數(shù),如果密碼正確,就返回True,如果不正確就返回False

hash算法是單向的,意味著它只能hash密碼,但是無法還原密碼。但是這些算法是絕對可靠的,輸入相同的內(nèi)容,那么hash后的內(nèi)容也會是一樣的。通常注冊或者驗證時,對比的是hash后的結(jié)果。

用戶注冊

在這個例子里,客戶端通過發(fā)送 POST 請求到 /api/users 上,并且請求的body部份必需是JSON格式,并且包含 usernamepassword 字段。

Flask 實現(xiàn)的代碼:

復制代碼
@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)}
復制代碼

這個函數(shù)真是簡單極了。只是用請求的JSON里面拿到 usernamepassword 兩個參數(shù)。

如果參數(shù)驗證通過,一個User實例被創(chuàng)建,密碼hash后,用戶資料就存到數(shù)據(jù)庫里面了。

 請求響應返回的是一個JSON格式的對象,狀態(tài)碼為201,并且在http header里面定義了Location指向剛剛創(chuàng)建的用戶的URI。

注意:get_user函數(shù)沒有在這里實現(xiàn),具體查以查看github。

試試使用curl發(fā)送一個注冊請求:

復制代碼
$ 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 201 CREATED
Content-Type: application/json
Content-Length: 27
Location: http://127.0.0.1:5000/api/users/1
Server: Werkzeug/0.9.4 Python/2.7.3
Date: Thu, 28 Nov 2013 19:56:39 GMT

{
  "username": "ok"
}
復制代碼

通常在正式的服務器里面,最好還是使用https通訊。這樣的登錄方式,明文通訊是很容易被截取的。

基于簡單密碼的認證

現(xiàn)在我們假設有一個API只向已經(jīng)注冊好的用戶開放。接入點是/api/resource

這里使用HTTP BASIC Authentication的方法來進行驗證,我計劃使用Flask-HTTPAuth這個擴展來實現(xiàn)這個功能。

導入Flask-HTTPAuth擴展模塊后,為對應的函數(shù)添加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裝飾器)需要知道如何驗證用戶信息,這就需要具體去實現(xiàn)安全驗證的方法了。

有一種辦法是十分靈活的,通過實現(xiàn)verify_password回調(diào)函數(shù)去驗證用戶名和密碼,驗證通過返回True,否則返回False。然后Flask-HTTPAuth再調(diào)用這個回調(diào)函數(shù),這樣就可以輕松自定義驗證方法了。(注:Python修飾器的函數(shù)式編程

具體實現(xiàn)代碼如下:

復制代碼
@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 存儲在應用上下文中而不再是請求上下文中,這意味著即使在應用上下文中它也是可訪問的而不是只能在請求上下文中。)方便其它函數(shù)使用。

讓我們使用已經(jīng)注冊的用戶來請求看看:

復制代碼
$ curl -u ok:python -i -X GET http://127.0.0.1:5000/api/resource
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 30
Server: Werkzeug/0.9.4 Python/2.7.3
Date: Thu, 28 Nov 2013 20:02:25 GMT

{
  "data": "Hello, ok!"
}
復制代碼

如果登錄錯誤,會返回以下內(nèi)容:

復制代碼
$ curl -u miguel:ruby -i -X GET http://127.0.0.1:5000/api/resource
HTTP/1.0 401 UNAUTHORIZED
Content-Type: text/html; charset=utf-8
Content-Length: 19
WWW-Authenticate: Basic realm="Authentication Required"
Server: Werkzeug/0.9.4 Python/2.7.3
Date: Thu, 28 Nov 2013 20:03:18 GMT

Unauthorized Access
復制代碼

再次重申,真實的API服務器最好在HTTPS下通訊。

基于Token的認證

因為需要每次請求都要發(fā)送用戶名和密碼,客戶端需要把驗證信息存儲起來進行發(fā)送,這樣十分不方便,就算在HTTPS下的傳輸,也是有風險存在的。

比前面的密碼驗證方法更好的是使用Token認證請求。

原理是第一次客戶端與服務器交換過認證信息后得到一個認證token,后面的請求就使用這個token進行請求。

Token通常會給一個過期的時間,當超過這個時間后,就會變成無效,需要產(chǎn)生一個新的token。這樣就算token泄漏了,危害也只是在有效的時間內(nèi)。

好多種辦法去實現(xiàn)token。一種簡單的做法就是產(chǎn)生一個固定長度的隨機序列字符與用戶名和密碼一同存儲在數(shù)據(jù)庫當中,有可能帶上一個過期時間。這樣token就變成了一串普通的字符,可以十分容易地和其它字符串驗證對比,并且可以檢查時間是否過期。

更復雜的實現(xiàn)辦法是不需要服務器端進行存儲token,而是使用數(shù)字簽名信息作為token。這樣做的好處是經(jīng)過用戶數(shù)字簽名生成的token是可以防篡改的。

Flask使用與數(shù)字簽名有些相似的辦法去實現(xiàn)加密的cookies的,這里我們使用itsdangerous的庫去實現(xiàn)。

生成token和驗證token的方法可以附加到User model上實現(xiàn):

復制代碼
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()函數(shù)中,token其實就是一個加密過的字典,里面包含了用戶的id和默認為10分鐘(600秒)的過期時間。

verify_auth_token()的實現(xiàn)是一個靜態(tài)方法,因為token只是一次解碼檢索里面的用戶id。獲取用戶id后就可以在數(shù)據(jù)庫中取得用戶資料了。

試試使用一個新的接入點,讓客戶端請求一個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協(xié)議沒有具體要求必需使用用戶名和密碼進行驗證,HTTP頭可以使用兩個字段去傳輸認證信息,對于token認證,只需要把token當成用戶名發(fā)送即可,密碼字段可以乎略。

綜上所說,一些認證還是要使用用戶名和密碼認證,另外一部份直接使用獲取的token認證。verify_password回調(diào)函數(shù)則需要包括兩種驗證的方式:

復制代碼
@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回調(diào)函數(shù),添加兩種驗證。開始用用戶名字段當作token,如果不是token來的,就采用用戶名和密碼驗證。

使用curl測試請求獲取一個認證token:

復制代碼
$ curl -u ok:python -i -X GET http://127.0.0.1:5000/api/token
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 139
Server: Werkzeug/0.9.4 Python/2.7.3
Date: Thu, 28 Nov 2013 20:04:15 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 200 OK
Content-Type: application/json
Content-Length: 30
Server: Werkzeug/0.9.4 Python/2.7.3
Date: Thu, 28 Nov 2013 20:05:08 GMT

{
  "data": "Hello, ok!"
}
復制代碼

注意,請求里面帶了unused字段。只是為了標識而已,替代密碼的占位符。

OAuth 認證

談到RESTful認證,通常會提到OAuth協(xié)議。

So what is OAuth?

通常是允許一個應用接入到另外一個應用的數(shù)據(jù)或者服務的驗證方法。

舉個例子,如果一個網(wǎng)站或者應用問你權(quán)限接入你的facebook賬號,并且提交一些東西到你的時間軸上面。這個例子,你就是資源擁有者(你擁有你的facebook時間軸),第三方應用是消費者,facebook是提供者。如果你授權(quán)接入允許消費者寫東西到你的時間軸上面,是不需要提供你的facebook登錄信息的。

OAuth并不合適用在client/server的RESTful API上面,一般是用在你的RESTful API允許第三方應用(消費者)去接入。

上面的例子是,客戶端/服務器端之間直接通訊并不需要去隱藏認證信息,客戶端是直接發(fā)送認證請求信息到服務器端的。

原文來自:http://blog./post/restful-authentication-with-flask

posted on 2014-12-25 17:03 Vovolie 閱讀(10) 評論(0) 編輯 收藏


    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    日韩三级黄色大片免费观看| 亚洲视频一级二级三级| 欧美一区二区三区在线播放| 国产丝袜美女诱惑一区二区| 黑丝国产精品一区二区| 日韩免费成人福利在线| 欧美日韩国产免费看黄片| 中文字幕av诱惑一区二区| 欧美日韩一区二区三区色拉拉| 91福利免费一区二区三区| 国产精品一区二区传媒蜜臀| 男人大臿蕉香蕉大视频| 欧美尤物在线视频91| 在线免费国产一区二区| 最新午夜福利视频偷拍| 国产一区二区三区不卡| 日本久久精品在线观看| 少妇人妻一级片一区二区三区| 日韩在线免费看中文字幕| 国产又粗又深又猛又爽又黄| 国产又粗又爽又猛又黄的| 91精品国产综合久久福利| 一区二区三区亚洲天堂| 开心激情网 激情五月天| 日韩人妻免费视频一专区| 欧美成人国产精品高清| 国产伦精品一区二区三区高清版 | 国产又粗又猛又爽色噜噜| 九九热视频网在线观看| 中文字幕一区二区三区中文| 一区二区三区在线不卡免费| 国内真实露脸偷拍视频| 欧美一级内射一色桃子| 精品日韩国产高清毛片| 夜色福利久久精品福利| 高清免费在线不卡视频| 欧美日韩成人在线一区| 麻豆看片麻豆免费视频| 亚洲淫片一区二区三区| 中文字幕一区二区免费| 日本妇女高清一区二区三区|