认证 vs 授权:概念区分
在安全领域,认证(Authentication)和授权(Authorization)是两个经常被混淆的概念,理解区别是构建安全系统的前提。
认证 (Authentication)
- 回答"你是谁?"
- 验证身份的真实性
- 典型手段:密码、JWT、OAuth
- 结果:当前请求者是用户 Alice
- Vapor 中:Authenticator 中间件
授权 (Authorization)
- 回答"你能做什么?"
- 检查操作的权限
- 典型手段:RBAC、ACL、策略
- 结果:Alice 可以编辑自己的文章
- Vapor 中:Guard 中间件或业务逻辑
核心概念词典
Authenticatable
Vapor 认证框架的基础协议,Model 实现此协议后才能被认证中间件识别为"可认证的实体"。通常还会搭配
ModelAuthenticatable(密码认证)或 ModelTokenAuthenticatable(Token 认证)使用。BearerAuthenticator
从请求头的
Authorization: Bearer <token> 中提取令牌并验证的认证器。实现 AsyncBearerAuthenticator 协议,在 authenticate(bearer:for:) 方法中执行验证逻辑(如解析 JWT、查询数据库)。JWT (JSON Web Token)
一种紧凑的自包含令牌格式,由三部分组成:Header(算法)、Payload(声明/数据)、Signature(签名)。服务端签发后发给客户端,客户端每次请求携带 JWT,服务端验证签名即可确认身份,无需查询数据库。
JWTPayload
JWT 载荷的 Swift 类型表示,实现
JWTPayload 协议。常用声明:sub(Subject,用户 ID)、exp(Expiration,过期时间)、iat(Issued At,签发时间)。自定义载荷可以添加任意业务字段(如 role、email)。JWTSigner
JWT 签名算法的配置,Vapor 支持:HS256/HS384/HS512(对称 HMAC,需要密钥),RS256/RS384(非对称 RSA,需要公私钥对),ES256(椭圆曲线,更安全高效)。对称算法简单,非对称算法可以分离签发和验证服务。
完整认证流程
注册/登录流程:
Client Vapor Server Database
│ │ │
│── POST /register {name, email, pw}→│ │
│ │── hash(password) ──────── │
│ │── INSERT user ──────────→ │
│ │←─ user saved ─────────── │
│←── 201 {user} ─────────────────── │ │
│ │ │
│── POST /login {email, password} ──→│ │
│ │── SELECT user by email ──→│
│ │←─ user row ──────────────│
│ │── verify(password, hash) │
│ │── sign JWT(sub=userID) │
│←── 200 {token: "eyJhbG..."} ────── │ │
已认证请求流程:
│── GET /profile │ │
│ Authorization: Bearer eyJhbG... →│ │
│ │── JWTAuthenticator │
│ │ verify(token signature) │
│ │ decode payload │
│ │ req.auth.login(user) │
│ │── handler logic │
│←── 200 {profile} ─────────────────│ │
实现 JWT 认证系统
1. 定义 JWT Payload
// Sources/App/Models/UserPayload.swift
import JWT
import Vapor
struct UserPayload: JWTPayload, Authenticatable {
// 标准声明
var sub: SubjectClaim // 用户 ID
var exp: ExpirationClaim // 过期时间
var iat: IssuedAtClaim // 签发时间
// 自定义声明
var email: String
var role: String // "user" | "admin"
// 验证声明(框架调用)
func verify(using signer: JWTSigner) throws {
try exp.verifyNotExpired()
}
}
extension UserPayload {
// 便捷构造器
init(user: User, expiresIn seconds: Int = 86400) throws {
let now = Date()
self.sub = .init(value: user.id!.uuidString)
self.exp = .init(value: now.addingTimeInterval(Double(seconds)))
self.iat = .init(value: now)
self.email = user.email
self.role = user.role
}
// 从 sub 提取用户 UUID
var userID: UUID {
get throws {
guard let uuid = UUID(uuidString: sub.value) else {
throw Abort(.unauthorized)
}
return uuid
}
}
}
2. AuthController:注册与登录
// Sources/App/Controllers/AuthController.swift
import Vapor
import JWT
import Fluent
struct AuthController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let auth = routes.grouped("auth")
auth.post("register", use: register)
auth.post("login", use: login)
auth.post("refresh", use: refresh)
}
// POST /auth/register
func register(req: Request) async throws -> UserResponse {
let dto = try req.content.decode(CreateUserDTO.self)
try CreateUserDTO.validate(content: req) // Vapor Validations
let existing = try await User.query(on: req.db)
.filter(\.$email == dto.email).first()
guard existing == nil else {
throw Abort(.conflict, reason: "Email already registered")
}
let user = User(
name: dto.name,
email: dto.email.lowercased(),
passwordHash: try Bcrypt.hash(dto.password)
)
try await user.save(on: req.db)
return UserResponse(from: user)
}
// POST /auth/login
func login(req: Request) async throws -> TokenResponse {
let dto = try req.content.decode(LoginDTO.self)
guard let user = try await User.query(on: req.db)
.filter(\.$email == dto.email.lowercased()).first() else {
throw Abort(.unauthorized, reason: "Invalid credentials")
}
guard try Bcrypt.verify(dto.password, created: user.passwordHash) else {
throw Abort(.unauthorized, reason: "Invalid credentials")
}
// 签发 JWT
let payload = try UserPayload(user: user)
let token = try req.jwt.sign(payload)
return TokenResponse(token: token, expiresIn: 86400)
}
}
3. 配置 JWT 密钥
// configure.swift
import JWT
public func configure(_ app: Application) async throws {
// JWT 密钥从环境变量读取
guard let jwtSecret = Environment.get("JWT_SECRET") else {
throw Abort(.internalServerError, reason: "JWT_SECRET environment variable not set")
}
app.jwt.signers.use(.hs256(key: jwtSecret))
}
4. 保护路由
// JWT 认证器:验证 Bearer Token
struct JWTAuthenticator: AsyncBearerAuthenticator {
func authenticate(bearer: BearerAuthorization, for req: Request) async throws {
let payload = try req.jwt.verify(bearer.token, as: UserPayload.self)
// 将 payload 存入 request,供后续处理函数使用
req.auth.login(payload)
}
}
// routes.swift — 路由保护
func routes(_ app: Application) throws {
// 公开路由
try app.routes.grouped("auth").register(collection: AuthController())
// 需要认证的路由(JWT 验证失败自动返回 401)
let protected = app.routes
.grouped(JWTAuthenticator())
.grouped(UserPayload.guardMiddleware())
// 在处理函数中获取当前用户
protected.get("profile") { req async throws -> UserResponse in
let payload = try req.auth.require(UserPayload.self)
guard let user = try await User.find(try payload.userID, on: req.db) else {
throw Abort(.notFound)
}
return UserResponse(from: user)
}
}
JWT Secret 安全警告
JWT 密钥(secret)绝对不能硬编码在源代码中,不能提交到 Git 仓库。必须通过环境变量或密钥管理服务(如 AWS Secrets Manager、HashiCorp Vault)注入。一旦 secret 泄漏,攻击者可以伪造任意用户的 JWT,整个认证体系将完全失效。
本章小结
Vapor 的认证框架通过 Authenticator 协议将认证逻辑与路由处理解耦;JWT 提供了无状态的身份验证方案,适合分布式服务;guardMiddleware 确保未认证请求在到达处理函数之前就被拦截。下一章将学习中间件的洋葱执行模型,了解如何在请求的任意阶段插入横切逻辑。