08 · 安全

安全能力在 ace-security module(import aceboot::security.*)。框架内核不内置安全——安全是「第三方关注点」,经中间件接入。

所有 guard 中间件(jwtAuth/csrf/rateLimit 等)失败时抛 HttpError(4xx)前提是 App 已 onError(jsonErrorHandler)AceApplication.buildApp 默认已接),否则 401/403 会退化成 500。

JWT

HS256(HMAC-SHA256)签发与校验:

public class Claims {
    public let sub: String              // 主体(用户标识)
    public let roles: Array<String>     // 角色
    public let exp: Int64               // Unix 秒,0 表示不过期
    public init(sub: String, roles: Array<String>, exp: Int64)
}

signJwt(claims: Claims, secret: String): String              // 签发
verifyJwt(token: String, secret: String): ?Claims            // 校验(不验 exp)
verifyJwtAt(token: String, secret: String, nowSec: Int64): ?Claims  // 校验 + 验 exp

密钥不内置,从配置读取:

let secret = appConfig.getOr("jwt.secret", "change-me")
let token = signJwt(Claims(userId, ["admin"], 0), secret)

认证 / 授权 guard 中间件

jwtAuth(secret: String): Middleware       // 从 Authorization: Bearer <token> 验签,通过则把 Principal 写入 ctx.state
authenticated(): Middleware                // 要求已认证,否则 401
requireRoles(roles: Array<String>): Middleware  // 要求拥有指定角色之一(OR),否则 403

当前用户(Principal)

public class Principal {
    public let sub: String
    public let roles: Array<String>
    public func hasRole(role: String): Bool
}

currentPrincipal(ctx: Context): ?Principal    // 读取已认证主体(jwtAuth 写入),未认证返回 None
@Post["/"]
public func create(ctx: Context, body: CreateTask): TaskView {
    let owner = match (currentPrincipal(ctx)) {
        case Some(p) => p.sub
        case None => ""
    }
    tasks.create(body.title, owner)
}

路由级声明式授权

authorize(policies) 按策略对路由要求角色,与控制器解耦:

public struct RolePolicy {
    public init(method: String, pathPrefix: String, roles: Array<String>)  // method 用 "*" 通配
}

authorize(policies: Array<RolePolicy>): Middleware
  • 命中的多条策略全部要求(AND);单条策略内 roles之一(OR)。
  • 无 Principal → 401;有但角色不匹配 → 403。

完整示例(认证 + 授权,声明式注册)

@Middleware(见 10 章)声明式接入:

package myapp.middleware
import aceboot::framework.*
import aceboot::framework_macros.*
import aceboot::security.*

// 认证:登录端点放行,其余要求合法 JWT
@Middleware[100]
public func authentication(): Middleware {
    let auth = jwtAuth(appConfig.getOr("jwt.secret", "change-me"))
    return {ctx, next =>
        if (ctx.path == "/api/auth/login") { next() } else { auth(ctx, next) }
    }
}

// 授权:声明式角色策略
@Middleware[110]
public func authorization(): Middleware {
    authorize([
        RolePolicy("GET", "/api/admin", ["admin"]),       // 管理端点仅 admin
        RolePolicy("*",   "/api",       ["user", "admin"]) // 其余 API 需 user/admin
    ])
}

登录端点签发 token:

@Controller["/api/auth"]
public class AuthController {
    @AppConfig var config: Config

    @Post["/login"]
    public func login(body: LoginRequest): TokenResponse {
        // 校验凭据(略)...
        let token = signJwt(Claims(body.username, ["user"], 0),
                            config.getOr("jwt.secret", "change-me"))
        let r = TokenResponse(); r.token = token; r
    }
}

安全头 / CSRF / 限流

securityHeaders(): Middleware    // X-Content-Type-Options/X-Frame-Options/Referrer-Policy 等安全响应头
csrf(): Middleware                // 双提交 cookie 防护:安全方法下发 ace_csrf cookie,变更方法比对 X-CSRF-Token

限流是可插拔组件,import aceboot::security.* 即按配置自动装配(无需手写):

[ratelimit]
  enabled = true
  max = 100          # 窗口内最大请求数
  windowMs = 60000   # 窗口毫秒

超额抛 HttpError(429, "rate limit exceeded")。也可手动用 rateLimit(max, windowMs, nowMs).use()/@Middleware 注册。

下一章:ORM 数据访问