42

現在、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-routesuser-auth?が試行されます (そして失敗します)。

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

(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何かが欠けているのか、それとも共通の基本パスを使用せずに(理想的には何もない)自分に制約を与えずに欲しいものを達成する方法があるのか​​ どうか疑問に思っています.

4

6 に答える 6

22
(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)

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

お役に立てれば。

編集: しばらく前に Compojure についての記事を書きました

于 2012-05-30T18:43:58.860 に答える
18

私はこの問題に出くわしました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?)))))
于 2015-04-16T06:54:10.640 に答える
4

これは理にかなった質問ですが、私自身がこの質問に出くわしたとき、驚くほどトリッキーであることがわかりました。

あなたが望むのはこれだと思います:

(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 フィールドで渡すことができる形式を非常に柔軟にすることで、物事を簡単にしようとするためです。真のハンドラー関数、文字列、応答マップ、またはおそらく私には思い浮かばなかった何かを渡すことができます。それはすべてrenderCompojure 内部のマルチメソッドに配置されています。

この柔軟性は、GET フォームが実際に行っていることを隠してしまうため、少し違うことをしようとすると、簡単に混乱してしまいます。

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

于 2013-11-07T03:38:25.190 に答える
1

Sandbar の使用を検討しましたか? ロールベースの承認を使用し、特定のリソースにアクセスするために必要なロールを宣言的に指定できます。詳細については、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))
于 2012-05-30T20:38:50.547 に答える
1

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

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?))))))
于 2012-05-30T18:37:11.400 に答える
0

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

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

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

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

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

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

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

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

于 2012-05-31T03:20:59.450 に答える