mirror of
https://github.com/KwiTsukasa/kt-template-online-api.git
synced 2026-05-27 15:44:54 +08:00
fix(api): 字典和 MinIO 接入认证守卫
This commit is contained in:
parent
5c526106bd
commit
a920a92ad0
4
API.md
4
API.md
@ -48,7 +48,7 @@
|
||||
|
||||
### 后台认证
|
||||
|
||||
Admin 与 Component 业务接口统一走 `JwtAuthGuard`。请求可以通过 `Authorization: Bearer <accessToken>` 传递 accessToken,也可以携带登录接口写入的 httpOnly `admin_access_token` cookie。未认证时接口返回 HTTP `401`。
|
||||
Admin、Component、Dict 与 MinIO 业务接口统一走 `JwtAuthGuard`。请求可以通过 `Authorization: Bearer <accessToken>` 传递 accessToken,也可以携带登录接口写入的 httpOnly `admin_access_token` cookie。未认证时接口返回 HTTP `401`。
|
||||
|
||||
`ADMIN_COOKIE_SECURE=false` 适用于当前内网 HTTP 访问;如果后续切到 HTTPS 域名,可以改为 `true`,cookie 会使用 `Secure + SameSite=None`。
|
||||
|
||||
@ -56,7 +56,7 @@ Admin 与 Component 业务接口统一走 `JwtAuthGuard`。请求可以通过 `A
|
||||
|
||||
### 数据库字典翻译
|
||||
|
||||
组件数据维护在 `admin_component` 表中,字典数据维护在新的 `admin_dict` 表中。`Component.typeMsg`、`Component.componentTypeMsg` 会在 TypeORM `AfterLoad` 阶段根据字典缓存自动映射;旧 `/dict/*` 接口路径保持兼容。
|
||||
组件数据维护在 `admin_component` 表中,字典数据维护在新的 `admin_dict` 表中。`Component.typeMsg`、`Component.componentTypeMsg` 会在 TypeORM `AfterLoad` 阶段根据字典缓存自动映射;旧 `/dict/*` 接口路径保持兼容,但仍需要登录态。
|
||||
|
||||
`admin_dict` 表核心字段:
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ pnpm test:e2e # e2e 测试
|
||||
- 如果基础后台菜单的 `meta` 被旧数据覆盖为空,执行 `sql/fix-admin-menu-meta.sql` 可以恢复初始化菜单的 `title/icon/order` 等元数据。
|
||||
- 旧 `component` 表迁移到 `admin_component` 时,执行 `sql/migrate-component-to-admin-component.sql`,脚本会把旧表重命名为备份表。
|
||||
- 如果旧版本曾写入 `admin_user.id=0`,先执行 `sql/fix-admin-user-zero-id.sql` 修复脏数据,再重启服务。
|
||||
- Admin 与 Component 业务接口统一走 `JwtAuthGuard`;登录、刷新 token、退出登录和部分示例状态测试接口通过 `@Public()` 放行。
|
||||
- Admin、Component、Dict 与 MinIO 业务接口统一走 `JwtAuthGuard`;登录、刷新 token、退出登录和部分示例状态测试接口通过 `@Public()` 放行。
|
||||
- `kt-template-admin` 登录会写入 access token 与刷新 token cookie,`kt-template-online-web` 和 `kt-template-online-playground` 可在回跳后通过刷新 token 重新持久化登录态。
|
||||
- `kt-template-admin` 开发环境通过 `/api` 代理到本服务 `48085`,已关闭 Vben Nitro Mock。
|
||||
- `POST /component/save` 新增组件,`POST /component/update` 编辑组件。
|
||||
@ -114,8 +114,8 @@ pnpm test:e2e # e2e 测试
|
||||
|
||||
## 联调关系
|
||||
|
||||
- `kt-template-online-web` 读取 `/component/list`、`/component/detail`、`/dict/*` 展示组件列表,并生成 Playground 跳转链接;组件接口返回 `401` 时跳转到 `kt-template-admin` 登录。
|
||||
- `kt-template-online-playground` 读取 `/dict/*` 初始化分类,保存时上传截图到 `/minio/upload`,再调用 `/component/save` 或 `/component/update`;组件接口返回 `401` 时跳转到 `kt-template-admin` 登录并在回跳后刷新 token。
|
||||
- `kt-template-online-web` 读取 `/component/list`、`/component/detail`、`/dict/*` 展示组件列表,并生成 Playground 跳转链接;业务接口返回 `401` 时跳转到 `kt-template-admin` 登录。
|
||||
- `kt-template-online-playground` 读取 `/dict/*` 初始化分类,保存时上传截图到 `/minio/upload`,再调用 `/component/save` 或 `/component/update`;业务接口返回 `401` 时跳转到 `kt-template-admin` 登录并在回跳后刷新 token。
|
||||
- 前端项目通过 Vite 代理把 `/api` 转发到 `http://localhost:48085/`。
|
||||
|
||||
## 轻量验证
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AdminAuthGuardModule } from './auth/admin-auth-guard.module';
|
||||
import { AdminAuthController } from './auth/admin-auth.controller';
|
||||
import { AdminAuthService } from './auth/admin-auth.service';
|
||||
import { JwtAuthGuard } from './auth/jwt-auth.guard';
|
||||
import { AdminTokenService } from './auth/admin-token.service';
|
||||
import { ComponentController } from './component/component.controller';
|
||||
import { Component } from './component/component.entity';
|
||||
import { ComponentService } from './component/component.service';
|
||||
@ -35,6 +33,7 @@ import { MinioClientModule } from '@/minio/minio.module';
|
||||
AdminDept,
|
||||
Component,
|
||||
]),
|
||||
AdminAuthGuardModule,
|
||||
DictModule,
|
||||
MinioClientModule,
|
||||
],
|
||||
@ -49,15 +48,12 @@ import { MinioClientModule } from '@/minio/minio.module';
|
||||
AdminExampleController,
|
||||
],
|
||||
providers: [
|
||||
AdminAuthService,
|
||||
ComponentService,
|
||||
AdminDeptService,
|
||||
AdminMenuService,
|
||||
AdminRoleService,
|
||||
AdminTimezoneService,
|
||||
AdminTokenService,
|
||||
AdminUserService,
|
||||
JwtAuthGuard,
|
||||
ToolsService,
|
||||
],
|
||||
})
|
||||
|
||||
14
src/admin/auth/admin-auth-guard.module.ts
Normal file
14
src/admin/auth/admin-auth-guard.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AdminUser } from '../user/admin-user.entity';
|
||||
import { AdminAuthService } from './admin-auth.service';
|
||||
import { AdminTokenService } from './admin-token.service';
|
||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, TypeOrmModule.forFeature([AdminUser])],
|
||||
providers: [AdminAuthService, AdminTokenService, JwtAuthGuard],
|
||||
exports: [AdminAuthService, AdminTokenService, JwtAuthGuard],
|
||||
})
|
||||
export class AdminAuthGuardModule {}
|
||||
@ -5,11 +5,13 @@ import {
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
Res,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { DictService } from './dict.service';
|
||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiArrayResponse, ToolsService } from '@/common';
|
||||
import { DictDto } from './dict.dto';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
|
||||
const componentTypeDictExample = [
|
||||
{
|
||||
@ -35,6 +37,7 @@ const chartDictExample = [
|
||||
|
||||
@ApiTags('dict')
|
||||
@Controller('dict')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class DictController {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AdminAuthGuardModule } from '../auth/admin-auth-guard.module';
|
||||
import { DictController } from './dict.controller';
|
||||
import { DictService } from './dict.service';
|
||||
import { ToolsService } from '@/common';
|
||||
import { AdminDict } from './admin-dict.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([AdminDict])],
|
||||
imports: [AdminAuthGuardModule, TypeOrmModule.forFeature([AdminDict])],
|
||||
controllers: [DictController],
|
||||
providers: [DictService, ToolsService],
|
||||
exports: [DictService],
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
Query,
|
||||
Res,
|
||||
UploadedFile,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
@ -34,6 +35,7 @@ import {
|
||||
MinioObjectDto,
|
||||
MinioUploadResultDto,
|
||||
} from './minio.dto';
|
||||
import { JwtAuthGuard } from '@/admin/auth/jwt-auth.guard';
|
||||
|
||||
const PROXY_RESOURCE_TIMEOUT = 1000 * 15;
|
||||
const PROXY_RESOURCE_CONTENT_TYPES = [
|
||||
@ -49,6 +51,7 @@ const PROXY_RESOURCE_EXTENSION_RE =
|
||||
|
||||
@Controller('minio')
|
||||
@ApiTags('minio')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class MinioClientController {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module';
|
||||
import { MinioClientController } from './minio.controller';
|
||||
import { MinioClientService } from './minio.service';
|
||||
import { ToolsService } from '@/common';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
imports: [AdminAuthGuardModule, ConfigModule],
|
||||
controllers: [MinioClientController],
|
||||
providers: [MinioClientService, ToolsService],
|
||||
exports: [MinioClientService],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { HttpException, HttpStatus, INestApplication } from '@nestjs/common';
|
||||
import { APP_INTERCEPTOR, Reflector } from '@nestjs/core';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import request = require('supertest');
|
||||
@ -76,6 +76,17 @@ const authServiceMock = {
|
||||
currentUser: jest.fn(),
|
||||
};
|
||||
|
||||
const unauthorizedException = () =>
|
||||
new HttpException(
|
||||
{
|
||||
code: -1,
|
||||
data: null,
|
||||
error: 'Unauthorized Exception',
|
||||
message: 'Unauthorized Exception',
|
||||
},
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
|
||||
const dictServiceMock = {
|
||||
getDictByKey: jest.fn(),
|
||||
getComponentDictByType: jest.fn(),
|
||||
@ -518,6 +529,10 @@ describe('KT Template Online API (e2e)', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
authServiceMock.currentUser.mockResolvedValue({
|
||||
id: '2041739550026043001',
|
||||
username: 'admin',
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -560,4 +575,22 @@ describe('KT Template Online API (e2e)', () => {
|
||||
data: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('protects dict and minio endpoints with jwt auth', async () => {
|
||||
authServiceMock.currentUser.mockRejectedValue(unauthorizedException());
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.get('/dict/getDictByKey')
|
||||
.query({ dictKey: 'COMPONENT_TYPE' })
|
||||
.expect(401);
|
||||
|
||||
expect(dictServiceMock.getDictByKey).not.toHaveBeenCalled();
|
||||
|
||||
jest.clearAllMocks();
|
||||
authServiceMock.currentUser.mockRejectedValue(unauthorizedException());
|
||||
|
||||
await request(app.getHttpServer()).get('/minio/check').expect(401);
|
||||
|
||||
expect(minioServiceMock.checkConnection).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user