第18章:API
认证,第一部分¶
到目前为止,你所建立的TILApp
有很多伟大的功能,但它也有一个小问题:任何人都可以创建新的用户、类别或缩写。在API
或网站上没有认证,以确保只有已知的用户可以改变数据库中的内容。在本章中,你将学习如何用认证来保护你的API
。你将学习如何在你的API
中实现HTTP
基本认证和令牌认证。你还将学习存储密码和验证用户的最佳做法。
Note
你必须在你的项目中设置和配置好PostgreSQL
。如果你仍然需要这样做,请按照第6章"配置数据库"的步骤进行。
密码¶
认证是验证某人是谁的过程。这与授权不同,后者是验证用户是否有权限执行某个特定的动作。你通常用一个用户名和密码组合来验证用户,TILApp
也不例外。
在Xcode
中打开Vapor
应用程序,打开User.swift
。在User
下面添加以下属性 var username: String
:
@Field(key: "password")
var password: String
这个属性使用列名password
来存储用户的密码。接下来,为了说明新的属性,将初始化器init(id:name:username)
替换为以下内容:
init(
id: UUID? = nil,
name: String,
username: String,
password: String
) {
self.name = name
self.username = username
self.password = password
}
密码存储¶
感谢Codable
,你不需要做任何额外的修改就可以创建带密码的用户。现有的UserController
现在自动期望在传入的JSON
中找到password
属性。然而,如果不做任何改动,你将会把用户的密码保存为纯文本。
你不应该以纯文本形式存储密码。你应该始终以安全的方式存储密码。Bcrypt
是散列密码的行业标准,Vapor
内置了它。
Bcrypt
是一种单向散列算法。这意味着你可以把密码变成哈希值,但不能把哈希值转回密码。由于Bcrypt
设计得很慢,如果有人窃取了密码散列,需要很长时间来破解密码。Bcrypt
将一个salt
与密码混合。盐是一个独特的、随机的值,以帮助防御常见的攻击。Bcrypt
还提供了一种机制,使用密码和哈希值来验证密码。
打开UsersController.swift
,找到createHandler(_:user:)
并在let user = try req.content.decode(User.self)
后添加如下内容:
user.password = try Bcrypt.hash(user.password)
在将用户的密码保存在数据库中之前,这将对其进行加密。
让用户名独一无二¶
在本章接下来的章节中,你将会使用用户名和密码来唯一地识别用户。目前,没有什么可以防止多个用户拥有相同的用户名。
打开CreateUser.swift
。在.create()
之前添加:
.field("password", .string, .required)
.unique(on: "username")
这就更新了迁移,为密码增加了一个字段,并为User
的username
增加了一个唯一索引。在应用程序运行更新后的迁移后,任何试图创建重复用户名的行为都会导致错误。
修复测试¶
你改变了User
的初始化器,所以你需要更新测试,以便Xcode
可以编译你的应用程序。打开UserTests.swift
,在testUserCanBeSavedWithAPI()
中,将let user = User...
改为以下内容:
let user = User(
name: usersName,
username: usersUsername,
password: "password")
接下来,打开Models+Testable.swift
,更新User
的扩展中的create(name:username:on:)
。再次,为password
参数添加一个值:
let user = User(
name: name,
username: username,
password: "password")
从API
中返回用户¶
由于模型已经改变,你需要重置数据库。Fluent
已经运行了User
迁移,但是表现在有一个新的列。为了在表中添加新的列,你必须删除数据库,这样Fluent
才会再次运行迁移。在终端,输入:
# 1
docker stop postgres
# 2
docker rm postgres
# 3
docker run --name postgres -e POSTGRES_DB=vapor_database \
-e POSTGRES_USER=vapor_username \
-e POSTGRES_PASSWORD=vapor_password \
-p 5432:5432 -d postgres
下面是这个的作用:
- 停止运行中的
Docker
容器postgres
。这是当前运行数据库的容器。 - 移除
Docker
容器postgres
,以删除任何现有的数据。 - 启动一个新的运行
PostgreSQL
的Docker
容器。更多信息请参见第6章"配置数据库"。
现在,构建并运行,Fluent
会用你的新添加的内容创建一个干净的数据库。
启动RESTed
,创建一个新的请求,并进行如下配置:
- URL: http://localhost:8080/api/users/
- method: POST
- Parameter encoding: JSON-encoded
添加三个带有名称和值的参数:
- name: your name
- username: a username of your choice
- password: a password of your choice
点击Send Request
。你的应用程序创建了请求的用户,但响应返回了密码哈希:
这可不好! 你应该保护密码哈希值,永远不要在响应中返回它们。事实上,任何由API
返回的用户都包括密码哈希值,包括列出所有的用户! 这是因为你在所有的路由中返回User
。你应该返回一个User
的"公共视图"。
在Xcode
中,打开User.swift
,在User
的初始化器下面添加以下内容:
final class Public: Content {
var id: UUID?
var name: String
var username: String
init(id: UUID?, name: String, username: String) {
self.id = id
self.name = name
self.username = username
}
}
这就创建了一个内层类来代表User
的公共视图,以便在响应中返回。接下来,在User.swift
的底部添加以下内容:
extension User {
// 1
func convertToPublic() -> User.Public {
// 2
return User.Public(id: id, name: name, username: username)
}
}
以下是新方法的作用:
- 在
User
上定义一个方法,返回User.Public
。 - 创建一个当前对象的公共版本。
最后,在新的扩展下面添加以下内容:
// 1
extension EventLoopFuture where Value: User {
// 2
func convertToPublic() -> EventLoopFuture<User.Public> {
// 3
return self.map { user in
// 4
return user.convertToPublic()
}
}
}
// 5
extension Collection where Element: User {
// 6
func convertToPublic() -> [User.Public] {
// 7
return self.map { $0.convertToPublic() }
}
}
// 8
extension EventLoopFuture where Value == Array<User> {
// 9
func convertToPublic() -> EventLoopFuture<[User.Public]> {
// 10
return self.map { $0.convertToPublic() }
}
}
下面是这个的作用:
- 定义
EventLoopFuture<User>
的扩展。 - 定义一个新的方法,返回一个
EventLoopFuture<User.Public>
。 - 解除包含在
self
中的用户。 - 将
User
对象转换为User.Public
。 - 为
[User]
定义一个扩展。 - 定义一个新方法,返回
[User.Public]
。 - 将数组中所有的
User
对象转换为User.Public
。 - 定义一个
EventLoopFuture<[User]>
的扩展。 - 定义一个新的方法,返回
EventLoopFuture<[User.Public]>
。 - 解除包含在·中的数组,并使用之前的扩展将所有的
User
转换为User.Public
。
这些扩展允许你在EventLoopFuture<User>
、[User]
和EventLoopFuture<[User]>
上调用convertToPublic()
。这有助于理顺你的代码,减少嵌套。这些新方法允许你改变你的路由处理程序以返回公共用户。
首先,打开UsersController.swift
,改变createHandler(_:user:)
的返回类型:
func createHandler(_ req: Request)
-> EventLoopFuture<User.Public> {
接下来,改变map
的结果,以返回一个公共用户:
return user.save(on: req.db).map { user.convertToPublic() }
这使用新的方法将一个User
转换为User.Public
。建立并运行,然后在RESTed
中创建一个新用户。你会发现用户的密码哈希值不再被返回:
现在,你必须更新其余返回User
的路由。
首先,在UsersController.swift
中,将getAllHandler(_:)
的签名改为如下:
func getAllHandler(_ req: Request)
-> EventLoopFuture<[User.Public]> {
接下来,将getAllHandler(_:)
的主体改为如下:
User.query(on: req.db).all().convertToPublic()
这使用了EventLoopFuture<[User]>
的扩展,将从数据库返回的用户转换为User.Public
。接下来,改变getHandler(_:)
的签名以返回一个公共用户:
func getHandler(_ req: Request)
-> EventLoopFuture<User.Public> {
接下来,改变主体以返回一个公共用户:
User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.convertToPublic()
最后,打开AcronymsController.swift
,替换getUserHandler(_:)
,使其返回一个公共用户:
// 1
func getUserHandler(_ req: Request)
-> EventLoopFuture<User.Public> {
Acronym.find(req.parameters.get("acronymID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { acronym in
// 2
acronym.$user.get(on: req.db).convertToPublic()
}
}
以下是变化的内容:
- 将该方法的返回类型改为
Future<User.Public>
。 - 对缩写的用户调用
convertToPublic()
以返回一个公共用户。
现在,对你的API
进行检索用户的调用都不会返回一个密码哈希值。
基本认证¶
HTTP
基本认证是一种通过HTTP
发送证书的标准化方法,由RFC 7617定义。你通常在HTTP
请求的授权标头中包含凭证。
为了生成这个头的令牌,你把用户名和密码结合起来,然后对结果进行Base64
编码。
例如,对于用户名timc
和密码password
,合并后的凭证字符串是:
timc:password
然后你对其进行Base64
编码,这样就可以得到:
dGltYzpwYXNzd29yZA==
完整的标题变成了:
Authorization: Basic dGltYzpwYXNzd29yZA==
认证是内置在Vapor
中的,包含了使用HTTP Basic
认证的帮助器。打开User.swift
,在文件的底部,添加以下内容:
// 1
extension User: ModelAuthenticatable {
// 2
static let usernameKey = \User.$username
// 3
static let passwordHashKey = \User.$password
// 4
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.password)
}
}
下面是这个的作用:
- 使
User
符合ModelAuthenticatable
。这是一个允许Fluent Model
使用HTTP
基本认证的协议。 - 告诉
Vapor User
的哪个关键路径是用户名。 - 告诉
Vapor User
的关键路径是密码散列。 - 按照
ModelAuthenticatable
的要求实现verify(password:)
。由于你使用Bcrypt
对User
的密码进行散列,在这里用Bcrypt
验证散列。
打开AcronymsController.swift
,在boot(route:)
的底部添加以下内容:
// 1
let basicAuthMiddleware = User.authenticator()
// 2
let guardAuthMiddleware = User.guardMiddleware()
// 3
let protected = acronymsRoutes.grouped(
basicAuthMiddleware,
guardAuthMiddleware)
// 4
protected.post(use: createHandler)
下面是这个的作用:
- 创建一个
ModelAuthenticator
中间件的实例,它使用HTTP基本认证。由于User
符合ModelAuthenticatable
,这可以作为模型的一个静态方法。 - 创建一个
GuardAuthenticationMiddleware
的实例,确保请求包含认证的用户。 - 创建一个中间件组,使用
basicAuthMiddleware
和guardAuthMiddleware
。 - 通过这个中间件组将"创建首字母缩写"路径连接到
createHandler(_:acronym:)
。
中间件允许你在你的应用程序中拦截请求和响应。在这个例子中,basicAuthMiddleware
拦截了请求并验证了所提供的用户。你可以把中间件连锁起来。在上面的例子中,basicAuthMiddleware
认证了用户。然后guardAuthMiddleware
确保该请求包含一个认证的用户。如果没有认证的用户,guardAuthMiddleware
会抛出一个错误。你可以在第29章"中间件"中进一步了解中间件。
这确保只有使用HTTP
基本认证的请求才能创建缩略语。
接下来,删除以下内容,以删除未经认证的路由:
acronymsRoutes.post(use: createHandler)
建立并运行,然后启动RESTed
。创建一个新的请求,并进行如下配置:
- URL: http://localhost:8080/api/acronyms
- method: POST
- Parameter encoding: JSON-encoded
添加三个带有名称和值的参数:
- short: OMG
- long: Oh My God
- userID: The ID of the user created earlier
点击Send Request
,你会收到一个401 Unauthorized
的错误响应。你应该看到以下内容:
在RESTed
中,点击Authorization
,输入之前创建的用户的用户名和密码。勾选在认证挑战前呈现,然后点击OK
:
这就设置了上述的基本Authorization
头。再次点击Send Request
。这一次,请求成功了:
Token
认证¶
获得一个Token
¶
在这个阶段,只有经过认证的用户可以创建首字母缩写。然而,所有其他的破坏性路由仍然是不受保护的。要求用户在每次请求时都输入凭证是不现实的。你也不想在你的应用程序中的任何地方存储用户的密码,因为你必须以纯文本形式存储它。相反,你将允许用户登录到你的API
。当他们登录时,你将他们的凭证换成客户端可以保存的Token
。
在Sources/App/Models
创建一个新文件,Token.swift
。打开这个新文件,并添加以下内容:
import Vapor
import Fluent
final class Token: Model, Content {
static let schema = "tokens"
@ID
var id: UUID?
@Field(key: "value")
var value: String
@Parent(key: "userID")
var user: User
init() {}
init(id: UUID? = nil, value: String, userID: User.IDValue) {
self.id = id
self.value = value
self.$user.id = userID
}
}
这为Token
定义了一个模型,包含以下属性:
id
:模型的ID
。value
:提供给客户的token
字符串。user
:一个@Parent
字段,指向token
所有者的用户。
在Sources/App/Migrations
中为新模型创建一个迁移文件,CreateToken.swift
,并在下面插入迁移:
import Fluent
struct CreateToken: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("tokens")
.id()
.field("value", .string, .required)
.field(
"userID",
.uuid,
.required,
.references("users", "id", onDelete: .cascade))
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("tokens").delete()
}
}
和之前的其他迁移一样,这个迁移为Token
创建了一个表。它还为userID
字段创建了对User
的引用。这个引用被标记为级联删除,这样当你删除一个用户时,任何Token
都会被自动删除。在configure.swift
中,在app.migrations.add(CreateAcronymCategoryPivot())
之后添加以下内容:
app.migrations.add(CreateToken())
这样就把CreateToken``添加
到了迁移列表中,这样Vapor
就会在应用程序下次启动时创建该表。当一个用户登录时,应用程序必须为该用户创建一个token
。
打开Token.swift
,在文件的底部添加以下内容:
extension Token {
// 1
static func generate(for user: User) throws -> Token {
// 2
let random = [UInt8].random(count: 16).base64
// 3
return try Token(value: random, userID: user.requireID())
}
}
以下是这个扩展的作用:
- 定义一个静态方法来为用户生成一个
Token
。 - 生成
16
个随机字节作为Token
,并对其进行Base64
编码。 - 使用随机字节的
Base64
编码表示和用户的ID
创建一个Token
。
打开UsersController.swift
,在getAcronymsHandler(_:)
下添加以下内容:
// 1
func loginHandler(_ req: Request) throws
-> EventLoopFuture<Token> {
// 2
let user = try req.auth.require(User.self)
// 3
let token = try Token.generate(for: user)
// 4
return token.save(on: req.db).map { token }
}
下面是这个的作用:
- 定义一个用于登录用户的路由处理程序。
- 从请求中获取认证的用户。你将用
HTTP
基本认证中间件来保护这个路由。这将把用户的身份保存在请求的认证缓存中,允许你以后检索用户对象。req.auth.require(_:)
如果没有认证的用户,会抛出一个认证错误。 - 为用户创建一个
token
。 - 保存并返回
token
。
在boot(rouse:)
的底部添加以下内容:
// 1
let basicAuthMiddleware = User.authenticator()
let basicAuthGroup = usersRoute.grouped(basicAuthMiddleware)
// 2
basicAuthGroup.post("login", use: loginHandler)
下面是这个的作用:
- 使用
HTTP
基本认证创建一个受保护的路由组,就像你创建一个缩写一样。这没有使用GuardAuthenticationMiddleware
,因为req.auth.require(_:)
在用户没有被认证时抛出正确的错误。 - 通过受保护的组连接
/api/users/login
到loginHandler(_:)
。
建立并运行,然后回到RESTed
。
确保你已经配置了HTTP
基本认证,并将URL
设置为http://localhost:8080/api/users/login。
点击Send Request
,你会收到一个token
的回馈:
使用token
¶
打开Token.swift
,在文件的末尾添加以下内容:
// 1
extension Token: ModelTokenAuthenticatable {
// 2
static let valueKey = \Token.$value
// 3
static let userKey = \Token.$user
// 4
typealias User = App.User
// 5
var isValid: Bool {
true
}
}
下面是这个的作用:
- 使
Token
符合Vapor
的ModelTokenAuthenticatable
协议。这允许你将令牌用于HTTP
承载认证。 - 告诉
Vapor
价值键的关键路径,在这种情况下,Token
的value
预测值。 - 告诉
Vapor
用户键的路径,在此情况下,Token
的user
预测值。 - 告诉
Vapor
该用户是什么类型。 - 确定
token
是否有效。现在返回true
,但你可以在将来添加一个到期日或撤销的属性来检查。
Bearer authentication
是一种发送token
以验证请求的机制。它使用Authorization
头,就像HTTP
基本认证一样,但头看起来像Authorization:Bearer <TOKEN STRING>
。
目前,当用户创建首字母缩写时,他们必须在请求中发送他们的ID
。然而,由于你要求认证,你现在知道每个请求是哪个用户发送的。在AcronymsController.swift
中,从CreateAcronymData
中删除let userID: UUID
。接下来,在createHandler(_:)
中,替换:
let acronym = Acronym(
short: data.short,
long: data.long,
userID: data.userID)
为以下内容:
// 1
let user = try req.auth.require(User.self)
// 2
let acronym = try Acronym(
short: data.short,
long: data.long,
userID: user.requireID())
所做的改变是:
- 从请求中获取认证的用户。
- 使用来自请求和认证用户的数据创建一个新的
Acronym
。
接下来,将updateHandler(_:)
替换为以下内容:
func updateHandler(_ req: Request) throws
-> EventLoopFuture<Acronym> {
let updateData =
try req.content.decode(CreateAcronymData.self)
// 1
let user = try req.auth.require(User.self)
// 2
let userID = try user.requireID()
return Acronym
.find(req.parameters.get("acronymID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { acronym in
acronym.short = updateData.short
acronym.long = updateData.long
// 3
acronym.$user.id = userID
return acronym.save(on: req.db).map {
acronym
}
}
}
所做的改变是:
- 从请求中获取认证的用户。
- 从用户中获取用户
ID
。在这里做这个很有用,因为你不能扔在flatMap(_:)
里面。 - 将缩写的用户
ID
设置为上面步骤中的用户ID
。
最后,更新测试,使项目可以编译。打开AcronymTests.swift
。在testAcronymCanBeSavedWithAPI()
中,将let createAcronymData = ...
改为以下内容:
let createAcronymData =
CreateAcronymData(short: acronymShort, long: acronymLong)
这样就删除了userID
参数,因为它不再需要了。同时,删除let user = try User.create...
一行,因为它不再需要了。最后,在testUpdatingAnAcronym()
中,将let updatedAcronymData = ...
改为以下内容,以删除额外的userID
参数:
let updatedAcronymData =
CreateAcronymData(short: acronymShort, long: newLong)
返回到AcronymsController.swift
。在boot(routes:)
中,删除你之前用来保护"创建首字母缩写"路由的代码,用下面的内容代替它:
// 1
let tokenAuthMiddleware = Token.authenticator()
let guardAuthMiddleware = User.guardMiddleware()
// 2
let tokenAuthGroup = acronymsRoutes.grouped(
tokenAuthMiddleware,
guardAuthMiddleware)
// 3
tokenAuthGroup.post(use: createHandler)
以下是新代码的作用:
- 为
Token
创建一个ModelTokenAuthenticator
中间件。这将从请求中提取出承载token
,并将其转换为登录用户。 - 使用
tokenAuthMiddleware
和guardAuthMiddleware
创建一个路由组,以保护用token
认证创建缩写的路由。 - 通过这个中间件组,使用新的
AcronymCreateData
将"创建首字母缩写"路径连接到createHandler(_:data:)
。
建立并运行,然后回到RESTed
。复制从用户登录返回的token
值字符串。像这样配置一个请求:
- URL: http://localhost:8080/api/acronyms/
- method: POST
- Parameter encoding: JSON-encoded
添加两个带有名称和值的参数:
- short: IKR
- long: I Know Right
为Authorization
创建一个新的头域,其值为Bearer <TOKEN STRING>
,使用你先前复制的token
字符串。删除你用于登录的HTTP
基本认证凭证。
要做到这一点,请点击Authorization
,删除用户名和密码,并取消勾选在认证挑战前呈现。
点击Send Request
,你会看到创建的首字母缩写返回:
打开AcronymsController.swift
,找到boot(routes:)
,并删除以下几行:
acronymsRoutes.put(":acronymID", use: updateHandler)
acronymsRoutes.delete(":acronymID", use: deleteHandler)
acronymsRoutes.post(":acronymID", "categories", ":categoryID",
use: addCategoriesHandler)
acronymsRoutes.delete(":acronymID", "categories", ":categoryID",
use: removeCategoriesHandler)
这是所有不是get()
路由的原始路由。在boot(routes:)
的底部,添加它们的替换:
tokenAuthGroup.delete(":acronymID", use: deleteHandler)
tokenAuthGroup.put(":acronymID", use: updateHandler)
tokenAuthGroup.post(
":acronymID",
"categories",
":categoryID",
use: addCategoriesHandler)
tokenAuthGroup.delete(
":acronymID",
"categories",
":categoryID",
use: removeCategoriesHandler)
这确保只有经过认证的用户可以创建、编辑和删除缩写,并为缩写添加类别。未认证的用户仍然可以查看首字母缩写的细节。
现在,打开CategoriesController.swift
,在boot(routes:)
中,删除categoriesRoute.post(use: createHandler)
。
在该方法的结尾处用以下内容代替:
let tokenAuthMiddleware = Token.authenticator()
let guardAuthMiddleware = User.guardMiddleware()
let tokenAuthGroup = categoriesRoute.grouped(
tokenAuthMiddleware,
guardAuthMiddleware)
tokenAuthGroup.post(use: createHandler)
这使用了token
中间件来保护类别的创建,就像创建一个缩写一样,确保只有经过认证的用户才能创建类别。最后,打开UsersController.swift
,删除usersRoute.post(use: createHandler)
。在boot(route:)
的底部,添加以下内容:
let tokenAuthMiddleware = Token.authenticator()
let guardAuthMiddleware = User.guardMiddleware()
let tokenAuthGroup = usersRoute.grouped(
tokenAuthMiddleware,
guardAuthMiddleware)
tokenAuthGroup.post(use: createHandler)
同样,使用tokenAuthMiddleware
和guardAuthMiddleware
确保只有经过认证的用户才能创建其他用户。这可以防止任何人创建一个用户来向你刚刚保护的路由发送请求!
现在,所有可以执行"破坏性"操作的API
路由--也就是创建、编辑或删除资源--都受到了保护。对于这些操作,应用程序只接受来自认证用户的请求。
数据库播种¶
在这一点上,API
是安全的,但现在还有一个问题。当你部署你的应用程序,或者下一次恢复数据库的时候,你的数据库中就没有任何用户了。
但是,你不能创建一个新的用户,因为那条路由需要认证! 解决这个问题的一个方法是,在应用程序第一次启动时,对数据库进行播种并创建一个用户。在Vapor
中,你可以通过迁移来做到这一点。
在Sources/App/Migrations
中创建一个新文件,CreateAdminUser.swift
。打开这个新文件,添加以下内容:
import Fluent
import Vapor
// 1
struct CreateAdminUser: Migration {
// 2
func prepare(on database: Database) -> EventLoopFuture<Void> {
// 3
let passwordHash: String
do {
passwordHash = try Bcrypt.hash("password")
} catch {
return database.eventLoop.future(error: error)
}
// 4
let user = User(
name: "Admin",
username: "admin",
password: passwordHash)
// 5
return user.save(on: database)
}
// 6
func revert(on database: Database) -> EventLoopFuture<Void> {
// 7
User.query(on: database)
.filter(\.$username == "admin")
.delete()
}
}
下面是这个的作用:
- 定义一个符合
Migration
的新类型。 - 实现所需的
prepare(on:)
。 - 从密码中创建一个密码哈希值。捕获任何抛出的错误并返回一个失败的未来。
- 创建一个新的用户,名字是
Admin
,用户名是admin
,密码是哈希值。 - 保存该用户并返回。
- 执行所需的
revert(on:)
。 - 查询
User
并删除所有用户名与admin
相匹配的行。因为用户名必须是唯一的,所以这只删除了一个管理员行。
Note
显然,在一个生产系统中,你不应该使用password
作为你的管理用户的密码!你也不希望硬编码,以防它在源码控制中出现。你也不希望硬编码密码,以防它最终出现在源代码控制中。你可以读取一个环境变量或者生成一个随机密码并打印出来。
打开configure.swift
,在app.migrations.add(CreateToken())
之后添加以下内容:
app.migrations.add(CreateAdminUser())
这将把CreateAdminUser
添加到迁移列表中,以便应用程序在下次启动时执行迁移。
建立并运行。前往RESTed
,试用所有新保护的路由。你甚至可以用新的管理员用户来登录。
接下来去哪?¶
在本章中,你了解了HTTP
基本认证和承载认证。你看到了认证中间件是如何简化你的代码并为你做很多繁重的工作的。你看到了如何修改你现有的模型,使之与Vapor
的认证功能一起工作。你把这一切粘在一起,把认证添加到你的API
。
但是,还有很多事情要做。翻过这一页,忙着更新你的测试套件和你的iOS应用,使其与新的认证功能一起工作。