Flashkを使ってRESTfulを設計する認証

19329 ワード

Flashkを使ってRESTfulの認証を設計します.
今日は簡単な展示をしますが、Flashkを使って作成されたAPIを保護するために安全な方法で使用されます.パスワードやトークンを使って認証されたものです.
サンプルコード
本明細書で使用するコードは、github上で見つけることができる.REST-auth.
ユーザー・データベース
本明細書では、サンプルを実際のプロジェクトのように見せるために、Flask-SQLAlchemyを使用してユーザデータベースモデルを構築し、データベースに格納する.
ユーザーのデータベースモデルは非常に簡単です.ユーザーごとに、usernameとpassword_shは保存されます.
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)) 
セキュリティのため、ユーザの元のパスワードは保存されません.パスワードは登録時にハッシュされてデータベースに保存されます.ハッシュパスワードを使うと、ユーザーデータベースが誤って悪意の攻撃者の手に入ると、ハッシュから真実のパスワードを解析することも難しいです.
パスワードは決してユーザデータベースに明確に保存できません.
パスワードのハッシュ
パスワードハッシュを作成するために、私はPassLibライブラリを使います.パスワードハッシュ用のPythonパッケージです.
PassLibは選択のための様々なハッシュアルゴリズムを提供する.customap_contextはshar 256_に基づいて使いやすいです.cryptのハッシュアルゴリズム.
Userユーザモデルは、パスワードハッシュとパスワード検証機能を追加するために2つの新しい方法を追加する必要があります.
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)
hashpassword()関数は、明文のパスワードをパラメータとして受け取り、明文パスワードのハッシュを記憶する.新しいユーザがサーバに登録されたり、パスワードが変更されたりすると、この関数が呼び出されます.
verify_パスワード関数は、明文のパスワードをパラメータとして受け取り、パスワードが正しい場合はTrueまたはパスワードが間違っている場合はFalseに戻ります.この関数は、ユーザーが証明書を提供し、検証する必要があるときに呼び出します.
元のパスワードがハッシュされたらどうやって元のパスワードを検証できますか?
ハッシュアルゴリズムは、一方向関数であり、これは、パスワードに従ってハッシュを生成することができるということであるが、生成されたハッシュから元のパスワードを逆算することはできない.しかし、これらのアルゴリズムは決定的であり、同じ入力を与えると、それらは常に同じ出力を得る.PassLibは、登録時に同じ関数ハッシュパスワードを使用して、データベースに格納されているハッシュ値と比較することによって、パスワードを検証する必要があります.
ユーザー登録
本明細書の例では、1つのクライアントは、POST要求を使用して、/appi/usersに新しいユーザを登録することができる.要求の主体は、usernameとpasswordを含むJSON形式のオブジェクトでなければならない.
Flashkにおけるルーティングの実現は以下の通りである.
@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)} 
この関数はとても簡単です.パラメータusernameとpasswordは、要求によって運ばれたJSONデータから取得し、それらを検証する.
パラメータが検証されると、新しいUserインスタンスが作成される.usernameはUserに与えられ、引き続きhash_を使います.passwordメソッドのハッシュパスワード.ユーザは最終的にデータベースに書き込みます.
応答の主体は、ユーザを表すJSONオブジェクトであり、201の状態コード及び新たに作成されたユーザを指すURIのHTTPヘッダ情報:Location.
注意:get_user関数はgithub上に完全なコードを見つけることができます.
ここではユーザ登録の要求です.Curlから送信します.
$ curl -i -X POST -H "Content-Type: application/json" -d '{"username":"miguel","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": "miguel"
}
実際のアプリケーションでは、ここでは安全なHTTP(例えばHTTPS)が使用される可能性があるので注意が必要です.ユーザ登録の証憑が明文を通じてネットワークで送信される場合、APIの保護措置は全く意味がない.
パスワードによる認証.
今、私たちは一つのリソースがAPIを通じて登録しなければならないユーザーに暴露されると仮定します.このリソースはURLを通じてアクセスできます.
このリソースを保護するために、HTTP基本ID認証を使用しますが、自分で完全なコードを作成して実現するのではなく、Flaask-HTTPAuth拡張をしてくれます.
Flaask-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 }) 
しかし、Flaask-HTTPAuthは、ユーザの認証を検証するためにより多くの情報を与える必要があります.もちろん、Flashk-HTTPAuthには多くのオプションがあります.これはアプリケーションが実現するセキュリティレベルに依存します.
最大自由度の選択を提供することができます(これも唯一の互換性のあるPassLibハッシュかもしれません)は、verify_を選択します.このコールバック関数は、提供されたusernameとpasswordの組み合わせに基づいて、True(検証によって)またはFlashe(検証されていません)を返します.Flaask-HTTPAuthは、usernameとpasswordペアを検証する必要があるときに、このコールバック関数を呼び出します.
verify_passwordコールバック関数の実現は以下の通りである.
@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 
この関数はusernameによってユーザーを見つけます.そしてverify_を使います.パスワード()方法でパスワードを検証します.認証が通れば、ユーザオブジェクトはFlashkのgオブジェクトに格納され、このようにビューが使用される.
ここでは、curl要求でユーザが取得した保護リソースのみを登録することができる.
$ curl -u miguel: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, miguel!"
}
ログインに失敗したら、以下の内容が得られます.
$ 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
ここで改めて確認します.実際のアプリケーションでは、安全なHTTPを使ってください.
トークンによる認証/
要求毎にusernameとpasswordを送らなければならないのは非常に不便です.安全なHTTP伝送を通じてもリスクがあります.クライアントは暗号化されていない認証証明書を記憶しなければならないので、毎回要求に応じて送信することができます.
前の解決策に基づく最適化は、トークンを用いて要求を検証することである.
私たちの考えは、クライアント・アプリケーションが認証証明書を使って認証トークンを交換し、次の要求は認証トークンのみを送信することである.
トークンは有効時間を持ち、有効時間が過ぎると、トークンは無効となり、新たなトークンを再取得する必要がある.トークンの潜在的なリスクはトークンを生成するアルゴリズムが弱いことにあるが、有効期間が短いとリスクを減らすことができる.
トークンを強化する多くの方法がある.簡単な強化方法としては、データベースに保存されているユーザとパスワードに基づいてランダムな長さの文字列を生成し、有効期限が切れる可能性があります.トークンは平文パスワードの並べ替えとなり、文字列の比較が容易になり、有効期限を確認することができる.
より完全な実装は、トークンとして暗号化された署名を使用するサーバ端末の記憶動作を必要としないことである.このような方法には多くの利点があり、ユーザー情報に基づいて署名を生成することができ、改竄が難しい.
Flashkはcookiesを同様な方法で処理します.この実現はits dangerusというライブラリに依存しています.ここでも採用します.
トークンの生成および検証はUserモデルに追加され、具体的には以下のように実装される.
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 
ゲナート_auth_token()方法は、ユーザID値を値とし、「id」をキーワードとした辞書の暗号化トークンを生成する.トークンには、デフォルトでは10分(600秒)の有効期限が同時に追加されます.
検証トークンはverify_にあります.auth_token()静的方法で実現した.静的方法がここで使用されるのは、トークンが復号されるとユーザが知ることができるからである.トークンが復号された場合、対応するユーザはクエリされ、リターンされる.
APIはトークンを取得する新しい関数を必要とし、このようにクライアントはトークンに出願することができる.
@app.route('/api/token')
@auth.login_required
def get_auth_token(): token = g.user.generate_auth_token() return jsonify({ 'token': token.decode('ascii') }) 
注意:この関数はauth.login_を使用しています.required装飾器は、つまりusernameとpasswordを提供する必要があります.
残りは、クライアントがどのように要求にこのトークンを含めるかを決定することである.
HTTP基本認証方式は、特にusernamesとpasswordsを認証用として要求しません.HTTPヘッダの中のこの2つのフィールドは、任意のタイプの認証情報に使用することができます.トークンの認証に基づいて、トークンはusernameフィールドとして使用でき、passwordフィールドは無視できる.
これは、サーバが、認証としてusernameとpasswordを同時に処理する必要があり、また、トークンがusernameとしての認証方式を必要とすることを意味する.verify_passwordコールバック関数は、この2つの方法を同時にサポートする必要があります.
@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コールバック関数は2回の認証を試みます.まず、USernameパラメータをトークンとして認証します.検証が通っていないと、パスワード認証に基づくように、usernameとpasswordを検証します.
以下のcurl要求は、認証トークンを取得することができる.
$ curl -u miguel: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"
}
トークンを使用してリソースを取得することができます.
$ 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, miguel!"
}
ここではパスワードが使われていません.
OAuth認証/
RESTful認証について議論すると、OAuthプロトコルはしばしば言及される.
OAuthとは何ですか?
OAuthは多くの意味を持つことができる.最も一般的なのは、他のアプリケーションのユーザのアクセスまたはサービスの利用を許可するアプリケーションですが、ユーザはアプリケーションで提供される登録証明書を使用しなければなりません.読書者はOAuthを見て、より多くの知識を知ることができると提案します.