【问题标题】:Use Friend for authentication and authorisation in a single page Clojure web application在单页 Clojure Web 应用程序中使用 Friend 进行身份验证和授权
【发布时间】:2013-12-14 21:47:57
【问题描述】:

我正在尝试将 Friend 身份验证和授权集成到 Clojure/Compojure 单页 Web 应用程序中。

我有一个由 Angular 控制器支持的登录表单,并且该控制器使用 AJAX 对 Web 应用程序的用户名和密码进行身份验证,并获取经过身份验证的用户记录。因此,我不想要 Friend 基于表单的登录提供的默认行为 - 我基本上想要依赖 HTTP 状态代码,并且我确实想要任何 Friend 页面重定向。

例如,发出未经身份验证的请求应仅返回 401 状态代码,并且不应重定向到“/login”。我通过在配置 Friend 时指定自定义“:unauthenticated-handler”来完成这部分工作(代码包含在下面)。

在成功登录时,我只需要 200 状态代码,而不是重定向到最初请求的页面。这是我无法工作的。

我根据各种示例编写了一个自定义好友身份验证工作流程(我的 Clojure 技能目前处于初级水平):

(defn my-auth
  [& {:keys [credential-fn]}]
    (routes
      (GET "/logout" req
        (friend/logout* {:status 200}))
      (POST "/login" {{:keys [username password]} :params}
        (if-let [user-record (-> username credential-fn)]
          (if
            (and
              [user-record password]
              (creds/bcrypt-verify password (:password user-record)))
            (let [user-record (dissoc user-record :password)]
              (workflows/make-auth user-record {:cemerick.friend/workflow :my-auth :cemerick.friend/redirect-on-auth? true}))
            {:status 401})
          {:status 401}))))

这是我声明了中间件的处理程序:

(def app
  (-> 
    (handler/site
      (friend/authenticate app-routes
        {:credential-fn (partial creds/bcrypt-credential-fn my-credential-fn)
         :unauthenticated-handler unauthenticated
         :workflows [(my-auth :credential-fn my-credential-fn)]}))
    (session/wrap-session)
    (params/wrap-keyword-params)
    (json/wrap-json-body)
    (json/wrap-json-response {:pretty true})))

以及上面引用的附加处理函数:

(defn unauthenticated [v]
  {:status 401 :body "Unauthenticated"})

最后是一个额外的路由片段来测试身份验证:

(GET "/auth" req
  (friend/authenticated (str "You have successfully authenticated as "
    (friend/current-authentication))))

大部分有效,几乎可以满足我的所有需求。

因为“redirect-on-auth?”在“make-auth”中为真,成功登录时会生成一个页面重定向 - 我想阻止该重定向,所以我将值设置为 false。但是,这个单一的更改会导致 404 和登录失败。

除了 Friend 身份验证映射之外,我还需要以某种方式在此处返回 200 状态代码,并且我还想在响应正文中返回“用户记录”,以便客户端应用程序可以根据用户定制 UI角色(我已经将 JSON 请求/响应打包并工作)。

所以我认为当我调用 Friend“make-auth”函数时我需要与此等效的功能:

{:status 200 :body user-record}

但是,我似乎可以拥有身份验证映射或响应 - 但不能同时拥有。

这可以通过 Friend 实现吗?如果可以,如何实现?

【问题讨论】:

标签: authentication clojure compojure


【解决方案1】:

我已经为您的问题构建了一个可行的解决方案。关键是确保您返回状态、正文,并将身份验证凭据合并到响应中。所以你想返回

{:status 200 :body {:message "Hello World"} 
 :session {:cemerick.friend/identity {...}} ...}

上述解决方案的问题在于它将会话结果合并到正文中,而不是合并到响铃响应本身中。 Gist

(defn- form-ajax-response 
  "Creates an Ajax Request.
   Authenticates User by merging authentication
   into response along with status and body allowing
   for ajax response."
  [status body resp auth content-type]
  (let [auth-resp (friend/merge-authentication (dissoc resp :body-params) auth)
        formatter (case content-type 
                    "application/edn" noir.response/edn
                    noir.response/json)]
    (merge auth-resp {:status status} (formatter {:body body}))))

(def not-nil? (complement nil?))

(defn form-or-ajax-login 
  "Friend Workflow for Handling forms or ajax requests posted to 
   '/login'.  When a form is posted, the request will initiate a redirect
   on login When the post is performed via ajax. A response will be sent
   with success and redirect information"
  [& {:keys [login-uri credential-fn login-failure-handler redirect-on-auth?] :as ajax-config
      :or {redirect-on-auth? true}}]
  (fn [{:keys [uri request-method content-type params] :as request}]
    (when (and (= :post request-method)
               (= uri (gets :login-uri ajax-config (::friend/auth-config request))))
      (let [creds {:username (:username params) 
                   :password (:password params)}
            {:keys [username password]} creds
            ajax? (not-nil? (ajax-request-type content-type))
            cred-fn (gets :credential-fn ajax-config (::friend/auth-config request))]
        (when-let [user-record (and username password (cred-fn creds))]
          (let [user-auth (workflow/make-auth user-record {::friend/workflow :form-or-ajax
                                                           ::friend/redirect-on-auth? false})]
            (case ajax?
              true (form-ajax-response 202 {:Tom "Peterman"} request user-auth content-type)
              user-auth)))))))

【讨论】:

    【解决方案2】:

    您需要将:redirect-on-auth? 设置为false,并且您需要将您的响应包装在响应映射{:status 200 :body (workflows/make-auth...)} 中。请注意,您可能需要将您的正文序列化为 String 或其他可以处理的内容。

    【讨论】:

    • 这看起来像它的工作,但虽然它确实正确地将用户记录作为 JSON 返回到客户端以及 200 状态代码,但经过身份验证的会话仍然 创建。当我尝试访问受保护的资源时,它失败了。
    • @caprica,很遗憾听到这个消息。您能否提供更多代码作为示例?我对你的中间件特别感兴趣。
    • @caprica,您能否尝试删除 session/wrap-sessionhandler/site 已经将您的处理程序包装在会话中,这可能会导致您的问题。
    • 删除 session/wrap-session 没有任何区别 - 在随后访问经过身份验证的链接时我仍然失败。实际上,我只是添加了 session/wrap-session 来在事情不工作时尝试一些事情。
    • 由于另一个答案中提到的错误或缺少功能/行为,它不起作用。在我自己的应用程序中,我通过让初始客户端 AJAX 请求忽略成功身份验证时的页面重定向来伪造它,然后发出后续请求以获取角色。不理想,但它有效。
    猜你喜欢
    • 1970-01-01
    • 2014-09-05
    • 1970-01-01
    • 2016-12-27
    • 1970-01-01
    • 2019-09-23
    • 1970-01-01
    • 2015-06-29
    相关资源
    最近更新 更多