web-dev-qa-db-ja.com

異なるミドルウェアを使用したルートの構成

私は現在、Compojure(およびRingと関連するミドルウェア)を使用してClojureでAPIを作成しています。

ルートに応じて異なる認証コードを適用しようとしています。次のコードについて考えてみます。

(defroutes public-routes
  (GET "/public-endpoint" [] ("PUBLIC ENDPOINT")))

(defroutes user-routes
  (GET "/user-endpoint1" [] ("USER ENDPOINT 1"))
  (GET "/user-endpoint2" [] ("USER ENDPOINT 1")))

(defroutes admin-routes
  (GET "/admin-endpoint" [] ("ADMIN ENDPOINT")))

(def app
  (handler/api
    (routes
      public-routes
      (-> user-routes
          (wrap-basic-authentication user-auth?)))))
      (-> admin-routes
          (wrap-basic-authentication admin-auth?)))))

wrap-basic-authenticationは実際にルートをラップするため、ラップされたルートに関係なく試行されるため、これは期待どおりに機能しません。具体的には、リクエストをadmin-routesにルーティングする必要がある場合でも、user-auth?は試行されます(失敗します)。

私はcontextを使用してroot共通のベースパスの下にあるいくつかのルートを使用しましたが、それはかなりの制約です(以下のコードは単にアイデアを説明するために機能しない場合があります):

(defroutes user-routes
  (GET "-endpoint1" [] ("USER ENDPOINT 1"))
  (GET "-endpoint2" [] ("USER ENDPOINT 1")))

(defroutes admin-routes
  (GET "-endpoint" [] ("ADMIN ENDPOINT")))

(def app
  (handler/api
    (routes
      public-routes
      (context "/user" []
        (-> user-routes
            (wrap-basic-authentication user-auth?)))
      (context "/admin" []
        (-> admin-routes
            (wrap-basic-authentication admin-auth?))))))

何かが足りないのか、それともdefroutesに制約がなく、共通のベースパスを使用せずに(理想的には何もない)、目的を達成する方法があるのだろうかと思っています。

38
user1391110
(defroutes user-routes*
  (GET "-endpoint1" [] ("USER ENDPOINT 1"))
  (GET "-endpoint2" [] ("USER ENDPOINT 1")))

(def user-routes
     (-> #'user-routes*
         (wrap-basic-authentication user-auth?)))

(defroutes admin-routes*
  (GET "-endpoint" [] ("ADMIN ENDPOINT")))


(def admin-routes
     (-> #'admin-routes*
         (wrap-basic-authentication admin-auth?)))

(defroutes main-routes
  (ANY "*" [] admin-routes)
  (ANY "*" [] user-routes)

これにより、最初にadminルートを介して、次にユーザールートを介して着信要求が実行され、どちらの場合も正しい認証が適用されます。ここでの主な考え方は、発信者がルートにアクセスできない場合、エラーをスローする代わりに、認証関数がnilを返す必要があるということです。このように、a)ルートが定義された管理ルートと実際に一致しない場合、またはb)ユーザーが必要な認証を持っていない場合、admin-routesはnilを返します。 admin-routesがnilを返す場合、user-routesはcompojureによって試行されます。

お役に立てれば。

編集:私はしばらく前にCompojureについての投稿を書きましたが、それはあなたが役に立つと思うかもしれません: https://vedang.me/techlog/2012-02-23-composability-and-compojure/

20
vedang

私はこの問題に遭遇しましたが、wrap-routes(compojure 1.3.2)はエレガントに解決しているようです。

(def app
  (handler/api
    (routes
      public-routes
      (-> user-routes
          (wrap-routes wrap-basic-authentication user-auth?)))))
      (-> admin-routes
          (wrap-routes wrap-basic-authentication admin-auth?)))))
17
Julio

これは理にかなった質問であり、自分で遭遇したときに驚くほど難しいと感じました。

私はあなたが欲しいものはこれだと思います:

(defroutes public-routes
  (GET "/public-endpoint" [] ("PUBLIC ENDPOINT")))

(defroutes user-routes
  (GET "/user-endpoint1" _
       (wrap-basic-authentication
        user-auth?
        (fn [req] (ring.util.response/response "USER ENDPOINT 1"))))

  (GET "/user-endpoint2" _
       (wrap-basic-authentication
        user-auth?
        (fn [req] (ring.util.response/response "USER ENDPOINT 1")))))

(defroutes admin-routes
  (GET "/admin-endpoint" _
       (wrap-basic-authentication
        admin-auth? (fn [req] (ring.util.response/response "ADMIN ENDPOINT")))))

(def app
  (handler/api
   (routes
    public-routes
    user-routes
    admin-routes)))

注意すべき2つのこと:認証ミドルウェアはルーティングフォーム内にあり、ミドルウェアは本物のハンドラーである無名関数を呼び出します。どうして?

  1. あなたが言ったように、あなたは認証ミドルウェアを適用する必要がありますルーティング、さもないとリクエストは認証ミドルウェアにルーティングされません!言い換えると、ルーティングはミドルウェアリング上にある必要があります外部認証リング。

  2. GETなどのCompojureのルーティングフォームを使用し、フォームの本体にミドルウェアを適用する場合、ミドルウェア関数は引数として本物のリング応答ハンドラー(つまり、要求を受け取り、応答を返す関数)を必要とします。文字列や応答マップのような単純なものではなく。

これは、定義上、wrap-basic-authenticationのようなミドルウェア関数は、ハンドラーのみを引数として受け取り、裸の文字列や応答マップなどを受け取らないためです。

では、なぜこれを見逃しやすいのでしょうか。その理由は、(GET [path args&body] ...)のようなCompojureルーティング演算子は、bodyフィールドで渡すことができるフォームに非常に柔軟に対応することで、物事を簡単にしようとするためです。真のハンドラー関数、文字列、応答マップ、またはおそらく私が思いつかなかった何かを渡すことができます。これはすべて、Compojure内部のrenderマルチメソッドに配置されています。

この柔軟性は、GETフォームが実際に行っていることを偽装するため、少し違うことをしようとすると混乱しやすくなります。

私の見解では、ヴェーダーンガによる主要な回答の問題は、ほとんどの場合、良い考えではありません。基本的に、「ルートはリクエストに一致しますか?」という質問に答えるための複合機械を使用します。 (そうでない場合は、nilを返します)「要求は認証に合格しますか?」という質問にも答えます。通常、認証に失敗したリクエストは、HTTP仕様に従って、401ステータスコードで適切な応答を返す必要があるため、これは問題があります。その回答では、失敗した管理者認証に対するこのようなエラー応答をその例に追加した場合、有効なユーザー認証済みリクエストがどうなるかを検討してください。すべての有効なユーザー認証済みリクエストは失敗し、管理ルーティングレイヤーでエラーが発生します。

3
algal

同じ問題に対処する次の無関係なページを見つけました。

http://compojureongae.posterous.com/using-the-app-engine-users-api-from-clojure

そのタイプの構文を使用できることに気づいていませんでした(まだテストしていません)。

(defroutes public-routes
  (GET "/public-endpoint" [] ("PUBLIC ENDPOINT")))

(defroutes user-routes
  (GET "/user-endpoint1" [] ("USER ENDPOINT 1"))
  (GET "/user-endpoint2" [] ("USER ENDPOINT 1")))

(defroutes admin-routes
  (GET "/admin-endpoint" [] ("ADMIN ENDPOINT")))

(def app
  (handler/api
    (routes
      public-routes
      (ANY "/user*" []
        (-> user-routes
            (wrap-basic-authentication user-auth?)))
      (ANY "/admin*" []
        (-> admin-routes
            (wrap-basic-authentication admin-auth?))))))
1
user1391110

砂州 の使用を検討しましたか?ロールベースの承認を使用し、特定のリソースにアクセスするために必要なロールを宣言的に指定できます。詳細については、Sandbarのドキュメントを確認してください。ただし、次のように機能する可能性があります(架空のmy-auth-functionへの参照に注意してください。ここに、認証コードを配置します)。

(def security-policy
     [#"/admin-endpoint.*"          :admin 
      #"/user-endpoint.*"           :user
      #"/public-endpoint.*"         :any])

(defroutes my-routes
  (GET "/public-endpoint" [] ("PUBLIC ENDPOINT"))
  (GET "/user-endpoint1"  [] ("USER ENDPOINT1"))
  (GET "/user-endpoint2"  [] ("USER ENDPOINT2"))
  (GET "/admin-endpoint"  [] ("ADMIN ENDPOINT"))

(def app
  (-> my-routes
      (with-security security-policy my-auth-function)
      wrap-stateful-session
      handler/api))
1
Gert

認証でルートを認証およびフィルタリングするプロセスを分割するために、一般的に認証を処理する方法をシフトします。

Admin-authを持っているだけではなく?とユーザー認証?ブール値またはユーザー名を返します。これを「アクセスレベル」キーとして使用します。これにより、さまざまなルートを「再認証」することなく、ルートごとのレベルでフィルタリングできます。

(defn auth [user pass]
  (cond
    (admin-auth? user pass) :admin
    (user-auth? user pass) :user
    true :unauthenticated))

また、このパスの既存の基本認証ミドルウェアの代替を検討することもできます。現在設計されているため、認証情報を提供しない場合は常に{:status 401}が返されるため、これを考慮して、代わりに続行する必要があります。

この結果は、リクエストマップの:basic-authenticationキーに配置され、必要なレベルでフィルタリングできます。

頭に浮かぶ主な「フィルタリング」のケースは次のとおりです。

  • 必要な:basic-authenticationキーを持たないリクエストを除外できることを除いて、コンテキストレベル(回答にあるものなど)で
  • ルートごとのレベルで、認証方法のローカルチェック後に401応答を返します。個々のルートでコンテキストレベルのフィルタリングを行わない限り、これが404と401を区別する唯一の方法であることに注意してください。
  • 認証レベルに応じたページの異なるビュー

覚えておくべき最大のことは、要求されているURLが認証を必要としない限り、無効なルートに対してnilをフィードバックし続ける必要があるということです。 401を返すことで、必要以上にフィルターで除外されていないことを確認する必要があります。これにより、リングは他のルート/ハンドルの試行を停止します。

0
deterb