rwadurian/backend/services/identity-service/src/shared/guards/jwt-auth.guard.ts

79 lines
2.2 KiB
TypeScript

import {
Injectable,
CanActivate,
ExecutionContext,
createParamDecorator,
SetMetadata,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { UnauthorizedException } from '@/shared/exceptions/domain.exception';
export interface JwtPayload {
userId: string;
accountSequence: string; // 格式: D + YYMMDD + 5位序号
deviceId: string;
type: 'access' | 'refresh';
}
export interface CurrentUserData {
userId: string;
accountSequence: string; // 格式: D + YYMMDD + 5位序号
deviceId: string;
}
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
export const CurrentUser = createParamDecorator(
(
data: keyof CurrentUserData | undefined,
ctx: ExecutionContext,
): CurrentUserData | string | number => {
const request = ctx.switchToHttp().getRequest();
const user = request.user as CurrentUserData;
return data ? user?.[data] : user;
},
);
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private readonly reflector: Reflector,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) throw new UnauthorizedException('缺少认证令牌');
try {
const payload = await this.jwtService.verifyAsync<JwtPayload>(token);
if (payload.type !== 'access')
throw new UnauthorizedException('无效的令牌类型');
request.user = {
userId: payload.userId,
accountSequence: payload.accountSequence,
deviceId: payload.deviceId,
};
} catch {
throw new UnauthorizedException('令牌无效或已过期');
}
return true;
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}