私は現在、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
に制約がなく、共通のベースパスを使用せずに(理想的には何もない)、目的を達成する方法があるのだろうかと思っています。
(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/
私はこの問題に遭遇しましたが、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?)))))
これは理にかなった質問であり、自分で遭遇したときに驚くほど難しいと感じました。
私はあなたが欲しいものはこれだと思います:
(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つのこと:認証ミドルウェアはルーティングフォーム内にあり、ミドルウェアは本物のハンドラーである無名関数を呼び出します。どうして?
あなたが言ったように、あなたは認証ミドルウェアを適用する必要があります後ルーティング、さもないとリクエストは認証ミドルウェアにルーティングされません!言い換えると、ルーティングはミドルウェアリング上にある必要があります外部認証リング。
GETなどのCompojureのルーティングフォームを使用し、フォームの本体にミドルウェアを適用する場合、ミドルウェア関数は引数として本物のリング応答ハンドラー(つまり、要求を受け取り、応答を返す関数)を必要とします。文字列や応答マップのような単純なものではなく。
これは、定義上、wrap-basic-authenticationのようなミドルウェア関数は、ハンドラーのみを引数として受け取り、裸の文字列や応答マップなどを受け取らないためです。
では、なぜこれを見逃しやすいのでしょうか。その理由は、(GET [path args&body] ...)のようなCompojureルーティング演算子は、bodyフィールドで渡すことができるフォームに非常に柔軟に対応することで、物事を簡単にしようとするためです。真のハンドラー関数、文字列、応答マップ、またはおそらく私が思いつかなかった何かを渡すことができます。これはすべて、Compojure内部のrender
マルチメソッドに配置されています。
この柔軟性は、GETフォームが実際に行っていることを偽装するため、少し違うことをしようとすると混乱しやすくなります。
私の見解では、ヴェーダーンガによる主要な回答の問題は、ほとんどの場合、良い考えではありません。基本的に、「ルートはリクエストに一致しますか?」という質問に答えるための複合機械を使用します。 (そうでない場合は、nilを返します)「要求は認証に合格しますか?」という質問にも答えます。通常、認証に失敗したリクエストは、HTTP仕様に従って、401ステータスコードで適切な応答を返す必要があるため、これは問題があります。その回答では、失敗した管理者認証に対するこのようなエラー応答をその例に追加した場合、有効なユーザー認証済みリクエストがどうなるかを検討してください。すべての有効なユーザー認証済みリクエストは失敗し、管理ルーティングレイヤーでエラーが発生します。
同じ問題に対処する次の無関係なページを見つけました。
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?))))))
砂州 の使用を検討しましたか?ロールベースの承認を使用し、特定のリソースにアクセスするために必要なロールを宣言的に指定できます。詳細については、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))
認証でルートを認証およびフィルタリングするプロセスを分割するために、一般的に認証を処理する方法をシフトします。
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
キーを持たないリクエストを除外できることを除いて、コンテキストレベル(回答にあるものなど)で覚えておくべき最大のことは、要求されているURLが認証を必要としない限り、無効なルートに対してnilをフィードバックし続ける必要があるということです。 401を返すことで、必要以上にフィルターで除外されていないことを確認する必要があります。これにより、リングは他のルート/ハンドルの試行を停止します。