REST API承認戦略


8

ここでは、RESTful APIの認証と承認のメカニズムを扱う多くの質問がありますが、アプリケーションレベルで安全なサービスを実装する方法の詳細については触れていません。

たとえば、私のWebアプリケーション(Javaを念頭に置いていますが、これは実際にはすべてのバックエンドに当てはまります)に、APIのユーザーがユーザー名とパスワードでログインできる安全な認証システムがあるとします。ユーザーがリクエストを行うと、リクエスト処理パイプラインの任意の時点でgetAuthenticatedUser()、ユーザーがログインしていない場合はnullユーザー、またはログインしているユーザーを表すユーザードメインオブジェクトを返すメソッドを呼び出すことができます。

APIにより、認証されたユーザーはデータにアクセスできます。たとえば、GET /api/orders/はそのユーザーの注文リストを返します。同様に、GET to /api/tasks/{task_id}はその特定のタスクに関連するデータを返します。

ユーザーのアカウントに関連付けることができるいくつかの異なるドメインオブジェクトがあると仮定します(注文とタスクは2つの例であり、顧客、請求書などもある可能性があります)。認証されたユーザーだけが自分のオブジェクトに関するデータにアクセスできるようにしたいので、ユーザーがを呼び出すときは、ユーザーが/api/invoices/{invoice_id}そのリソースにアクセスすることを許可されていることを確認してから、サービスを提供します。

私の質問は、この承認の問題に対処するためのパターンや戦略は存在するのでしょうか。私が検討しているオプションの1つは、ヘルパーインターフェイス(つまりSecurityUtils.isUserAuthorized(user, object))を作成することです。これは、リクエストの処理中に呼び出して、ユーザーがオブジェクトをフェッチすることを許可されていることを確認できます。これは、これらの呼び出しの多くでアプリケーションのエンドポイントコードを汚染するため、理想的ではありません。

Object someEndpoint(int objectId) {
    if (!SecurityUtils.isUserAuthorized(loggedInUser, objectDAO.get(objectId)) {
        throw new UnauthorizedException();
    }
    ...
}

...そして、少し面倒かもしれませんが、すべてのドメインタイプにこのメソッドを実装するという問題があります。これが唯一のオプションかもしれませんが、私はあなたの提案を聞いて興味があります!


ユーザーが「ログイン」と言うとき、セッションを維持しているということですか?
JimmyJames

回答:


8

神の愛のためにSecurityUtilsクラスを作らないでください!

あなたのクラスはたった数ヶ月でスパゲッティコードの1万行になります!あなたは持っている必要がありますAction(作成、読み取り、更新、破壊、リストなど)をお使いに渡されるタイプをisUserAuthorized()すばやく千行長になる方法、switchユニットテストには難しいだろう、ますます複雑なロジックとの声明。しないでください。


一般に、少なくともRuby on Railsでは、各モデルにポリシークラスを設定することにより、各ドメインオブジェクトに独自のアクセス権限を与えるようにします。次に、コントローラーは、要求の現在のユーザーがリソースにアクセスできるかどうかをポリシークラスに尋ねます。Rubyでの例は次のとおりです。Javaでこれほど実装したことがないので、アイデアは明確に出てくるはずです。

class OrderPolicy

    class Scope < Struct.new(:user, :scope)

        def resolve

            # A user must be logged in to interact with this resource at all
            raise NotAuthorizedException unless user

            # Admin/moderator can see all orders
            if (user.admin? || user.moderator?)
                scope.all
            else
                # Only allow the user to see their own orders
                scope.where(orderer_id: user.id)
            end
        end
    end

    # Constructor, if you don't know Ruby
    def initialize(user, order)
        raise NotAuthorizedException unless user
        @user = user
        @order= order
    end

    # Whitelist what data can be manipulated by each type of user
    def valid_attributes
        if @user.admin?
            [:probably, :want, :to, :let, :admin, :update, :everything]
        elsif @user.moderator?
            [:fewer, :attributes, :but, :still, :most]
        else
            [:regualar, :user, :attributes]
        end
    end

    # Maybe restrict updatable attributes further
    def valid_update_attributes
    end

    # Who can create new orders
    def create?
        true # anyone, and they would have been authenticated already by #initialize
    end

    # Read operation
    def show?
        @user.admin? || @user.moderator? || owns_order
    end

    # Only superusers can update resources
    def update?
        @user.admin? || @user.moderator?
    end

    # Only admins can delete, because it's extremely destructive or whatever
    def destroy?
        @user.admin?
    end

    private

    # A user 'owns' an order if they were the person who submitted the order
    # E.g. superusers can access the order, but they didn't create it
    def owns_order
        @order.orderer_id == @user.id
    end
end

複雑なネストされたリソースがある場合でも、一部のリソースはネストされたリソースを「所有」する必要があるため、トップレベルのロジックは本質的にバブルダウンします。ただし、ネストされたリソースは、「親」リソースとは関係なく更新できる場合に、独自のポリシークラスを必要とします。

私の大学の学部向けのアプリケーションでは、すべてCourseオブジェクトを中心に展開します。これはUser中心的なアプリケーションではありません。ただし、Userはに登録されているCourseため、次のことを簡単に確認できます。

@course.users.include? current_user && (whatever_other_logic_I_need)

Userほとんどすべてのリソースがに関連付けられているため、特定のリソースを変更する必要がありますCourse。これは、owns_whateverメソッドのポリシークラスで行われます。

私はあまりJavaアーキテクチャーを実行していませんPolicyが、認証を必要とするさまざまなリソースがインターフェースを実装する必要があるインターフェースを作成できるようです。次に、ドメインオブジェクトごとに必要となるほど複雑になる可能性のあるすべての必要なメソッドがあります。重要なことは、ロジックをモデル自体に結び付けることですが、同時にそれを別のクラス(単一責任原則(SRP))に保持します。

コントローラのアクションは次のようになります。

public List<Order> index(OrderQuery query) {

    authorize(Order.class)
    // you should be auto-rescuing the NotAuthorizedException thrown by
    //the policy class at the controller level (or application level)

    // if the authorization didn't fail/rescue from exception, just render the resource
    List<Order> orders = db.search(query);
    return renderJSON(orders);
}

public Order show(int orderId) {

    authorize(Order.class)
    Order order = db.find(orderId);
    return renderJSON(order);
}

1

より便利な解決策は、アノテーションを使用して、何らかの形式の承認を必要とするメソッドをマークすることです。これはビジネスコードとは異なり、Spring SecurityまたはカスタムAOPコードで処理できます。エンドポイントではなくビジネスメソッドでこれらのアノテーションを使用すると、許可されていないユーザーがエントリポイントに関係なくそれらを呼び出そうとすると、必ず例外が発生します。


私が求めていることではありません。Springアノテーションを使用すると、ユーザーが特定の認証レベルを持っていることを保証できます(ユーザーが管理者であることなど)が、特定のエンティティへのアクセス制限に役立つとは思いません。ビジネスロジックがIDに基づいて請求書をフェッチして返すとします。ログインして、他の誰かの請求書IDをエンドポイントに提供します。Springアノテーションがそのフォームへの水平アクセスを妨げるとは思わないのですか?
HJCee 2016

1
@HJCee Spring Securities AOPアノテーションは非常に表現力あります。セグメントは、オブジェクトを返すメソッドを持つfieldName を使用して、パスされたオブジェクトを参照する@PreAuthorization("hasRole('ADMIN') and #requestingUser.company.uuid == authentication.details.companyUuid")アノテーションを定義できます。指すセキュリティコンテキストに格納されたオブジェクト。#requestingUserrequestingUsergetCompany()getUuid()authenticationAuthentication
Roman Vottner、2016

1
@RomanVottner本当に複雑な認証が必要な場合はどうなりますか?たとえば、YタグにXゴールドバッジが付いているStack Exchangeのモデレーターのみが、質問(またはその他)を削除するための編集を行うことができます。300文字の単一行注釈を渡します。
Chris Cirefice 16

1
@ChrisCirefice @PreAuthorize("hasPermission(#user, 'allowDoSomething')")カスタム権限エバリュエーターを使用して実装するか、カスタム式ハンドラーとルートを記述します。利用可能な注釈の動作を変更したい場合は、このスレッドを
Roman Vottner

0

機能ベースのセキュリティを使用します。

機能は、特定のアクションを実行できるという証拠として機能する、偽造できないオブジェクトです。この場合:

  • 各ロール(許可されたアクションのセット)をインターフェースにします。
  • 認証を必要とする操作は、それぞれのインターフェース上のメソッドである必要があります。可能であれば、受信者がリクエストの現在のユーザーでない場合、これらは例外をスローする必要があります。

これにより、現在のユーザーが許可されていないことを実行することは不可能になります。

そのようにそれは不可能です


1
批評家ではありませんが、これは私の回答のTL; DRではありませんか?はいの場合、自分で書くのではなく、私の答えにコメントすることをお
勧めし

結構です。ここでの考え方は、Java型システムでユーザーが持つことができるさまざまな役割を表現することです。つまり、ユーザーが持っていない特権を必要とするユーザーに対してメソッドを呼び出すことはできません。
デミ

Chrisのコメントに加えて、私の質問はロールベースのアクセス制限(これは適切なWebフレームワークで実装するのは簡単です)ではなく、ユーザーとデータ間の関連付けに基づくアクセス制限(「オブジェクトXはユーザーYが所有しています」)に関するものですこのような関連付けの非常に単純な例ですが、非常に複雑になる場合があります)。それが私が本当にアドバイスを求めている問題です。
HJCee 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.