Skip to content

JWT 中间件

该中间件增强了在 Vafast 处理程序中使用 JWT 的支持。

安装

安装命令:

bash
bun add @vafast/jwt

基本用法

typescript
import { Server, createHandler } from 'vafast'
import { jwt } from '@vafast/jwt'

// 创建 JWT 中间件
const jwtMiddleware = jwt({
    name: 'jwt',
    secret: 'your-secret-key-here',
    sub: 'auth',
    iss: 'your-domain.com',
    exp: '7d'
})

// 定义路由
const routes = [
    {
        method: 'GET',
        path: '/sign/:name',
        handler: createHandler(async ({ req, params }: { req: Request, params: Record<string, string> }) => {
            // 应用 JWT 中间件
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const name = params.name
            
            // 创建 JWT 令牌
            const token = await (req as any).jwt.sign({ name })
            
            return {
                data: `Sign in as ${name}`,
                headers: {
                    'Set-Cookie': `auth=${token}; HttpOnly; Max-Age=${7 * 86400}; Path=/`
                }
            }
        })
    },
    {
        method: 'GET',
        path: '/profile',
        handler: createHandler(async ({ req }: { req: Request }) => {
            // 应用 JWT 中间件
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            // 从 Cookie 中获取令牌
            const cookies = req.headers.get('cookie')
            const authCookie = cookies?.split(';').find((c) => c.trim().startsWith('auth='))
            const token = authCookie?.split('=')[1]
            
            // 验证 JWT 令牌
            const profile = await (req as any).jwt.verify(token)
            
            if (!profile) {
                return {
                    status: 401,
                    data: 'Unauthorized'
                }
            }
            
            return { message: `Hello ${profile.name}` }
        })
    }
]

// 创建服务器
const server = new Server(routes)

// 导出 fetch 函数
export default {
    fetch: (req: Request) => server.fetch(req)
}

配置选项

该中间件扩展了 jose 的配置。

JWTOption

typescript
interface JWTOption<Name extends string | undefined = 'jwt', Schema extends TSchema | undefined = undefined> {
    /** 注册方法的名称,默认为 'jwt' */
    name?: Name
    
    /** JWT 密钥 */
    secret: string | Uint8Array | JWK
    
    /** JWT 负载的严格类型验证 */
    schema?: Schema
    
    /** 签名算法,默认为 'HS256' */
    alg?: string
    
    /** JWT 类型,默认为 'JWT' */
    typ?: string
    
    /** 发行者声明 */
    iss?: string
    
    /** 主体声明 */
    sub?: string
    
    /** 受众声明 */
    aud?: string | string[]
    
    /** JWT ID 声明 */
    jti?: string
    
    /** "未生效" 声明 */
    nbf?: string | number
    
    /** 过期时间声明 */
    exp?: string | number
    
    /** "签发时间" 声明 */
    iat?: boolean
    
    /** 其他 jose 支持的头部参数 */
    b64?: true
    crit?: string[]
    kid?: string
    x5t?: string
    x5c?: string[]
    x5u?: string
    jku?: string
    jwk?: JWK
    cty?: string
}

支持的算法

中间件支持以下签名算法:

  • HS256, HS384, HS512: HMAC 算法
  • PS256, PS384, PS512: RSA-PSS 算法
  • RS256, RS384, RS512: RSA 算法
  • ES256, ES256K, ES384, ES512: ECDSA 算法
  • EdDSA: Edwards-curve 数字签名算法

使用模式

1. 基本 JWT 认证

typescript
import { Server, createHandler } from 'vafast'
import { jwt } from '@vafast/jwt'

const jwtMiddleware = jwt({
    name: 'jwt',
    secret: 'your-secret-key',
    exp: '1h',
    iss: 'your-app.com'
})

const routes = [
    {
        method: 'POST',
        path: '/login',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const body = await req.json()
            const { username, password } = body
            
            // 验证用户凭据
            if (username === 'admin' && password === 'password') {
                const token = await (req as any).jwt.sign({
                    username,
                    role: 'admin',
                    id: 1
                })
                
                return {
                    message: 'Login successful',
                    token,
                    user: { username, role: 'admin' }
                }
            } else {
                return {
                    status: 401,
                    message: 'Invalid credentials'
                }
            }
        })
    },
    {
        method: 'GET',
        path: '/protected',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const authHeader = req.headers.get('authorization')
            const token = authHeader?.replace('Bearer ', '')
            
            if (!token) {
                return {
                    status: 401,
                    message: 'No token provided'
                }
            }
            
            const payload = await (req as any).jwt.verify(token)
            
            if (!payload) {
                return {
                    status: 401,
                    message: 'Invalid token'
                }
            }
            
            return {
                message: 'Access granted',
                user: payload
            }
        })
    }
]

const server = new Server(routes)
export default { fetch: (req: Request) => server.fetch(req) }

2. 带类型验证的 JWT

typescript
import { Server, createHandler } from 'vafast'
import { jwt } from '@vafast/jwt'
import { Type as t } from '@sinclair/typebox'

// 定义用户模式
const UserSchema = t.Object({
    id: t.Number(),
    username: t.String(),
    email: t.String(),
    role: t.Union([t.Literal('user'), t.Literal('admin')])
})

const jwtMiddleware = jwt({
    name: 'jwt',
    secret: 'your-secret-key',
    exp: '24h',
    schema: UserSchema
})

const routes = [
    {
        method: 'POST',
        path: '/register',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const body = await req.json()
            
            // 创建用户令牌
            const token = await (req as any).jwt.sign({
                id: 1,
                username: body.username,
                email: body.email,
                role: 'user'
            })
            
            return {
                message: 'User registered successfully',
                token,
                user: {
                    id: 1,
                    username: body.username,
                    email: body.email,
                    role: 'user'
                }
            }
        })
    }
]

const server = new Server(routes)
export default { fetch: (req: Request) => server.fetch(req) }

3. 多 JWT 实例

typescript
import { Server, createHandler } from 'vafast'
import { jwt } from '@vafast/jwt'

// 创建不同配置的 JWT 中间件
const accessTokenMiddleware = jwt({
    name: 'accessToken',
    secret: 'access-secret',
    exp: '15m',
    iss: 'your-app.com'
})

const refreshTokenMiddleware = jwt({
    name: 'refreshToken',
    secret: 'refresh-secret',
    exp: '7d',
    iss: 'your-app.com'
})

const routes = [
    {
        method: 'POST',
        path: '/auth/login',
        handler: createHandler(async ({ req }: { req: Request }) => {
            // 应用两个 JWT 中间件
            accessTokenMiddleware(req, () => Promise.resolve(new Response()))
            refreshTokenMiddleware(req, () => Promise.resolve(new Response()))
            
            const body = await req.json()
            const { username, password } = body
            
            if (username === 'admin' && password === 'password') {
                const accessToken = await (req as any).accessToken.sign({
                    username,
                    role: 'admin',
                    type: 'access'
                })
                
                const refreshToken = await (req as any).refreshToken.sign({
                    username,
                    type: 'refresh'
                })
                
                return {
                    message: 'Login successful',
                    accessToken,
                    refreshToken,
                    expiresIn: '15m'
                }
            } else {
                return {
                    status: 401,
                    message: 'Invalid credentials'
                }
            }
        })
    },
    {
        method: 'POST',
        path: '/auth/refresh',
        handler: createHandler(async ({ req }: { req: Request }) => {
            accessTokenMiddleware(req, () => Promise.resolve(new Response()))
            refreshTokenMiddleware(req, () => Promise.resolve(new Response()))
            
            const body = await req.json()
            const { refreshToken } = body
            
            const payload = await (req as any).refreshToken.verify(refreshToken)
            
            if (!payload || payload.type !== 'refresh') {
                return {
                    status: 401,
                    message: 'Invalid refresh token'
                }
            }
            
            // 生成新的访问令牌
            const newAccessToken = await (req as any).accessToken.sign({
                username: payload.username,
                role: 'admin',
                type: 'access'
            })
            
            return {
                message: 'Token refreshed',
                accessToken: newAccessToken,
                expiresIn: '15m'
            }
        })
    }
]

const server = new Server(routes)
export default { fetch: (req: Request) => server.fetch(req) }

4. 高级 JWT 配置

typescript
import { Server, createHandler } from 'vafast'
import { jwt } from '@vafast/jwt'

const advancedJwtMiddleware = jwt({
    name: 'jwt',
    secret: 'your-secret-key',
    alg: 'HS512',           // 使用更强的算法
    typ: 'JWT',
    iss: 'your-app.com',    // 发行者
    sub: 'authentication',  // 主题
    aud: ['web', 'mobile'], // 受众
    exp: '1h',              // 过期时间
    nbf: '0s',              // 立即生效
    iat: true,              // 包含签发时间
    jti: 'unique-id',       // JWT ID
    kid: 'key-1',           // 密钥 ID
    b64: true,              // Base64 编码
    crit: ['b64']           // 关键参数
})

const routes = [
    {
        method: 'POST',
        path: '/auth/advanced',
        handler: createHandler(async ({ req }: { req: Request }) => {
            advancedJwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const token = await (req as any).jwt.sign({
                username: 'admin',
                role: 'admin',
                permissions: ['read', 'write', 'delete']
            })
            
            return {
                message: 'Advanced JWT created',
                token,
                config: {
                    algorithm: 'HS512',
                    issuer: 'your-app.com',
                    audience: ['web', 'mobile'],
                    expiresIn: '1h'
                }
            }
        })
    }
]

const server = new Server(routes)
export default { fetch: (req: Request) => server.fetch(req) }

完整示例

typescript
import { Server, createHandler } from 'vafast'
import { jwt } from '@vafast/jwt'
import { Type as t } from '@sinclair/typebox'

// 定义用户模式
const UserSchema = t.Object({
    id: t.Number(),
    username: t.String(),
    email: t.String(),
    role: t.Union([t.Literal('user'), t.Literal('admin')])
})

// 创建 JWT 中间件
const jwtMiddleware = jwt({
    name: 'jwt',
    secret: 'your-super-secret-key-here',
    sub: 'authentication',
    iss: 'your-app.com',
    exp: '24h',
    schema: UserSchema
})

// 模拟用户数据库
const users = [
    { id: 1, username: 'admin', email: 'admin@example.com', password: 'admin123', role: 'admin' },
    { id: 2, username: 'user', email: 'user@example.com', password: 'user123', role: 'user' }
]

// 辅助函数:验证用户凭据
const validateUser = (username: string, password: string) => {
    return users.find(user => user.username === username && user.password === password)
}

// 辅助函数:从请求中提取令牌
const extractToken = (req: Request) => {
    const authHeader = req.headers.get('authorization')
    if (authHeader?.startsWith('Bearer ')) {
        return authHeader.substring(7)
    }
    return null
}

// 定义路由
const routes = [
    {
        method: 'GET',
        path: '/',
        handler: createHandler(() => {
            return { 
                message: 'Vafast JWT Authentication API',
                endpoints: [
                    'POST /auth/register - 用户注册',
                    'POST /auth/login - 用户登录',
                    'GET /profile - 获取用户资料',
                    'PUT /profile - 更新用户资料',
                    'POST /auth/logout - 用户登出',
                    'GET /admin - 管理员专用端点'
                ]
            }
        })
    },
    {
        method: 'POST',
        path: '/auth/register',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const body = await req.json()
            const { username, email, password } = body
            
            // 检查用户是否已存在
            if (users.find(user => user.username === username)) {
                return {
                    status: 400,
                    message: 'Username already exists'
                }
            }
            
            // 创建新用户
            const newUser = {
                id: users.length + 1,
                username,
                email,
                password,
                role: 'user' as const
            }
            
            users.push(newUser)
            
            // 生成 JWT 令牌
            const token = await (req as any).jwt.sign({
                id: newUser.id,
                username: newUser.username,
                email: newUser.email,
                role: newUser.role
            })
            
            return {
                message: 'User registered successfully',
                token,
                user: {
                    id: newUser.id,
                    username: newUser.username,
                    email: newUser.email,
                    role: newUser.role
                }
            }
        })
    },
    {
        method: 'POST',
        path: '/auth/login',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const body = await req.json()
            const { username, password } = body
            
            const user = validateUser(username, password)
            
            if (!user) {
                return {
                    status: 401,
                    message: 'Invalid credentials'
                }
            }
            
            // 生成 JWT 令牌
            const token = await (req as any).jwt.sign({
                id: user.id,
                username: user.username,
                email: user.email,
                role: user.role
            })
            
            return {
                message: 'Login successful',
                token,
                user: {
                    id: user.id,
                    username: user.username,
                    email: user.email,
                    role: user.role
                }
            }
        })
    },
    {
        method: 'GET',
        path: '/profile',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const token = extractToken(req)
            
            if (!token) {
                return {
                    status: 401,
                    message: 'No token provided'
                }
            }
            
            const payload = await (req as any).jwt.verify(token)
            
            if (!payload) {
                return {
                    status: 401,
                    message: 'Invalid token'
                }
            }
            
            const user = users.find(u => u.id === payload.id)
            
            if (!user) {
                return {
                    status: 404,
                    message: 'User not found'
                }
            }
            
            return {
                message: 'Profile retrieved successfully',
                user: {
                    id: user.id,
                    username: user.username,
                    email: user.email,
                    role: user.role
                }
            }
        })
    },
    {
        method: 'PUT',
        path: '/profile',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const token = extractToken(req)
            
            if (!token) {
                return {
                    status: 401,
                    message: 'No token provided'
                }
            }
            
            const payload = await (req as any).jwt.verify(token)
            
            if (!payload) {
                return {
                    status: 401,
                    message: 'Invalid token'
                }
            }
            
            const body = await req.json()
            const { email } = body
            
            const user = users.find(u => u.id === payload.id)
            
            if (!user) {
                return {
                    status: 404,
                    message: 'User not found'
                }
            }
            
            // 更新用户信息
            user.email = email
            
            return {
                message: 'Profile updated successfully',
                user: {
                    id: user.id,
                    username: user.username,
                    email: user.email,
                    role: user.role
                }
            }
        })
    },
    {
        method: 'GET',
        path: '/admin',
        handler: createHandler(async ({ req }: { req: Request }) => {
            jwtMiddleware(req, () => Promise.resolve(new Response()))
            
            const token = extractToken(req)
            
            if (!token) {
                return {
                    status: 401,
                    message: 'No token provided'
                }
            }
            
            const payload = await (req as any).jwt.verify(token)
            
            if (!payload) {
                return {
                    status: 401,
                    message: 'Invalid token'
                }
            }
            
            if (payload.role !== 'admin') {
                return {
                    status: 403,
                    message: 'Access denied. Admin role required.'
                }
            }
            
            return {
                message: 'Admin access granted',
                adminData: {
                    totalUsers: users.length,
                    systemStatus: 'healthy',
                    lastMaintenance: new Date().toISOString()
                }
            }
        })
    },
    {
        method: 'POST',
        path: '/auth/logout',
        handler: createHandler(async ({ req }: { req: Request }) => {
            // 在实际应用中,你可能需要将令牌加入黑名单
            return {
                message: 'Logout successful',
                note: 'Token has been invalidated'
            }
        })
    }
]

// 创建服务器
const server = new Server(routes)

// 导出 fetch 函数
export default {
    fetch: (req: Request) => server.fetch(req)
}

console.log('🚀 Vafast JWT Authentication API 服务器启动成功!')
console.log('📝 用户注册: POST /auth/register')
console.log('🔐 用户登录: POST /auth/login')
console.log('👤 获取资料: GET /profile')
console.log('✏️ 更新资料: PUT /profile')
console.log('👑 管理端点: GET /admin')
console.log('🚪 用户登出: POST /auth/logout')

测试示例

typescript
import { describe, expect, it } from 'bun:test'
import { Server, createHandler } from 'vafast'
import { jwt } from '@vafast/jwt'

describe('Vafast JWT Plugin', () => {
    it('should sign JWT tokens', async () => {
        const jwtMiddleware = jwt({
            name: 'jwt',
            secret: 'test-secret',
            sub: 'auth',
            iss: 'test.com',
            exp: '1h'
        })

        const app = new Server([
            {
                method: 'GET',
                path: '/sign',
                handler: createHandler(async ({ req }: { req: Request }) => {
                    jwtMiddleware(req, () => Promise.resolve(new Response()))
                    
                    const token = await (req as any).jwt.sign({
                        name: 'testuser'
                    })
                    return { token }
                })
            }
        ])

        const res = await app.fetch(new Request('http://localhost/sign'))
        const data = await res.json()

        expect(data.token).toBeDefined()
        expect(typeof data.token).toBe('string')
        expect(data.token.split('.')).toHaveLength(3) // JWT 有 3 个部分
    })

    it('should verify JWT tokens', async () => {
        const jwtMiddleware = jwt({
            name: 'jwt',
            secret: 'test-secret',
            sub: 'auth',
            iss: 'test.com',
            exp: '1h'
        })

        const app = new Server([
            {
                method: 'GET',
                path: '/verify',
                handler: createHandler(async ({ req }: { req: Request }) => {
                    jwtMiddleware(req, () => Promise.resolve(new Response()))
                    
                    // 首先签名一个令牌
                    const token = await (req as any).jwt.sign({
                        name: 'testuser',
                        id: 123
                    })

                    // 然后验证它
                    const payload = await (req as any).jwt.verify(token)

                    return { payload }
                })
            }
        ])

        const res = await app.fetch(new Request('http://localhost/verify'))
        const data = await res.json()

        expect(data.payload).toBeDefined()
        expect(data.payload.name).toBe('testuser')
        expect(data.payload.id).toBe(123)
    })

    it('should handle invalid JWT tokens', async () => {
        const jwtMiddleware = jwt({
            name: 'jwt',
            secret: 'test-secret',
            sub: 'auth',
            iss: 'test.com',
            exp: '1h'
        })

        const app = new Server([
            {
                method: 'GET',
                path: '/verify-invalid',
                handler: createHandler(async ({ req }: { req: Request }) => {
                    jwtMiddleware(req, () => Promise.resolve(new Response()))
                    
                    // 尝试验证无效令牌
                    const payload = await (req as any).jwt.verify('invalid.token.here')

                    return { payload }
                })
            }
        ])

        const res = await app.fetch(new Request('http://localhost/verify-invalid'))
        const data = await res.json()

        expect(data.payload).toBe(false)
    })

    it('should handle missing tokens', async () => {
        const jwtMiddleware = jwt({
            name: 'jwt',
            secret: 'test-secret',
            sub: 'auth',
            iss: 'test.com',
            exp: '1h'
        })

        const app = new Server([
            {
                method: 'GET',
                path: '/verify-missing',
                handler: createHandler(async ({ req }: { req: Request }) => {
                    jwtMiddleware(req, () => Promise.resolve(new Response()))
                    
                    // 尝试验证缺失的令牌
                    const payload = await (req as any).jwt.verify()

                    return { payload }
                })
            }
        ])

        const res = await app.fetch(new Request('http://localhost/verify-missing'))
        const data = await res.json()

        expect(data.payload).toBe(false)
    })
})

特性

  • JWT 签名: 支持创建和验证 JWT 令牌
  • 多种算法: 支持 HS256、RS256、ES256 等多种签名算法
  • 类型安全: 完整的 TypeScript 类型支持
  • 模式验证: 使用 TypeBox 进行负载验证
  • 灵活配置: 支持所有标准 JWT 声明
  • 高性能: 基于 jose 库的高性能实现
  • 易于集成: 无缝集成到 Vafast 应用

最佳实践

1. 密钥管理

typescript
// 使用环境变量存储密钥
const jwtMiddleware = jwt({
    name: 'jwt',
    secret: process.env.JWT_SECRET || 'fallback-secret',
    exp: '1h'
})

// 使用强密钥
const strongSecret = crypto.randomBytes(64).toString('hex')

2. 令牌过期策略

typescript
const jwtMiddleware = jwt({
    name: 'jwt',
    secret: 'your-secret',
    exp: '15m',  // 访问令牌:15 分钟
    nbf: '0s',   // 立即生效
    iat: true    // 包含签发时间
})

3. 错误处理

typescript
const payload = await (req as any).jwt.verify(token)

if (!payload) {
    return {
        status: 401,
        message: 'Invalid or expired token',
        code: 'TOKEN_INVALID'
    }
}

4. 安全考虑

typescript
// 在生产环境中使用强算法
const productionJwt = jwt({
    name: 'jwt',
    secret: process.env.JWT_SECRET,
    alg: 'HS512',  // 使用更强的算法
    exp: '1h',
    iss: 'your-domain.com',
    aud: ['web', 'api']
})

注意事项

  1. 密钥安全: 确保 JWT 密钥的安全性,不要将其暴露在客户端代码中
  2. 令牌过期: 合理设置令牌过期时间,平衡安全性和用户体验
  3. 算法选择: 根据安全需求选择合适的签名算法
  4. 负载大小: JWT 令牌会增加请求大小,避免在负载中存储过多数据
  5. 类型断言: 当前版本需要使用类型断言 (req as any).jwt 来访问 JWT 方法

相关链接