import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Request } from 'express'; @Injectable() export class AuthGuard implements CanActivate { constructor(private configService: ConfigService) {} canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const clientIp = this.getClientIp(request); // 检查是否为本地 IP if (this.isLocalIp(clientIp)) { return true; // 本地访问直接放行 } // 公网访问需要验证 API Key const apiKey = request.headers['x-api-key'] as string; const expectedApiKey = this.configService.get('API_KEY'); if (!expectedApiKey) { // 如果未配置 API_KEY,允许所有访问(开发环境) return true; } if (!apiKey || apiKey !== expectedApiKey) { throw new UnauthorizedException('Invalid or missing API Key'); } return true; } /** * 获取客户端真实 IP * 支持反向代理场景(X-Forwarded-For) */ private getClientIp(request: Request): string { const forwardedFor = request.headers['x-forwarded-for']; if (forwardedFor) { // X-Forwarded-For 可能包含多个 IP,取第一个 const ips = (forwardedFor as string).split(','); return ips[0].trim(); } return request.ip || request.socket.remoteAddress || 'unknown'; } /** * 判断是否为本地 IP */ private isLocalIp(ip: string): boolean { if (!ip || ip === 'unknown') { return false; } // IPv4 本地地址 if (ip === '127.0.0.1' || ip === 'localhost' || ip.startsWith('127.')) { return true; } // IPv6 本地地址 if (ip === '::1' || ip === '::ffff:127.0.0.1') { return true; } // 内网地址(可选,根据需求决定是否允许) // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 const privateIpRegex = /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/; if (privateIpRegex.test(ip)) { // 如果需要内网也免鉴权,可以返回 true // 这里默认返回 false,内网也需要鉴权 return false; } return false; } }