Compojure路由与不同的中间件

我正在使用Compojure(以及Ring和相关的中间件)在Clojure中编写一个API。

我试图根据路线应用不同的validation码。 考虑下面的代码:

(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来根据一个共同的基本path下的一些路由,但它是一个相当的约束(下面的代码可能无法正常工作,只是为了说明这个想法):

 (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 ,也没有使用共同的基本path(理想情况下,不会有)。

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

这将首先通过pipe理路由,然后通过用户路由运行传入的请求,在这两种情况下应用正确的身份validation。 这里的主要思想是,如果调用者不能访问路由而不是抛出错误,那么你的authentication函数应该返回nil 。 这样,如果a)路由实际上与定义的pipe理路由不匹配,或者b)用户没有所需的authentication,则admin-routes将返回nil。 如果admin-routes返回nil,用户路由将被组合尝试。

希望这可以帮助。

编辑:我写了一篇关于Compojure的post,你可能会觉得有用: http : //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?))))) 

你有没有考虑过使用沙洲 ? 它使用基于angular色的授权,并允许您声明性地指定访问特定资源所需的angular色。 查看Sandbar的文档以获取更多信息,但是可以这样工作(请注意参考一个虚构的my-auth-function ,这就是你authentication代码的地方):

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

这是一个合理的问题,当我自己碰到这个问题时,我发现了一个令人惊讶的棘手问题。

我想你想要的是这样的:

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

需要注意的两件事:身份validation中间件在路由表单中,中间件调用一个真正的处理程序的匿名函数。 为什么?

  1. 正如您所说的,您需要路由应用authentication中间件,否则请求将永远不会路由到authentication中间件! 换句话说,路由需要在authentication环之外的中间件环上。

  2. 如果您使用Compojure的路由表单(如GET),并且在表单主体中应用中间件,那么中间件function需要一个真正的响应响应处理程序(即接受请求并返回响应的函数)作为参数,而不是像string或响应图那样简单。

这是因为,根据定义,像wrap-basic-authentication这样的中间件函数只把处理器当作参数,而不是裸露的string或响应映射或其他东西。

那为什么很容易错过呢? 原因是像(GET [path args&body] …)这样的Compojure路由运算符试图通过在正文字段中允许传递的forms非常灵活来使事情变得容易。 你可以传入一个真正的处理函数,或者只是一个string,或者一个响应地图,或者可能还有别的东西,这些东西对我来说还没有发生。 这些都是在Compojure内部的render多方法中进行的。

这种灵活性掩盖了GET表单实际上在做什么,所以当你尝试做一些不同的事情时很容易混淆。

在我看来,vedang的主要答案在大多数情况下不是一个好主意。 它主要使用compojure机器来回答“路线是否符合要求?”的问题。 (如果没有,则返回nil)也回答“请求是否通过authentication? 这是有问题的,因为通常情况下,您希望validation失败的请求根据HTTP规范返回401状态码的正确响应。 在这个答案中,考虑一下如果你在这个例子中添加了一个失败的admin-authentication的错误响应,有效的用户authentication请求会发生什么情况:所有有效的用户authentication请求都会失败,并在pipe理路由层发生错误。

我刚刚发现了以下不相关的页面,解决了同样的问题:

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

我没有意识到可以使用这种types的语法(我还没有testing过):

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

我将改变你最终如何处理authentication,以分离身份validation和过滤路由的过程。

而不是只有pipe理authentication? 和用户身份validation? 返回布尔值或用户名称,将其用作更多的“访问级别”键,您可以根据每个路由级别进行过滤,而无需针对不同的路由进行“重新authentication”。

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

您还需要考虑针对此path的现有基本身份validation中间件的替代scheme。 按照目前的devise,如果您不提供凭据,它总是会返回{:status 401} ,所以您需要考虑这一点,并让它继续下去。

这样的结果放在请求映射中的:basic-authentication密钥中,然后可以在所需的级别进行过滤。

想到的主要“过滤”情况是:

  • 在上下文级别(如你的答案),除了你可以过滤掉没有要求的请求:basic-authentication key
  • 在每个路由级别上,在经过本地检查后如何返回401响应,如何进行身份validation。 请注意,除非您对单个路由进行上下文级别筛选,否则这是区分404和401的唯一方法。
  • 根据authentication级别不同的页面视图

要记住的最大的事情是,你必须继续反馈无效的路线零,除非被要求的url需要authentication。 你需要确保你没有过滤出超过你想要的,通过返回一个401,这将导致戒指停止尝试任何其他路线/句柄。