返回 AiToEarn
aitoearn-auth.guard.ts
根目录 / project / aitoearn-backend / libs / aitoearn-auth / src / aitoearn-auth.guard.ts
1 import {
2 CanActivate,
3 ExecutionContext,
4 Inject,
5 Injectable,
6 Logger,
7 UnauthorizedException,
8 } from '@nestjs/common'
9 import { Reflector } from '@nestjs/core'
10 import { JwtService } from '@nestjs/jwt'
11 import { AITOEARN_AUTH_OPTIONS, AitoearnAuthOptions, TokenPayload } from './aitoearn-auth.config'
12 import { API_KEY_HEADER_KEY, IS_INTERNAL_KEY, IS_PUBLIC_KEY } from './aitoearn-auth.constants'
13
14 @Injectable()
15 export class AitoearnAuthGuard implements CanActivate {
16 private readonly logger = new Logger(AitoearnAuthGuard.name)
17 private readonly reflector = new Reflector()
18 constructor(
19 private readonly jwtService: JwtService,
20 @Inject(AITOEARN_AUTH_OPTIONS)
21 private readonly options: AitoearnAuthOptions,
22 ) {}
23
24 async canActivate(context: ExecutionContext): Promise<boolean> {
25 const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
26 context.getHandler(),
27 context.getClass(),
28 ])
29 const isInternal = this.reflector.getAllAndOverride<boolean>(IS_INTERNAL_KEY, [
30 context.getHandler(),
31 context.getClass(),
32 ])
33
34 const request = context.switchToHttp().getRequest()
35
36 if (isInternal) {
37 const token = this.extractTokenFromHeader(request)
38 if (token === this.options.internalToken) {
39 return true
40 }
41 throw new UnauthorizedException()
42 }
43
44 // 1. API Key 认证(x-api-key,既有行为,对所有路由生效)
45 const apiKey = request.headers['x-api-key'] as string | undefined
46 if (apiKey) {
47 await this.resolveApiKey(request, apiKey)
48 return true
49 }
50
51 // 2. 额外声明的 header(@ApiKeyHeader)按 API Key 解析
52 const apiKeyHeader = this.reflector.getAllAndOverride<string>(API_KEY_HEADER_KEY, [
53 context.getHandler(),
54 context.getClass(),
55 ])
56 if (apiKeyHeader) {
57 // Authorization 特殊处理:优先按 JWT 校验,失败再兜底按 API Key 解析
58 if (apiKeyHeader.toLowerCase() === 'authorization') {
59 const token = this.extractTokenFromHeader(request)
60 if (token && token !== this.options.internalToken) {
61 if (await this.tryJwt(request, token)) {
62 return true
63 }
64 await this.resolveApiKey(request, token)
65 return true
66 }
67 }
68 else {
69 const headerValue = request.headers[apiKeyHeader.toLowerCase()] as string | undefined
70 if (headerValue) {
71 await this.resolveApiKey(request, headerValue)
72 return true
73 }
74 }
75 }
76
77 // 3. Bearer Token 认证(默认 JWT 路径)
78 const token = this.extractTokenFromHeader(request)
79 if (!token) {
80 if (isPublic) {
81 return true
82 }
83 throw new UnauthorizedException()
84 }
85
86 if (token === this.options.internalToken) {
87 return true
88 }
89
90 if (await this.tryJwt(request, token)) {
91 return true
92 }
93
94 if (isPublic) {
95 return true
96 }
97 throw new UnauthorizedException()
98 }
99
100 private async resolveApiKey(request: any, apiKey: string): Promise<void> {
101 if (!this.options.getTokenInfoByApiKey) {
102 throw new UnauthorizedException()
103 }
104 request['user'] = await this.options.getTokenInfoByApiKey(apiKey)
105 }
106
107 private async tryJwt(request: any, token: string): Promise<boolean> {
108 try {
109 const payload = await this.jwtService.verifyAsync<TokenPayload>(token, {
110 secret: this.options.secret,
111 })
112 request['user'] = await this.options.getTokenInfo(payload)
113 return true
114 }
115 catch (error) {
116 this.logger.debug({
117 message: 'token验证失败',
118 error,
119 })
120 return false
121 }
122 }
123
124 private extractTokenFromHeader(request: any): string | undefined {
125 const [type, token] = request.headers.authorization?.split(' ') ?? []
126 return type === 'Bearer' ? token : undefined
127 }
128 }
129
129 lines TYPESCRIPT