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:
3
.env
3
.env
@@ -43,3 +43,6 @@ LOG_LEVEL=debug
|
||||
ARK_API_KEY=a63d58b6-cf56-434b-8a42-5c781ba0822a
|
||||
|
||||
SSH_PASSPHRASE=x
|
||||
|
||||
API_KEY=22c64b60-6e60-433c-991d-f6d658024b9e
|
||||
|
||||
|
||||
@@ -25,3 +25,5 @@ LOG_LEVEL=info
|
||||
|
||||
# OpenAI API Key (用于 AI 推荐)
|
||||
ARK_API_KEY=your_openai_api_key_here
|
||||
|
||||
API_KEY=your_secure_api_key_here
|
||||
@@ -1,34 +1,52 @@
|
||||
import axios from 'axios'
|
||||
import axios, { type AxiosRequestConfig, type AxiosError } from 'axios';
|
||||
|
||||
/**
|
||||
* API配置
|
||||
* 配置axios实例,设置baseURL和请求拦截器
|
||||
*/
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:3000', // 设置后端服务地址
|
||||
baseURL:
|
||||
(import.meta.env.VITE_API_BASE_URL as string) || 'http://localhost:3000', // 设置后端服务地址
|
||||
timeout: 120000, // 请求超时时间(120秒)
|
||||
})
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
// 可以在这里添加认证信息等
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
(config: AxiosRequestConfig) => {
|
||||
// 如果 baseURL 不是 localhost,自动添加 API Key
|
||||
const baseURL =
|
||||
(config.baseURL as string) ||
|
||||
(api.defaults.baseURL as string) ||
|
||||
'';
|
||||
const isLocalhost =
|
||||
baseURL.includes('localhost') || baseURL.includes('127.0.0.1');
|
||||
|
||||
if (!isLocalhost) {
|
||||
// 从环境变量或 localStorage 获取 API Key
|
||||
const apiKey =
|
||||
(import.meta.env.VITE_API_KEY as string) ||
|
||||
localStorage.getItem('apiKey');
|
||||
if (apiKey && config.headers) {
|
||||
config.headers['X-API-Key'] = apiKey;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
return response
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
console.error('API请求错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
(error: AxiosError) => {
|
||||
console.error('API请求错误:', error);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
export default api
|
||||
export default api;
|
||||
@@ -11,6 +11,7 @@ import { TasksModule } from './schedule/schedule.module';
|
||||
import { LoggerModule } from './common/logger/logger.module';
|
||||
import { LoggingMiddleware } from './common/logger/logging.middleware';
|
||||
import { AiModule } from './ai/ai.module';
|
||||
import { AuthModule } from './common/auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -21,6 +22,7 @@ import { AiModule } from './ai/ai.module';
|
||||
exclude: ['/api'],
|
||||
}),
|
||||
LoggerModule,
|
||||
AuthModule,
|
||||
DatabaseModule,
|
||||
BidsModule,
|
||||
KeywordsModule,
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
13
src/common/auth/auth.module.ts
Normal file
13
src/common/auth/auth.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthGuard,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -21,6 +21,10 @@ async function bootstrap() {
|
||||
// 启用 CORS
|
||||
app.enableCors();
|
||||
|
||||
// 信任代理(用于获取真实客户端 IP)
|
||||
const httpAdapter = app.getHttpAdapter();
|
||||
httpAdapter.getInstance().set('trust proxy', true);
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
void bootstrap();
|
||||
|
||||
Reference in New Issue
Block a user