Security
密码的散列和对比由noir.util.crypt提供。
他提供了两个函数$encrypt$,$compare$.其中第一个函数加密密码,而第二个则是对比密码。实际上加密是通过BCrypt来处理的。
对比代码的函数看起来像这样:
加密函数可以自定义salt,或者就不使用salt加密。
(encrypt salt raw)
(encrypt raw)
想了解Secutiry库的更多内容,请点击这里
LDAP Authentication
下面的列子演示了如何通过clj-ldap来验证sAMAccountName
首先,我们先添加依赖
[org.clojars.pntblnk/clj-ldap "0.0.9"]
接下来,我们需要引入authentication命名空间
(ns ldap-auth
(:require [clj-ldap.client :as client]))
Input Validation
验证相关助手方法在noir.validation命名空间中。如果我们要使用验证,我们需要先引入这个命名空间。
(ns myapp.routes
(:require ... [noir.validation :as v]))
验证函数在当验证不通过时,将相应的错误设置到对应的字段上。验证函数接受一个条件函数和一个包含了需要验证的字段和错误信息的Vector.当验证函数验证通过时需要返回true否则返回false.
下面是已经存在的验证函数:
has-value? args: [v] - returns true if v is truthy and not an empty string.
has-values? args: [coll] - returns true if all members of the collection has-value? This works on maps as well.
not-nil? args: [v] - returns true if v is not nil
min-length? args: [v len] - returns true if v is greater than or equal to the given len
max-length? args: [v len - returns true if v is less than or equal to the given len
matches-regex? args: [v regex] - returns true if the string matches the given regular expression
is-email? args: [v] - returns true if v is an email address
valid-file? args: [m] - returns true if a valid file was supplied
valid-number? args: [v] - returns true if the string can be parsed to a Long
greater-than? args: [v n] - returns true if the string represents a number > given
less-than? args: [v n] - returns true if the string represents a number < given
equal-to? args: [v n] - returns true if the string represents a number = given
例如:如果我们想检查id和pass字段不能为空,则可以创建如下验证规则:
(v/rule (has-value? id)
[:id "screen name is required"])
(v/rule (has-value? pass)
[:pass "password is required"])
错误信息被保存在noir.validation/errors这个atom中,这个atom被绑定到了request上。每个error都包含一个key和一个对应的错误Vector.验证函数可以在同一字段上多次调用来设置多个错误。
一旦验证规则被执行了,我们可以通过errors?这个函数来检查是否有错误。如果没有传递参数,则此函数会检查noir.validation/error是否为空,如果传递了参数,次函数根据传递的值来查询是否有错误。
Sessions
Session管理相关功能由noir.session提供。默认提供的noir.util.middleware/app-handler函数默认将Session保存在内存里。
当然你可以修改它,只需要给函数传递第二个参数,告诉它将Session保存在哪里就可
以了。 下面的例子创建了一个保存在内存里的session.
(def app (middleware/app-handler [home-routes app-routes]))
下面,我们来重新定义session的保存位置。使用:session-options来替换掉默认的值就可以了。
(def app
(middleware/app-handler
[home-routes app-routes]
:session-options {:cookie-name "example-app-session"
:store (cookie-store)}))
Accessing the session
你可以在任意范围内的任何函数里访问并操作session。
(require '[noir.session :as session])
(defn set-user [id]
(session/put! :user id)
(session/get :user))
(defn remove-user []
(session/remove! :user)
(session/get :user))
(defn set-user-if-nil [id]
(session/get :user id))
(defn clear-session []
(session/clear!))
(defroutes app-routes
(GET "/login/:id" [id] (set-user id))
(GET "/remove" [] (remove-user))
(GET "/set-if-nil/:id" [id] (set-user-if-nil id))
(GET "/logout" [] (clear-session)))
你还可以通过noir.session/flash-put!和noir.session/flash-get来创建flash变量.
(session/flash-put! :message "User added!")
(session/flash-get :message)
Adding custom middleware
Luminus使用Ring来路由应用的处理程序,你也可以添加一些中间件。
中间件可以给处理程序添加一些额外的功能。中间件函数主要被用来扩展Ring的基本处理函数。
中间件其实就是个简单的函数,它接受现有的处理程序和一些可选参数作为函数参数并返回一个新的处理程序。
(defn wrap-nocache [handler]
(fn [request]
(let [response (handler request)]
(assoc-in response [:headers "Pragma"] "no-cache"))))
(def app (wrap-nocache handler))
可以看到,上面的函数接受一个处理函数并返回一个新的函数,这个函数接受request作为参数。返回的函数只在handler所在的范围内有效。当调用这个函数时,它将会给响应map添加Pragma:no-cache键值对。
你可以添加自定义的中间件,只需要使用:middleware关键字就可以了:
(def app (middleware/app-handler
all-routes
;;put any custom middleware
;;in the middleware vector
:middleware [wrap-nocache]))
具体内容可参见Ring官方文档。
Responses
noir.response提供了很多辅助函数来处理响应.
Setting headers
可以使用set-header来设置响应头,使用一个map作为参数。这里需要注意的是,map的key必须是字符串。
(set-headers {"x-csrf" csrf}
(common/layout [:p "hi there"]))
Setting content type
你可以使用content-type函数来设置响应的类型:
(GET "/project" []
(noir.response/content-type
"application/pdf"
(clojure.java.io/input-stream "report.pdf")))
下面是有效的响应类型设置:
- XML - Wraps the response with the content type for xml and sets the body to the content.
- JSON- Wraps the response in the json content type and generates JSON from the content
- JSONP - Generates JSON for the given content and creates a javascript response for calling func-name with it.
- edn - Wraps the response in the application/edn content-type and calls pr-str on the Clojure data stuctures passed in.
(GET "/xml" [] (xml "<foo></foo>"))
(GET "/json" [] (json {:response "ok"}))
(GET "/jsonp" [] (jsonp "showUsers" [{:name "John"} {:name "Jane"}]))
(GET "/edn" [] (edn {:foo 1 :bar 2}))
为了设置响应类型,你还需要设置noir.util.middleware/app-handler中间件。和上一节的请求一样,你只需要配置:formats键就可以了
(app-handler routes :formats [:json])
Requests
默认情况下,请求的参数(比如一个form的POST请求)将会被自动绑定到request的:params键上。
但是,如果你在请求体内传递一些特殊类型的参数,则你需要使用适合的中间件来处理他们。Luminus使用ring-middleware-format来处理这些参数。
中间件可以通过在noir.util.middleware/app-handler上添加:formats键来开启:
(def app (middleware/app-handler
all-routes
:formats [:json :edn]))
这样请求中的application/json和application/edn类型将会被中间件处理。相应的请求参数会在:params中。注意,这也会处理响应中的对应类型参数。具体信息请见Response Types章节。
下面是有效的格式化类型:
:json - JSON with string keys in :params and :body-params
:json-kw - JSON with keywodized keys in :params and :body-params
:edn - native Clojure format.
:yaml - YAML format
:yaml-kw - YAML format with keywodized keys in :params and :body-params
:yaml-in-html - yaml in a html page
Static resources
在noir.io下有多个访问静态资源的帮助方法。
你可以通过调用resource-path来获取public目录的路径。
Handling file uploads
上传文件通过noir.io空间下的upload-file方法来实现,upload-file接受一个路径和文件的map。
例如我们有一个upload.html文件:
<h2>Upload a file</h2>
<form action="/upload" enctype="multipart/form-data" method="POST">
<input id="file" name="file" type="file" />
<input type="submit" value="upload" />
</form>
我们可以这样来处理文件上传。
(ns myapp.upload
...
(:require [upload-test.views.layout :as layout]
[noir.response :as response]
[noir.io :as io]))
(defn upload-page []
(layout/render "upload.html"))
(defn handle-upload [file]
(io/upload-file "/" file)
(response/redirect
(str "/" (:filename file))))
(defroutes upload-routes
(GET "/upload" [] (upload-page))
(POST "/upload" [file] (handle-upload file)))