一个 Kitura Credentials 插件,用于使用网页表单进行本地身份验证(例如,检查本地数据库中的凭据)。
此插件仅与 Kitura 2 和 Swift 4 兼容。
用法类似于 Kitura-CredentialsHTTP,您需要在实例化插件时传递一个闭包来验证凭据。有两种方法可以做到这一点。
更简单的方法与 Kitura-CredentialsHTTP 的工作方式最接近。 你传递一个闭包,它接受用户名、密码和回调方法。 用户名和密码将从 URL 编码的 POST 主体中提取,其中包含名为“username”和“password”的值; 如果你希望这些字段在表单的 HTML 中具有不同的名称,请分别设置 usernamePostField
和 passwordPostField
参数来覆盖它们。
let local = CredentialsLocal() { username, password, callback in
// Check to see if the username is "admin@example.com" and the password is
// "swordfish". In real use you'd probably be doing something like hashing
// the password and checking for the credentials in a database.
if username == "admin@example.com", password == "swordfish" {
// On success, pass a UserProfile object to the callback.
let userProfile = UserProfile(id: username, displayName: username, provider: "Local")
callback(userProfile)
}
else {
// On failure, pass nil to the callback.
callback(nil)
}
}
// Override the names of the respective fields.
local.usernamePostField = "emailAddress"
local.passwordPostField = "passphrase"
如果你需要验证除了用户名和密码字段之外的更多字段,或者正在使用 POST 主体以另一种方式编码(例如“multipart/form-data”)的表单,则需要使用第二种方法。 在这种情况下,回调会传递整个 RouterRequest 对象。 这是一个示例,我们在表单上验证用户名和密码字段旁边的“captcha”字段。
let local = CredentialsLocal() { request, callback in
guard let body = request.body?.asURLEncoded, let userId = body["username"], let pass = body["password"], let cap = body["captchaVal"] else {
callback(nil)
return
}
if userId != "admin", pass != "swordfish", cap != "123456" {
callback(nil)
return
}
let userProfile = UserProfile(id: userId, displayName: userId, provider: "Local")
callback(userProfile)
}
实例化 CredentialsLocal 后,你应该将其作为插件添加到 Credentials 实例中,然后将后一个实例分配给处理应用程序登录操作的路由处理程序。 例如,如果你有一个发布到 /log-in
的表单
let simpleCredents = Credentials()
let simpleCallbackLocal = CredentialsLocal() { userId, password, callback in
// …
}
simpleCredents.register(plugin: simpleCallbackLocal)
router.post("/log-in", middleware: simpleCredents)
请注意,如果你的目标是阻止未经身份验证的用户访问某些页面,则需要自己编写代码来实现。 一个简单的例子是 编写一个 RouterMiddleware 实现,如下所示:
public class Restrictor: RouterMiddleware {
public func handle(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) throws {
guard let _ = request.userProfile else {
try! response.send("Access denied.").status(.forbidden).end()
return
}
next()
}
}
然后将其作为中间件附加到应用程序中的 /admin
路径。
这是一个示例 main.swift,其中包含更完整的实现示例,带有登录表单。 它还使用上面演示的“Restrictor”中间件。(如果你还不熟悉如何在 Kitura 中使用表单,我建议阅读 相关章节,在 Kitura Until Dawn,我的免费 Kitura 电子书中。)
import Kitura
import Credentials
import CredentialsLocal
import KituraSession
// Initialize a Router
let router = Router()
// Initialize Session and have it be active for all requests
let session = Session(secret: "I like turtles.")
router.all(middleware: session)
// Have the BodyParser middleware be active for all POST requests
router.post(middleware: BodyParser())
// Initialize Credentials and CredentialsLocal and configure them
let simpleCredents = Credentials()
let simpleCallbackLocal = CredentialsLocal() { userId, password, callback in
// An example "database" of usernames and passwords
let users = ["John" : "12345", "Mary" : "qwerasdf"]
if let storedPassword = users[userId] {
if (storedPassword == password) {
// Both username and password were vaild
callback(UserProfile(id: userId, displayName: userId, provider: "Local"))
return
}
}
// else if userId or password doesnt match
callback(nil)
}
simpleCredents.register(plugin: simpleCallbackLocal)
// Add access restriction under the "admin" path.
router.all("/admin", middleware: Restrictor())
// Add a simple handler to the base "/admin" path.
router.all("/admin") { request, response, next in
if let profile = request.userProfile {
response.send("\(profile.displayName) is logged in with \(profile.provider)")
}
else {
// This shouldn't have happened because our middleware should have
// stopped the request when no userProfile was created.
response.send("This shouldn't have happened.").status(.unauthorized)
}
next()
}
// On POST requests to "/log-in", validate the user.
router.post("/log-in", middleware: simpleCredents)
// On GET requests to "/log-in", show a credentials form.
router.get("/log-in") { request, response, next in
let page = """
<!DOCTYPE html>
<html><body>
<form method="post" action="/log-in">
Username: <input type="text" name="username" /><br />
Password: <input type="password" name="password" /><br />
<input type="submit" />
</form>
</body></html>
"""
response.send(page)
next()
}
// If the user successfully logged in, redirect them to the "/admin" path.
router.post("/log-in") { request, response, next in
if let _ = request.userProfile {
try response.redirect("/admin").end()
}
else {
response.send("Access denied.").status(.unauthorized)
}
next()
}
// Kick off Kitura
Kitura.addHTTPServer(onPort: 8080, with: router)
Kitura.run()
此 Credentials 插件的 1.0 版本是一个重定向插件,基本上意味着它会尝试将未经授权的用户重定向到登录表单页面。 我认为这不是一个好主意,因为通常只是向用户显示“访问被拒绝”消息而不是重定向他们更为可取,所以我将插件重构为非重定向插件。 在现实世界中,这意味着你需要意识到升级模块后不会再发生这种重定向,并且你需要实现自己的代码来执行任何类型的访问限制和/或重定向。
到目前为止,这段代码只经过了轻微的测试。 由于安全是网络上的主要问题,我强烈建议你在生产项目中使用之前进行广泛的测试。
如果你有任何问题,请通过 GitHub 联系我,在 IBM-Swift Slack 服务器上使用“nocturnal”,或在 Freenode 上使用“_Nocturnal”。 其他联系方式在 我的网站上概述。