84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
|
|
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<Request>();
|
|||
|
|
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<string>('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;
|
|||
|
|
}
|
|||
|
|
}
|