web-dev-qa-db-ja.com

REST API承認戦略

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

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

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

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

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

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

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

8
HJCee

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

あなたのクラスは、ほんの数ヶ月で10k行のスパゲッティコードになります! Actionタイプ(作成、読み取り、更新、破棄、リストなど)をisUserAuthorized()メソッドに渡す必要があります。これはすぐに数千行の長さになりますswitchステートメントは、単体テストが困難になるほど複雑化するロジックを含んでいます。しないでください。


一般的に、少なくともRuby on Rails)では、各ドメインオブジェクトにpolicy各モデルのクラス。次に、リクエストの現在のユーザーがリソースにアクセスできるかどうかをコントローラーがポリシークラスに尋ねます。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
_

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

私の大学の学部向けのアプリケーションでは、everythingCourseオブジェクトを中心に展開します。 User中心のアプリケーションではありません。ただし、Usersは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);
}
_
8
Chris Cirefice

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

1

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

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

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

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

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

0
Demi