Reitit是Clojure/ClojureScript的Web开发框架。它的主要功能有:
- 后台路由及针对ClojureScript的前端路由
- 支持Ring
- 支持中间件(middleware),中间件使用Wrapper的方式修改处理请求的行为。参考middleware-registry
- 支持拦截器(interceptor)。拦截器与中间件实现的是相同的功能,不同的是拦截器使用
:enter
,:leave
函数修改处理请求的行为。参考interceptors - 请求/应答参数类型转换及验证,支持clojure.spec, spec-tools/data-specs, Plumatic Schema 及malli。(其中data-specs是基于clojure.spec的实现。)
- 支持Swagger 及Swagger-UI(即Swagger的API Web调试页面)
本文主要介绍如何为Reitit添加登录验证。
假如我们有以下访问路径,
/web/*
即前端静态页面,不需要访问控制/admin/*
除了其中的/admin/login
外,其它均需要访问控制
Reitit的路由定义为:
(defn- handle-post-admin-login [{:keys [parameters session]}]
(let [{:keys [username password]} (:body parameters)]
(response
(if (check-user {:username username :password password})
(let [token (uuid)
data {:expires-at (+ (System/currentTimeMillis) (* 1000 1800))
:token token
:username username}]
(swap! token-store assoc token data)
{:code 0 :data data})
{:code 2 :msg "Invalid username or password"}))))
(def admin-routes
[""
{:middleware [[wrap-access-rules {:rules access-rules :on-error on-error}]]
:no-doc true}
["/web/*"
{:get {:handler (ring/create-file-handler {:root "./web/"})}}]
["/admin"
[""
{:get {:handler handle-get-admin}}]
["/users"
{:get {:parameters {:query {:page int?
:today-only boolean?}}
:handler handle-get-admin-users}}]
["/login"
{:post {:parameters {:body {:username string?
:password string?}}
:handler handle-post-admin-login}}]
["/logout"
{:post {:handler handle-post-admin-logout}}]]])
其中的middleware里使用了wrap-access-rules使用access-rules
对请求进行验证。access-rules
的相关定义如下:
;; 在(ns :require 里添加:)
[buddy.auth.accessrules :refer [wrap-access-rules]]
;;
;; 这里我们将token保存于内存中,用户量较大或需要持久化时可存储在Redis或数据库中:
(defonce token-store (atom {}))
;; token验证函数: 上传的token必须有效且没有过期
(defn check-identity [{:keys [headers]}]
(let [{:keys [token expires-at]} (some-> headers (get "mytoken") (@token-store))]
(->> (when expires-at
(or (> expires-at (System/currentTimeMillis))
(do
(swap! token-store dissoc token)
nil)))
nil?
not)))
(def any-access (constantly true))
;; 定义验证失败时的响应函数
(def on-error (constantly {:status 403 :body "Denied"}))
(def access-rules
[{:uri "/admin/login"
:handler any-access}
{:pattern #"^/admin.*"
:handler check-identity}])
需要注意,
- 对于没有在
access-rules
里匹配的URI,所有人均可以访问。上面的/web/*
就符合这种情形。 - 若需要对全局进行访问控制,可以在全局Middleware里使用access-rules,例如
(ring/ring-handler
(ring/router
[api-routes admin-routes]
{:data {
;;
:middleware [
;;
[wrap-access-rules {:rules access-rules :on-error on-error}]
;;
]}})
(ring/routes
(swagger-ui/create-swagger-ui-handler
{:path "/swagger"
:config {:validatorUrl nil}})
(ring/create-default-handler)))