我正在尝试将Friend身份验证和授权集成到Clojure / Compojure单页面Web应用程序中。
我有一个由Angular控制器支持的登录表单,该控制器使用AJAX对Web应用程序验证用户名和密码,并获取经过身份验证的用户记录。因此,我不希望Friend基于表单的登录提供默认行为 - 我基本上想要依赖HTTP状态代码而我不想要任何朋友页面重定向。< / p>
例如,制作未经身份验证的请求应该只返回401状态代码,而不应重定向到“/ login”。通过在配置好友时指定一个自定义的“: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请求/响应包装和工作)。
因此,当我调用朋友“make-auth”函数时,我认为我需要等效于此:
{:status 200 :body user-record}
然而,似乎我可以拥有身份验证地图或响应 - 但不能同时拥有两者。
这可以通过朋友实现吗?如果可以的话,怎么做?
答案 0 :(得分:1)
您需要将:redirect-on-auth?
设为false
,并且需要将回复包含在回复映射{:status 200 :body (workflows/make-auth...)}
中。请注意,您可能需要将您的正文序列化为String
或其他可以处理的内容。
答案 1 :(得分: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)))))))