Подтвердить что ты не робот

Сопоставление маршрутов с различным промежуточным программным обеспечением

В настоящее время я пишу API в Clojure с помощью Compojure (и Ring и связанного с ними промежуточного программного обеспечения).

Я пытаюсь применить другой код аутентификации в зависимости от маршрута. Рассмотрим следующий код:

(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 для корневых маршрутов под общей базой путь, но это довольно сложное (код ниже может не работать просто для иллюстрации идеи):

(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 и без использования общего базового пути (в идеале их не было бы).

4b9b3361

Ответ 1

(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 если маршрут не доступен для вызывающей стороны, вместо выдачи ошибки. Таким образом, admin-маршруты будут возвращать ноль, если а) маршрут фактически не соответствует определенным административным маршрутам или б) пользователь не имеет требуемой аутентификации. Если admin-route возвращает nil, пользовательские маршруты будут опробованы compojure.

Надеюсь это поможет.

РЕДАКТИРОВАТЬ: я написал пост о Compojure некоторое время назад, который вы могли бы найти полезным: https://vedang.me/techlog/2012-02-23-composability-and-compojure/

Ответ 2

Я наткнулся на эту проблему, и кажется, что 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?)))))

Ответ 3

Это разумный вопрос, который я нашел удивительно сложным, когда сам столкнулся с ним.

Я думаю, что вы хотите:

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

Следует отметить две вещи: промежуточное ПО аутентификации находится внутри формы маршрутизации, а промежуточное программное обеспечение вызывает анонимную функцию, которая является подлинным обработчиком. Почему?

  • Как вы сказали, вам необходимо применить промежуточное ПО аутентификации после маршрутизации, или запрос никогда не будет перенаправлен на промежуточное программное обеспечение аутентификации! Другими словами, маршрутизация должна находиться на промежуточном кольце вне кольца аутентификации.

  • Если вы используете формы маршрутизации Compojure, такие как GET, и вы применяете промежуточное программное обеспечение в теле формы, тогда функция промежуточного программного обеспечения в качестве аргумента нуждается в подлинном обработчике ответа на звонок (то есть в функции, которая принимает запрос и возвращает ответ), а не нечто более простое, как строка или карта ответов.

Это связано с тем, что, по определению, функции промежуточного программного обеспечения, такие как wrap-basic-authentication, принимают только обработчики в качестве аргументов, а не из простых строк или карт ответов или чего-то еще.

Так почему же так легко пропустить это? Причина в том, что операторы маршрутизации Compojure, такие как (GET [path args and body]...), стараются облегчить вам задачу, очень гибкая, с какой формой вы можете пройти в поле тела. Вы можете передать истинную функцию обработчика или просто строку или карту ответа или, возможно, что-то еще, что мне не приходило в голову. Все это изложено в мульти-методе render в внутренних компонентах Compojure.

Эта гибкость маскирует то, что делает форма GET, поэтому ее легко смешивать, когда вы пытаетесь сделать что-то немного другое.

На мой взгляд, проблема с ведущим ответом vedang - это не лучшая идея в большинстве случаев. Он по существу использует компоновую технику, которая предназначена для ответа на вопрос: "Соответствует ли маршрут запросу?" (если нет, верните нуль), чтобы ответить на вопрос "Проходит ли проверка подлинности?" Это проблематично, потому что обычно вы хотите, чтобы запросы, которые не выполняли аутентификацию, возвращали правильные ответы с 401 кодами состояния в соответствии с спецификацией HTTP. В этом ответе подумайте о том, что произойдет с действительными запросами, прошедшими аутентификацию пользователя, если вы добавили такой ответ об ошибке для неудачной аутентификации администратора к этому примеру: все действительные запросы, прошедшие аутентификацию пользователя, потерпят неудачу и выдадут ошибки на уровне маршрутизации admin.

Ответ 4

Считаете ли вы использование 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))

Ответ 5

Я только что нашел следующую несвязанную страницу, которая касается той же проблемы:

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

Ответ 6

Я бы переключил, как вы в конечном итоге обрабатываете аутентификацию, чтобы разделить процесс аутентификации и фильтрации маршрутов при аутентификации.

Вместо того, чтобы просто иметь admin-auth? и user-auth? return booleans или имя пользователя, используйте его как больше ключа "уровня доступа", который вы можете фильтровать на гораздо большем количестве уровня маршрута без необходимости повторной проверки подлинности для разных маршрутов.

(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-адрес не запрашивается для проверки подлинности. Вам нужно убедиться, что вы не отфильтровываете больше, чем хотите, возвращая 401, что вызовет остановку кольца, чтобы остановить попытки других маршрутов/ручек.