feat: add 7 built-in middleware components inspired by Hono.js

New Middleware:
- CORS: Cross-Origin Resource Sharing with full configuration support
- Cookie Helper: Cookie management with signed cookie support (HMAC-SHA256)
- JWT: JSON Web Token authentication (HS256/384/512)
- Bearer Auth: Simple Bearer token authentication
- Compression: Gzip and deflate response compression
- Rate Limiting: Request rate limiting with customizable stores
- Validator: Schema-based request validation

Features:
- Context store for middleware data sharing (get/set/get_client_ip)
- Middleware export module with utility functions
- Comprehensive property-based tests for all middleware
- Example file demonstrating all middleware usage

Files Added:
- cors.v, cookie.v, jwt.v, bearer_auth.v, compress.v, rate_limit.v, validator.v
- middleware.v (unified exports)
- examples/middleware_demo.v
- tests/test_*.v (9 new test files)

Documentation:
- Updated README.md with detailed middleware usage guide
- Added Schema Builder API documentation
This commit is contained in:
meiseayoung
2026-01-04 11:11:15 +08:00
parent 44ba82c194
commit 4e2c6a90c3
23 changed files with 7156 additions and 89 deletions
+600
View File
@@ -0,0 +1,600 @@
module hono
import crypto.sha256
import crypto.sha512
import encoding.base64
import net.http
import time
import x.json2
// JwtAlgorithm 枚举 - 支持的 JWT 签名算法
pub enum JwtAlgorithm {
hs256
hs384
hs512
}
// JwtVerifyOptions 结构体 - JWT 验证选项
pub struct JwtVerifyOptions {
pub:
iss string // 发行者验证
exp bool = true // 验证过期时间
nbf bool = true // 验证生效时间
iat bool = true // 验证签发时间
}
// JwtOptions 结构体 - JWT 中间件配置选项
pub struct JwtOptions {
pub:
secret string // 密钥(必需)
alg JwtAlgorithm = .hs256 // 算法
cookie string // 从 cookie 读取 token
header_name string = 'Authorization' // 请求头名称
verify_options JwtVerifyOptions = JwtVerifyOptions{}
}
// JwtPayload 结构体 - JWT 载荷
pub struct JwtPayload {
pub mut:
sub string // Subject
iss string // Issuer
aud string // Audience
exp i64 // Expiration Time (Unix timestamp)
nbf i64 // Not Before (Unix timestamp)
iat i64 // Issued At (Unix timestamp)
jti string // JWT ID
claims map[string]string // 自定义声明
}
// JwtHeader 结构体 - JWT 头部
struct JwtHeader {
alg string
typ string
}
// JwtToken 结构体 - 完整的 JWT Token
struct JwtToken {
header JwtHeader
payload JwtPayload
signature string
}
// base64url_encode - Base64URL 编码(不带填充)
fn base64url_encode(data []u8) string {
encoded := base64.encode(data)
// 转换为 URL 安全格式并移除填充
return encoded.replace('+', '-').replace('/', '_').trim_right('=')
}
// base64url_decode - Base64URL 解码
fn base64url_decode(data string) ![]u8 {
// 转换回标准 Base64 格式
mut standard := data.replace('-', '+').replace('_', '/')
// 添加填充
padding := (4 - standard.len % 4) % 4
for _ in 0 .. padding {
standard += '='
}
return base64.decode(standard)
}
// hmac_sha256 - HMAC-SHA256 签名
fn hmac_sha256(message []u8, secret []u8) []u8 {
block_size := 64 // SHA256 块大小
// 处理密钥
mut key := secret.clone()
if key.len > block_size {
// 如果密钥太长,先哈希
hash_result := sha256.sum(key)
key = []u8{len: 32}
for i := 0; i < 32; i++ {
key[i] = hash_result[i]
}
}
// 填充到块大小
for key.len < block_size {
key << u8(0)
}
// 计算 K' ⊕ ipad
mut i_key_pad := []u8{len: block_size}
for i := 0; i < block_size; i++ {
i_key_pad[i] = key[i] ^ u8(0x36)
}
// 计算 K' ⊕ opad
mut o_key_pad := []u8{len: block_size}
for i := 0; i < block_size; i++ {
o_key_pad[i] = key[i] ^ u8(0x5c)
}
// 计算内部哈希: H((K' ⊕ ipad) || m)
mut inner_data := i_key_pad.clone()
inner_data << message
inner_hash_result := sha256.sum(inner_data)
mut inner_hash := []u8{len: 32}
for i := 0; i < 32; i++ {
inner_hash[i] = inner_hash_result[i]
}
// 计算外部哈希: H((K' ⊕ opad) || inner_hash)
mut outer_data := o_key_pad.clone()
outer_data << inner_hash
outer_hash_result := sha256.sum(outer_data)
mut result := []u8{len: 32}
for i := 0; i < 32; i++ {
result[i] = outer_hash_result[i]
}
return result
}
// hmac_sha384 - HMAC-SHA384 签名
fn hmac_sha384(message []u8, secret []u8) []u8 {
block_size := 128 // SHA384 块大小
// 处理密钥
mut key := secret.clone()
if key.len > block_size {
// 如果密钥太长,先哈希
hash_result := sha512.sum384(key)
key = []u8{len: 48}
for i := 0; i < 48; i++ {
key[i] = hash_result[i]
}
}
// 填充到块大小
for key.len < block_size {
key << u8(0)
}
// 计算 K' ⊕ ipad
mut i_key_pad := []u8{len: block_size}
for i := 0; i < block_size; i++ {
i_key_pad[i] = key[i] ^ u8(0x36)
}
// 计算 K' ⊕ opad
mut o_key_pad := []u8{len: block_size}
for i := 0; i < block_size; i++ {
o_key_pad[i] = key[i] ^ u8(0x5c)
}
// 计算内部哈希
mut inner_data := i_key_pad.clone()
inner_data << message
inner_hash_result := sha512.sum384(inner_data)
mut inner_hash := []u8{len: 48}
for i := 0; i < 48; i++ {
inner_hash[i] = inner_hash_result[i]
}
// 计算外部哈希
mut outer_data := o_key_pad.clone()
outer_data << inner_hash
outer_hash_result := sha512.sum384(outer_data)
mut result := []u8{len: 48}
for i := 0; i < 48; i++ {
result[i] = outer_hash_result[i]
}
return result
}
// hmac_sha512 - HMAC-SHA512 签名
fn hmac_sha512(message []u8, secret []u8) []u8 {
block_size := 128 // SHA512 块大小
// 处理密钥
mut key := secret.clone()
if key.len > block_size {
// 如果密钥太长,先哈希
hash_result := sha512.sum512(key)
key = []u8{len: 64}
for i := 0; i < 64; i++ {
key[i] = hash_result[i]
}
}
// 填充到块大小
for key.len < block_size {
key << u8(0)
}
// 计算 K' ⊕ ipad
mut i_key_pad := []u8{len: block_size}
for i := 0; i < block_size; i++ {
i_key_pad[i] = key[i] ^ u8(0x36)
}
// 计算 K' ⊕ opad
mut o_key_pad := []u8{len: block_size}
for i := 0; i < block_size; i++ {
o_key_pad[i] = key[i] ^ u8(0x5c)
}
// 计算内部哈希
mut inner_data := i_key_pad.clone()
inner_data << message
inner_hash_result := sha512.sum512(inner_data)
mut inner_hash := []u8{len: 64}
for i := 0; i < 64; i++ {
inner_hash[i] = inner_hash_result[i]
}
// 计算外部哈希
mut outer_data := o_key_pad.clone()
outer_data << inner_hash
outer_hash_result := sha512.sum512(outer_data)
mut result := []u8{len: 64}
for i := 0; i < 64; i++ {
result[i] = outer_hash_result[i]
}
return result
}
// sign_message - 根据算法签名消息
fn sign_message(message []u8, secret []u8, alg JwtAlgorithm) []u8 {
return match alg {
.hs256 { hmac_sha256(message, secret) }
.hs384 { hmac_sha384(message, secret) }
.hs512 { hmac_sha512(message, secret) }
}
}
// alg_to_string - 将算法枚举转换为字符串
fn alg_to_string(alg JwtAlgorithm) string {
return match alg {
.hs256 { 'HS256' }
.hs384 { 'HS384' }
.hs512 { 'HS512' }
}
}
// string_to_alg - 将字符串转换为算法枚举
fn string_to_alg(s string) !JwtAlgorithm {
return match s {
'HS256' { JwtAlgorithm.hs256 }
'HS384' { JwtAlgorithm.hs384 }
'HS512' { JwtAlgorithm.hs512 }
else { error('Unsupported algorithm: ${s}') }
}
}
// sign_jwt - 创建并签名 JWT token
// 返回格式: header.payload.signature
pub fn sign_jwt(payload JwtPayload, secret string, alg JwtAlgorithm) !string {
if secret.len == 0 {
return error('Secret is required')
}
// 构建 header
header := JwtHeader{
alg: alg_to_string(alg)
typ: 'JWT'
}
// 编码 header
header_json := '{"alg":"${header.alg}","typ":"${header.typ}"}'
header_encoded := base64url_encode(header_json.bytes())
// 编码 payload
payload_json := encode_payload(payload)
payload_encoded := base64url_encode(payload_json.bytes())
// 创建签名输入
signing_input := '${header_encoded}.${payload_encoded}'
// 签名
signature := sign_message(signing_input.bytes(), secret.bytes(), alg)
signature_encoded := base64url_encode(signature)
return '${signing_input}.${signature_encoded}'
}
// encode_payload - 将 JwtPayload 编码为 JSON 字符串
fn encode_payload(payload JwtPayload) string {
mut parts := []string{}
if payload.sub.len > 0 {
parts << '"sub":"${escape_json_string(payload.sub)}"'
}
if payload.iss.len > 0 {
parts << '"iss":"${escape_json_string(payload.iss)}"'
}
if payload.aud.len > 0 {
parts << '"aud":"${escape_json_string(payload.aud)}"'
}
if payload.exp != 0 {
parts << '"exp":${payload.exp}'
}
if payload.nbf != 0 {
parts << '"nbf":${payload.nbf}'
}
if payload.iat != 0 {
parts << '"iat":${payload.iat}'
}
if payload.jti.len > 0 {
parts << '"jti":"${escape_json_string(payload.jti)}"'
}
// 添加自定义声明
for key, value in payload.claims {
parts << '"${escape_json_string(key)}":"${escape_json_string(value)}"'
}
return '{${parts.join(",")}}'
}
// escape_json_string - 转义 JSON 字符串中的特殊字符
fn escape_json_string(s string) string {
mut result := []u8{}
for c in s.bytes() {
match c {
`"` { result << `\\`; result << `"` }
`\\` { result << `\\`; result << `\\` }
`\n` { result << `\\`; result << `n` }
`\r` { result << `\\`; result << `r` }
`\t` { result << `\\`; result << `t` }
else { result << c }
}
}
return result.bytestr()
}
// decode_jwt - 解码 JWT token(不验证签名)
// 返回解码后的 payload
pub fn decode_jwt(token string) !JwtPayload {
parts := token.split('.')
if parts.len != 3 {
return error('Invalid token format')
}
// 解码 payload
payload_bytes := base64url_decode(parts[1])!
payload_json := payload_bytes.bytestr()
return parse_payload(payload_json)
}
// parse_payload - 解析 JSON payload 为 JwtPayload 结构体
fn parse_payload(json_str string) !JwtPayload {
raw := json2.decode[json2.Any](json_str) or {
return error('Invalid JSON payload: ${err}')
}
obj := raw.as_map()
mut payload := JwtPayload{
claims: map[string]string{}
}
// 解析标准声明
if 'sub' in obj {
payload.sub = obj['sub'] or { json2.Any('') }.str()
}
if 'iss' in obj {
payload.iss = obj['iss'] or { json2.Any('') }.str()
}
if 'aud' in obj {
payload.aud = obj['aud'] or { json2.Any('') }.str()
}
if 'exp' in obj {
payload.exp = (obj['exp'] or { json2.Any(0) }).i64()
}
if 'nbf' in obj {
payload.nbf = (obj['nbf'] or { json2.Any(0) }).i64()
}
if 'iat' in obj {
payload.iat = (obj['iat'] or { json2.Any(0) }).i64()
}
if 'jti' in obj {
payload.jti = obj['jti'] or { json2.Any('') }.str()
}
// 解析自定义声明
standard_claims := ['sub', 'iss', 'aud', 'exp', 'nbf', 'iat', 'jti']
for key, value in obj {
if key !in standard_claims {
payload.claims[key] = value.str()
}
}
return payload
}
// verify_jwt - 验证 JWT token 并返回 payload
// 验证签名、过期时间、生效时间等
pub fn verify_jwt(token string, secret string, alg JwtAlgorithm) !JwtPayload {
if secret.len == 0 {
return error('Secret is required')
}
parts := token.split('.')
if parts.len != 3 {
return error('Invalid token format')
}
// 解码 header 并验证算法
header_bytes := base64url_decode(parts[0])!
header_json := header_bytes.bytestr()
header_raw := json2.decode[json2.Any](header_json) or {
return error('Invalid header JSON: ${err}')
}
header_obj := header_raw.as_map()
token_alg_str := (header_obj['alg'] or { json2.Any('') }).str()
token_alg := string_to_alg(token_alg_str)!
// 验证算法一致性
if token_alg != alg {
return error('Algorithm mismatch: expected ${alg_to_string(alg)}, got ${token_alg_str}')
}
// 验证签名
signing_input := '${parts[0]}.${parts[1]}'
expected_signature := sign_message(signing_input.bytes(), secret.bytes(), alg)
expected_signature_encoded := base64url_encode(expected_signature)
if !constant_time_compare_jwt(parts[2], expected_signature_encoded) {
return error('Invalid signature')
}
// 解码 payload
payload := decode_jwt(token)!
return payload
}
// verify_jwt_with_options - 带选项的 JWT 验证
// 验证签名、过期时间、生效时间、发行者等
pub fn verify_jwt_with_options(token string, secret string, alg JwtAlgorithm, options JwtVerifyOptions) !JwtPayload {
// 先进行基本验证
payload := verify_jwt(token, secret, alg)!
now := time.now().unix()
// 验证过期时间
if options.exp && payload.exp != 0 {
if now > payload.exp {
return error('Token expired')
}
}
// 验证生效时间
if options.nbf && payload.nbf != 0 {
if now < payload.nbf {
return error('Token not yet valid')
}
}
// 验证发行者
if options.iss.len > 0 {
if payload.iss != options.iss {
return error('Invalid issuer: expected ${options.iss}, got ${payload.iss}')
}
}
return payload
}
// constant_time_compare_jwt - 常量时间比较,防止时序攻击
fn constant_time_compare_jwt(a string, b string) bool {
if a.len != b.len {
return false
}
mut result := u8(0)
for i := 0; i < a.len; i++ {
result |= a[i] ^ b[i]
}
return result == 0
}
// jwt - JWT 中间件工厂函数
// 返回一个 ContextMiddleware,用于验证 JWT token
pub fn jwt_middleware(options JwtOptions) ContextMiddleware {
return fn [options] (mut c Context, next fn (mut Context) http.Response) http.Response {
// 获取 token
token := get_jwt_token(c, options) or {
c.status(401)
return c.json('{"error":"Unauthorized","message":"${err}"}')
}
// 验证 token
payload := verify_jwt_with_options(token, options.secret, options.alg, options.verify_options) or {
c.status(401)
return c.json('{"error":"Unauthorized","message":"${err}"}')
}
// 将 payload 存储到 Context
store_jwt_payload(mut c, payload)
// 继续处理请求
return next(mut c)
}
}
// get_jwt_token - 从请求中获取 JWT token
fn get_jwt_token(c Context, options JwtOptions) !string {
// 优先从 Cookie 读取
if options.cookie.len > 0 {
if cookie_token := get_cookie(c, options.cookie) {
return cookie_token
}
}
// 从 header 读取
header_value := c.req.header.get_custom(options.header_name) or {
return error('Missing authorization header')
}
if header_value.len == 0 {
return error('Missing authorization header')
}
// 解析 Bearer token
if header_value.starts_with('Bearer ') {
token := header_value[7..].trim_space()
if token.len == 0 {
return error('Invalid token format')
}
return token
}
// 如果不是 Bearer 格式,直接返回整个值
return header_value
}
// store_jwt_payload - 将 JWT payload 存储到 Context
fn store_jwt_payload(mut c Context, payload JwtPayload) {
// 存储标准声明
if payload.sub.len > 0 {
c.set('jwt_sub', payload.sub)
}
if payload.iss.len > 0 {
c.set('jwt_iss', payload.iss)
}
if payload.aud.len > 0 {
c.set('jwt_aud', payload.aud)
}
if payload.exp != 0 {
c.set('jwt_exp', payload.exp.str())
}
if payload.nbf != 0 {
c.set('jwt_nbf', payload.nbf.str())
}
if payload.iat != 0 {
c.set('jwt_iat', payload.iat.str())
}
if payload.jti.len > 0 {
c.set('jwt_jti', payload.jti)
}
// 存储自定义声明
for key, value in payload.claims {
c.set('jwt_${key}', value)
}
// 存储完整的 payload JSON(便于获取)
c.set('jwt_payload', encode_payload(payload))
}
// get_jwt_payload - 从 Context 获取 JWT payload
// 这是一个便捷方法,用于在 handler 中获取已验证的 JWT payload
pub fn get_jwt_payload(c Context) ?JwtPayload {
payload_json := c.get('jwt_payload') or { return none }
payload := parse_payload(payload_json) or { return none }
return payload
}
// get_jwt_claim - 从 Context 获取单个 JWT 声明
pub fn get_jwt_claim(c Context, key string) ?string {
return c.get('jwt_${key}')
}