RESTスタイルのアカウント管理

ご注意ください

このチュートリアルはあなたが読んだと仮定します 迅速に起動する そして 認証と権限 ガイドさん。

比較的まれなオープン(通常は読み取り専用)公共APIを除いて、ほとんどのサービスは、認証されたユーザによってのみアクセス可能である。一般的なパターンの1つは、ユーザがウェブサイト上でまたはモバイルアプリケーションを使用して彼らのアカウントを作成することである。彼らがアカウントを持つと、彼らは1つ以上のAPIを消費することができる。これは多くのソーシャルネットワークやサービスプロバイダ(Twitter、Facebook、Netflixなど)が準拠しているモデルです。では、サービスプロバイダとして、アカウント自体が使用するAPIと同じAPIを使用しながら、アカウントをどのように作成、編集、削除しますか?

次の段落では、いくつかの可能なアカウント管理実装が見られ、それらは、例えば、多くのイヴ機能を大量に使用している。 エンドポイントセキュリティをカスタマイズする. そして、 役割に基づくアクセス制御 そして、 ユーザ制限されたリソースアクセス そして、 事件とリンクする. それがそうです。

SSL/TLSが有効になっていると仮定すると、これはトランスポート層が暗号化されていることを意味します 基本的な身分検証 そして トークンによる認証 APIエンドポイントを保護するための有効なオプション。

もし私たちがアップグレードしているとしましょう 迅速に起動する チュートリアル。

基本認証付き口座

私たちの課題は

  1. すべてのアカウント管理アクティビティにエンドポイントを使用できるようにする (/accounts )。

  2. 端末のセキュリティを保護し,我々が制御するクライアントのみがアクセスできるようにする:我々自身のサイト,アカウント管理機能を持つモバイルアプリケーションなどである.

  3. 他のすべてのAPI終端ノードは、認証されたアカウント(上記の終端ノードによって作成された)にのみアクセス可能であることを保証します。

  4. 認証されたユーザは、彼ら自身が作成したリソースにしかアクセスできないことを可能にする。

1. /accounts 端点.

アカウント管理エンドポイントは、他のAPIエンドポイントとあまり変わりません。これはただ私たちの設定文書でそれを宣言する問題に過ぎない。まずインフラを宣言しましょう

schema =  {
    'username': {
        'type': 'string',
        'required': True,
        'unique': True,
        },
    'password': {
        'type': 'string',
        'required': True,
    },
},

そして,サイトを定義しましょう.

accounts = {
    # the standard account entry point is defined as
    # '/accounts/<ObjectId>'. We define  an additional read-only entry
    # point accessible at '/accounts/<username>'.
    'additional_lookup': {
        'url': 'regex("[\w]+")',
        'field': 'username',
    },

    # We also disable endpoint caching as we don't want client apps to
    # cache account data.
    'cache_control': '',
    'cache_expires': 0,

    # Finally, let's add the schema definition for this endpoint.
    'schema': schema,
}

私たちは以下の位置で追加の読み取り専用入口点を定義しました /accounts/<username> それがそうです。これは実際には必要ではないが、ユーザ名が使用されているか否かを容易に検証したり、ユーザ名を知らずにアカウントを検索したりすることができる。 ObjectId 事前に。もちろん,資源サイトを問い合わせることでこの2つの情報を見つけることもできる. (/accounts?where={{"username": "johndoe"}} )であるが、その後、応答ペイロードを解析する必要があり、GET要求を使用して新しいエンドポイントに命中することによって、裸アカウントデータ、すなわち 404 Not Found もしその口座が存在しなければ。

エンドポイントを構成すると、APIドメインに追加する必要があります。

DOMAIN['accounts'] = accounts

2.保護 /accounts/ 端点.

2 A.私たちが入る方法をハードコードしています

端点を保護する方法はよく知られていることしか許されていません superusers 手術をしています我々の認証クラス(起動スクリプトで定義される)は,この場合をハードコードで扱うことができる.

import bcrypt
from eve import Eve
from eve.auth import BasicAuth


class BCryptAuth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource, method):
        if resource == 'accounts':
            return username == 'superuser' and password == 'password'
        else:
            # use Eve's own db driver; no additional connections/resources are used
            accounts = app.data.driver.db['accounts']
            account = accounts.find_one({'username': username})
            return account and \
                bcrypt.hashpw(password, account['password']) == account['password']


if __name__ == '__main__':
    app = Eve(auth=BCryptAuth)
    app.run()

そのためには superuser アカウントは使用が許可されます accounts 一方、標準認証ロジックは、他のすべてのエンドポイントに適用される。私たちのモバイルアプリケーション(例えば)は、簡単なPOST要求でサイトをクリックしてアカウントを追加しますが、もちろん自分を認証します。 superuser 使用することで Authorization ヘディングです。このスクリプトは,格納されたパスワードの使用を想定している bcrypt (平文形式でパスワードを格納することは) 絶対にしない これはいい考えですね)。見 基本的な身分検証 別の速度が速いが、セキュリティが悪いSHA 1/MACの例を表示します。

2 B.ユーザキャラクタアクセス制御

ハードコード化ユーザー名とパスワードはこの仕事をうまく終えるかもしれませんが、これは私たちがここで取ることができる最善の方法ではありません。もし別の人が superurser アカウントは終点にアクセスする必要がありますか?特権ユーザが行列に参加するたびにスクリプトを更新することは適切ではないようである(事実はそうではない).幸いなことに 役割に基づくアクセス制御 機能はここで私たちを助けることができる。私たちが何をしようとしているのか知っています私たちの考えは superuser そして admin 役割は終端ノードへのアクセス権限が付与される.

インフラを更新することから始めましょう

     schema =  {
         'username': {
             'type': 'string',
             'required': True,
             },
         'password': {
             'type': 'string',
             'required': True,
         },
         'roles': {
             'type': 'list',
             'allowed': ['user', 'superuser', 'admin'],
             'required': True,
         }
     },

私たちはさっき新しいのを追加しました roles フィールド、これは必須リストです。これから、アカウントを作成する際に1つ以上の役割を割り当てなければなりません。

エンドポイントのアクセスを制限する必要があります superuser そして admin アカウントのみですので、それに応じてエンドポイント定義を更新しましょう。

 accounts = {
     # the standard account entry point is defined as
     # '/accounts/<ObjectId>'. We define  an additional read-only entry
     # point accessible at '/accounts/<username>'.
     'additional_lookup': {
         'url': 'regex("[\w]+")',
         'field': 'username',
     },

     # We also disable endpoint caching as we don't want client apps to
     # cache account data.
     'cache_control': '',
     'cache_expires': 0,

     # Only allow superusers and admins.
     'allowed_roles': ['superuser', 'admin'],

     # Finally, let's add the schema definition for this endpoint.
     'schema': schema,
 }

最後に、私たちの認証クラスを書き換える必要があります。

from eve import Eve
from eve.auth import BasicAuth
from werkzeug.security import check_password_hash


class RolesAuth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource, method):
        # use Eve's own db driver; no additional connections/resources are used
        accounts = app.data.driver.db['accounts']
        lookup = {'username': username}
        if allowed_roles:
            # only retrieve a user if his roles match ``allowed_roles``
            lookup['roles'] = {'$in': allowed_roles}
        account = accounts.find_one(lookup)
        return account and check_password_hash(account['password'], password)


if __name__ == '__main__':
    app = Eve(auth=RolesAuth)
    app.run()

上記のコードセグメントの役割は,ロールベースのアクセス制御を用いてすべてのAPIエンドポイントを保護することである.実際それは 役割に基づくアクセス制御 それがそうです。この技術はより多くのコードを追加する時にコードをそのまま維持することができます superuser あるいは…。 admin アカウント(私たちは私たち自身のAPIにアクセスすることによってそれらを追加するかもしれません)。また,必要であれば,設定ファイルを更新するだけで,より多くのサイトへのアクセスを容易に制限することができ,認証クラスに触れる必要もない.

3.他のAPIエンドポイントのセキュリティを保護する

これは速いでしょうなぜなら2つは hard-coding そして role-based 上記のアクセス制御方法は、すべてのAPIエンドポイントを効率的に保護している。認証クラスを Eve オブジェクトはAPI全体の認証を有効にする:エンドポイントが要求されるたびに,クラスインスタンスを呼び出す.

もちろん、セキュリティを微調整することができ、例えば、いくつかのエンドポイントまたはいくつかのHTTP方法への共通アクセスを可能にすることができる。見 認証と権限 もっと細かいことを知っています。

4.口座資源へのアクセスのみを許可する

ほとんどの場合、認証されたユーザがデータを格納することを許可する場合、あなたは自分のデータにアクセスすることだけを望みます。これは使用することで ユーザ制限されたリソースアクセス クローズアップする。有効化後、各格納された文書は、そのアカウントを作成することに関連付けられます。これにより、APIは、アカウント作成された文書をすべてのタイプの要求(読み取り、編集、削除、もちろん作成)のみに透過的に提供することができる。

この機能を活性化するには2つのことが必要です

  1. 構成は、文書所有者のフィールドの名前を格納する。

  2. POST要求に入力された各文書所有者を設定する.

すべてのAPIエンドポイントのためにこの機能を有効にしたいため,更新するだけでよい. settings.py 適切なものを設定することで AUTH_FIELD 価値:

# Name of the field used to store the owner of each document
AUTH_FIELD = 'user_id'

次に、このフィールドの値を正確に更新するために認証クラスを更新したい。

 from eve import Eve
 from eve.auth import BasicAuth
 from werkzeug.security import check_password_hash


 class RolesAuth(BasicAuth):
     def check_auth(self, username, password, allowed_roles, resource, method):
         # use Eve's own db driver; no additional connections/resources are used
         accounts = app.data.driver.db['accounts']
         lookup = {'username': username}
         if allowed_roles:
             # only retrieve a user if his roles match ``allowed_roles``
             lookup['roles'] = {'$in': allowed_roles}
         account = accounts.find_one(lookup)
         # set 'AUTH_FIELD' value to the account's ObjectId
         # (instead of _Id, you might want to use ID_FIELD)
         self.set_request_auth_value(account['_id'])
         return account and check_password_hash(account['password'], password)


 if __name__ == '__main__':
     app = Eve(auth=RolesAuth)
     app.run()

これが私たちがしなければならないすべてのことだ。今、お客様がクリックすると、言ってください /invoices GET要求のエンドポイントを有する場合、それは、自身のアカウントによって作成されたインボイスのみを使用する。削除や修復も同様の場合があり,認証されたユーザが意外に他者のデータを検索,編集,削除することは不可能である.

トークン身分証明書を使用したアカウント

ご覧のとおり トークンによる認証 なお,トークン認証は基本認証の専用バージョンのみである.これは実際に標準的な基本認証要求として実行されている. ユーザー名 フィールドはトークンのために使用され、暗号フィールドは提供されない(含まれる場合、フィールドは無視される)。

したがって,トークンを用いた認証処理アカウントは我々が見たものと非常に類似している. 基本認証付き口座 しかし、トークンは、アカウントと共に生成および記憶し、最終的にクライアントに戻る必要があるという小さな警告がある。

そこで最新のタスクリストを振り返ってみましょう

  1. すべてのアカウント管理アクティビティにエンドポイントを使用できるようにする (/accounts )。

  2. エンドポイントは,我々が制御するクライアント(トークン)のみにアクセスできるように保護する.

  3. アカウントを作成する際には、そのトークンを生成して記憶する。

  4. 代替的に、新しいトークンは、応答と共に返される。

  5. 他のすべてのAPI終端ノードは,認証されたトークンのみにアクセス可能であることを保証する.

  6. 認証されたユーザが自分で作成したリソースのみにアクセスすることを可能にします

1. /accounts/ 端点.

これは私たちと 基本認証付き口座 それがそうです。追加するだけです token フィールドは、私たちのアーキテクチャに追加されます:

     schema =  {
         'username': {
             'type': 'string',
             'required': True,
             'unique': True,
             },
         'password': {
             'type': 'string',
             'required': True,
         },
         'roles': {
             'type': 'list',
             'allowed': ['user', 'superuser', 'admin'],
             'required': True,
         },
         'token': {
             'type': 'string',
             'required': True,
         }
     }

2.保護 /accounts/ 端点.

私たちは roles 属性のフィールド accounts 前の段階のパターンですまた,サイトを定義し,許可されたユーザキャラクタを設定していることを確保する必要がある.

 accounts = {
     # the standard account entry point is defined as
     # '/accounts/<ObjectId>'. We define  an additional read-only entry
     # point accessible at '/accounts/<username>'.
     'additional_lookup': {
         'url': 'regex("[\w]+")',
         'field': 'username',
     },

     # We also disable endpoint caching as we don't want client apps to
     # cache account data.
     'cache_control': '',
     'cache_expires': 0,

     # Only allow superusers and admins.
     'allowed_roles': ['superuser', 'admin'],

     # Finally, let's add the schema definition for this endpoint.
     'schema': schema,
 }

最後にこれは私たちの起動スクリプトですもちろん使っています TokenAuth 今回はサブクラスです

from eve import Eve
from eve.auth import TokenAuth


class RolesAuth(TokenAuth):
    def check_auth(self, token,  allowed_roles, resource, method):
        # use Eve's own db driver; no additional connections/resources are used
        accounts = app.data.driver.db['accounts']
        lookup = {'token': token}
        if allowed_roles:
            # only retrieve a user if his roles match ``allowed_roles``
            lookup['roles'] = {'$in': allowed_roles}
        account = accounts.find_one(lookup)
        return account


if __name__ == '__main__':
    app = Eve(auth=RolesAuth)
    app.run()

3.アカウント作成時のカスタムトークンの構築

上のコードには、まだトークンが生成されていないので、誰にも認証を行わないという問題があります。したがって,クライアントはその認証トークンを取り出すことができないため,彼らは実際にどのように認証を行うかを知らない.畏敬の念を抱くような 事件とリンクする. クローズアップする。コールバック関数を登録することで起動スクリプトを更新し、新しいアカウントがデータベースに格納される直前にコールバック関数を呼び出します。

 from eve import Eve
 from eve.auth import TokenAuth
 import random
 import string


 class RolesAuth(TokenAuth):
     def check_auth(self, token,  allowed_roles, resource, method):
         # use Eve's own db driver; no additional connections/resources are used
         accounts = app.data.driver.db['accounts']
         lookup = {'token': token}
         if allowed_roles:
             # only retrieve a user if his roles match ``allowed_roles``
             lookup['roles'] = {'$in': allowed_roles}
         account = accounts.find_one(lookup)
         return account


 def add_token(documents):
     # Don't use this in production:
     # You should at least make sure that the token is unique.
     for document in documents:
         document["token"] = (''.join(random.choice(string.ascii_uppercase)
                                      for x in range(10)))


 if __name__ == '__main__':
     app = Eve(auth=RolesAuth)
     app.on_insert_accounts += add_token
     app.run()

ご覧のように、定期購読しています on_insert の事件です。 accounts endpoint with our add_token function. This callback will receive documents as an argument, which is a list of validated documents accepted for database insertion. We simply add (or replace in the unlikely case that the request contained it already) a token to every document, and we're done! For more information on callbacks, see Event Hooks それがそうです。

4.トークンとレスポンスを返す

代替的に、あなたは、応答と共にトークンを返すことを望むことができる。実を言うと、これはいい考えではありません。あなたは通常、電子メールのようなアクセス情報を帯外で送信することを望んでいます。しかしながら、我々はSSLを使用しており、クライアントがモバイルアプリケーションであり、ユーザが直ちにサービスを使用したい場合など、認証トークンを送信することは意味があると仮定する。

通常、自動処理のフィールドのみ (ID_FIELD そして、 LAST_UPDATED そして、 DATE_CREATED そして、 ETAG )は、応答後ペイロードに含まれる。幸いにも応答に追加のフィールドを注入することができます EXTRA_RESPONSE_FIELDS 終了点レベルの等価物よりも extra_response_fields それは.私たちがすべきことはサイト定義を更新することです

 accounts = {
     # the standard account entry point is defined as
     # '/accounts/<ObjectId>'. We define  an additional read-only entry
     # point accessible at '/accounts/<username>'.
     'additional_lookup': {
         'url': 'regex("[\w]+")',
         'field': 'username',
     },

     # We also disable endpoint caching as we don't want client apps to
     # cache account data.
     'cache_control': '',
     'cache_expires': 0,

     # Only allow superusers and admins.
     'allowed_roles': ['superuser', 'admin'],

     # Allow 'token' to be returned with POST responses
     'extra_response_fields': ['token'],

     # Finally, let's add the schema definition for this endpoint.
     'schema': schema,
 }

今からは /accounts エンドポイントは、クライアントが他のAPIエンドポイントを直ちに使用することを可能にするために、新たに生成された認証トークンを含む。

5.他のAPIエンドポイントの保護

私たちが以前見たように認証クラスを Eve オブジェクトはすべてのAPI終端ノードで認証を有効にする.同様に、いくつかのエンドポイントまたはいくつかのHTTP方法への共通アクセスを可能にすることによって、セキュリティを微調整することができる。見 認証と権限 もっと細かいことを知っています。

6.口座資源へのアクセスのみを許可する

これは以下のように実現される ユーザ制限されたリソースアクセス 機能は,中に示すように 基本認証付き口座 それがそうです。ユーザーのトークンを AUTH_FIELD 値は、しかし、ユーザトークンが容易にキャンセルされやすいことを望む場合、アカウント固有IDを使用することが最も良い選択です。

基本VSトークン:最終的な考慮事項

サーバ側でトークン認証を設定することはやや困難であるが、トークン認証は顕著な利点を提供する。まず、あなたのパスワードはクライアントに格納されておらず、要求のたびにネットワークを介して送信されます。帯域外でトークンを送信し、SSL/TLSを使用している場合、かなりの追加のセキュリティが必要です。