feat(auth): implement API key authentication and enhance request handling
- Add AuthGuard to validate API key for public access. - Create AuthModule to provide the AuthGuard globally. - Update API request interceptor to automatically include API key for non-localhost requests. - Modify .env and .env.example to include API_KEY configuration. - Enhance API request handling with improved error logging and client IP detection.
This commit is contained in:
83
src/common/auth/auth.guard.ts
Normal file
83
src/common/auth/auth.guard.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user