18-3 接口安全:引入拦截器对接口进行脱敏处理
课程内容润色与扩展
18-3 接口安全:引入拦截器对接口进行脱敏处理
同学们好!我是Brian,今天我们将深入探讨如何利用NestJS拦截器对接口返回的敏感信息(如密码、身份证号等)进行脱敏处理,从而显著提升系统的安全性。🚀
课程目标
- 理解接口安全的重要性:为什么需要对敏感信息进行脱敏处理?
- 掌握拦截器的核心概念:拦截器是什么?它与管道、守卫有何区别?
- 实战演练:动手实现一个响应拦截器,完成敏感数据的自动过滤。
- 扩展应用:探索拦截器的高级用法,如动态配置敏感字段和深度脱敏。
1. 接口安全风险与脱敏需求
1.1 敏感数据暴露的风险
在现代Web应用中,接口返回的数据可能包含用户的敏感信息,例如:
- 密码
- 身份证号
- 银行卡号
- 手机号
如果这些信息未经处理直接返回给前端,可能会导致严重的安全问题,如数据泄露或恶意攻击。
示例:不安全的响应
{
"id": 1,
"username": "test_user",
"password": "123456", // 敏感字段暴露
"phone": "13800138000"
}
json
💡 安全规范:根据OWASP API安全指南,接口响应必须过滤敏感信息。
1.2 基础脱敏方案
最简单的脱敏方法是在控制器中手动删除敏感字段:
// 基础脱敏实现
const user = await this.userService.create(dto);
delete user.password; // 手动删除密码字段
return user;
typescript
缺点分析
- 维护成本高:如果敏感字段较多,需要重复编写删除逻辑。
- 易出错:字段名变更时容易遗漏。
- 代码冗余:多个接口需要相同的脱敏逻辑。
2. NestJS拦截器核心概念
2.1 拦截器的定位与作用
拦截器是NestJS中一种面向切面编程(AOP)的技术,可以在请求处理的多个阶段插入逻辑。它的核心能力包括:
- 转换响应数据:修改接口返回的结果。
- 异常处理:捕获并处理异常。
- 扩展功能:如日志记录、性能监控等。
生命周期中的位置
2.2 拦截器 vs 管道 vs 守卫
组件 | 执行阶段 | 修改响应能力 | 典型应用场景 |
---|---|---|---|
管道 | 控制器之前 | ❌ | 数据验证/转换 |
守卫 | 控制器之前 | ❌ | 权限认证 |
拦截器 | 控制器前后 | ✅ | 响应数据脱敏/日志记录 |
💡 关键区别:拦截器是唯一可以在控制器之后修改响应数据的组件。
3. 实现响应脱敏拦截器
3.1 创建拦截器
使用NestJS CLI快速生成拦截器:
nest g interceptor common/interceptors/serialize --flat
bash
3.2 核心实现逻辑
// serialize.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map } from 'rxjs/operators';
@Injectable()
export class SerializeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
map(data => {
// 脱敏操作
if (data?.password) delete data.password;
return data;
})
);
}
}
typescript
代码解析
map
操作符:用于对响应数据进行转换。delete
语句:动态删除敏感字段。
3.3 应用拦截器
3.3.1 控制器级应用
@Controller('users')
@UseInterceptors(SerializeInterceptor) // 应用拦截器
export class UserController {
@Post('signup')
async signup(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
}
typescript
3.3.2 全局应用
// main.ts
app.useGlobalInterceptors(new SerializeInterceptor());
typescript
4. 拦截器执行顺序实验
4.1 实验设计
通过添加多个拦截器,观察它们的执行顺序:
// global.interceptor.ts
intercept(context: ExecutionContext, next: CallHandler) {
console.log('全局拦截器 - 前');
return next.handle().pipe(
tap(() => console.log('全局拦截器 - 后'))
);
}
// controller.interceptor.ts
intercept(context: ExecutionContext, next: CallHandler) {
console.log('控制器拦截器 - 前');
return next.handle().pipe(
tap(() => console.log('控制器拦截器 - 后'))
);
}
typescript
4.2 预期执行顺序
5. 安全实践扩展
5.1 动态字段删除
支持配置多个敏感字段:
const sensitiveFields = ['password', 'token', 'ssn'];
sensitiveFields.forEach(field => {
if (data[field]) delete data[field];
});
typescript
5.2 深度脱敏处理
递归遍历对象,删除嵌套的敏感字段:
function deepDeleteSensitive(obj: any) {
if (typeof obj !== 'object') return;
Object.keys(obj).forEach(key => {
if (sensitiveFields.includes(key)) {
delete obj[key];
} else {
deepDeleteSensitive(obj[key]);
}
});
}
typescript
💡 推荐工具:使用class-transformer
的@Exclude()
装饰器,实现更优雅的脱敏方案。
6. 总结与作业
6.1 核心要点总结
技术 | 关键能力 | 适用场景 |
---|---|---|
拦截器 | 修改响应数据 | 接口脱敏/日志记录 |
管道 | 验证/转换输入数据 | DTO验证/数据格式化 |
守卫 | 访问控制 | 身份认证/权限检查 |
6.2 课后作业
- 实践任务:创建三个拦截器(全局/控制器级/路由级),并测试它们的执行顺序。
- 扩展功能:实现动态配置敏感字段的功能。
- 思考题:拦截器能否用于请求数据的修改?为什么?
💡 参考资料:
希望这节课能帮助大家掌握拦截器的核心用法,并在实际项目中灵活应用!如果有任何问题,欢迎随时提问。🎉
1. 接口安全风险与脱敏需求
1.1 敏感数据暴露风险
在现代Web应用中,接口直接返回包含敏感字段的完整用户对象会带来严重的安全隐患。以用户注册接口为例,常见的风险包括:
- 数据泄露风险:
- 密码明文传输可能被中间人攻击截获
- 用户隐私信息(如手机号、邮箱)可能被恶意利用
- 合规性问题:
- 违反GDPR等数据保护法规
- 不符合PCI DSS等支付行业安全标准
// 不安全响应示例 - 暴露了密码等敏感信息
{
"id": 1,
"username": "test",
"password": "123456", // 严重安全隐患
"email": "test@example.com",
"phone": "13800138000"
}
javascript
💡 安全规范参考:
- OWASP API安全Top 10要求:必须对敏感信息进行脱敏处理
- RESTful API设计最佳实践建议:响应数据应遵循最小权限原则
1.2 基础脱敏方案
方案实现
最简单的脱敏方式是在控制器中手动删除敏感字段:
// 基础脱敏实现示例
async createUser(@Body() dto: CreateUserDto) {
const user = await this.userService.create(dto);
// 手动删除敏感字段
delete user.password;
delete user.salt;
delete user.creditCard;
return user;
}
typescript
方案缺陷分析
- 维护性问题:
- 当敏感字段增加到10个以上时,代码会变得冗长难维护
- 每个需要脱敏的接口都需要重复编写类似代码
- 变更风险:
- 字段名修改时(如password改为passwd),容易遗漏更新删除逻辑
- 新加入的开发人员可能不了解需要删除哪些字段
- 代码质量问题:
- 违反DRY(Don't Repeat Yourself)原则
- 难以统一管理脱敏策略
- 性能影响:
- 大量delete操作会影响响应速度
- 深拷贝对象时内存开销较大
⚠️ 实际案例: 某电商平台因未对接口返回的信用卡CVV码进行脱敏,导致数万用户支付信息泄露,最终被处以巨额罚款。
改进方向
建议采用以下更专业的解决方案:
- 使用拦截器统一处理
- 通过DTO定义返回结构
- 采用专业的数据脱敏库
- 实现动态字段过滤配置
// 改进后的DTO方案示例
export class SafeUserDto {
id: number;
username: string;
// 显式排除敏感字段
@Exclude()
password: string;
}
typescript
💡 扩展思考: 如何设计一个既能保证安全性,又不影响开发效率的脱敏方案?下节课我们将深入探讨拦截器的解决方案。
2. NestJS拦截器核心概念
2.1 拦截器定位与工作原理
生命周期中的关键位置
拦截器在NestJS请求生命周期中扮演着独特角色:
- 执行时机:
- 前置处理:在控制器方法执行前
- 后置处理:在控制器方法返回后
- 核心能力:
- 修改响应数据格式
- 包装异常响应
- 添加通用业务逻辑
💡 技术本质:基于RxJS的Observable实现,采用AOP(面向切面编程)思想
典型工作流程示例
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 前置处理
console.log('Before...');
return next.handle().pipe(
// 后置处理
map(data => ({ data, timestamp: Date.now() }))
);
}
typescript
2.2 与管道/守卫的深度对比
三组件功能矩阵
组件 | 执行阶段 | 修改请求 | 修改响应 | 中断流程 | 典型应用场景 |
---|---|---|---|---|---|
管道 | 控制器之前 | ✅ | ❌ | ❌ | 数据验证/类型转换 |
守卫 | 控制器之前 | ❌ | ❌ | ✅ | 权限认证/访问控制 |
拦截器 | 控制器前后 | ❌ | ✅ | ❌ | 响应格式化/日志记录 |
关键差异说明:
- 管道(Pipe):
- 专注于输入数据的清洗和验证
- 典型用例:DTO转换、参数校验
@Get() @UsePipes(ValidationPipe) findUser(@Query() query: SearchDto) {}
typescript - 守卫(Guard):
- 负责请求的准入控制
- 典型用例:JWT验证、角色检查
@UseGuards(JwtAuthGuard) @Post('create') createItem() {}
typescript - 拦截器(Interceptor):
- 专注于输出数据的处理和增强
- 典型用例:
- 统一响应格式封装
- 敏感数据过滤
- 性能日志记录
@UseInterceptors(LoggingInterceptor) @Get('profile') getProfile() {}
typescript
组合使用示例
💡 最佳实践建议:
- 守卫应保持精简,只做权限判断
- 管道专注于数据清洗,避免业务逻辑
- 拦截器适合处理跨切面关注点
- 三者配合使用可实现完整的安全防护链
常见误区警示
- ❌ 在拦截器中修改请求参数(应使用管道)
- ❌ 在守卫中处理响应数据(应使用拦截器)
- ❌ 在管道中中断请求流程(应使用守卫)
扩展阅读:
3. 实现响应脱敏拦截器
3.1 创建拦截器
命令详解
nest g interceptor common/interceptors/serialize --flat
bash
nest g interceptor
: 生成拦截器的Nest CLI命令common/interceptors
: 推荐存放位置,保持项目结构清晰--flat
: 不创建额外文件夹- 生成文件:
serialize.interceptor.ts
💡 最佳实践:
- 将拦截器放在
common
目录下,方便跨模块复用 - 按功能命名(如
SerializeInterceptor
),不要使用泛型名称 - 对于小型项目可以使用
--flat
,大型项目建议按功能分目录
生成文件结构
// 自动生成的基础模板
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class SerializeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle();
}
}
typescript
3.2 核心实现逻辑
完整实现方案
// serialize.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map } from 'rxjs/operators';
@Injectable()
export class SerializeInterceptor implements NestInterceptor {
private readonly sensitiveFields = ['password', 'token', 'creditCard'];
intercept(context: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
map(data => {
// 处理数组响应
if (Array.isArray(data)) {
return data.map(item => this.removeSensitiveFields(item));
}
// 处理单对象响应
return this.removeSensitiveFields(data);
})
);
}
private removeSensitiveFields(data: any) {
if (!data || typeof data !== 'object') return data;
// 创建新对象避免修改原数据
const result = { ...data };
// 删除敏感字段
this.sensitiveFields.forEach(field => {
if (result[field] !== undefined) {
delete result[field];
}
});
// 处理嵌套对象
Object.keys(result).forEach(key => {
if (typeof result[key] === 'object') {
result[key] = this.removeSensitiveFields(result[key]);
}
});
return result;
}
}
typescript
代码优化点:
- 动态配置敏感字段:通过类属性维护可配置的敏感字段列表
- 深度脱敏:递归处理嵌套对象
- 数组支持:同时处理单个对象和数组响应
- 不可变操作:避免直接修改原数据
⚠️ 注意事项:
- 对于大数据量响应,要考虑性能影响
- 敏感字段建议通过环境变量配置
- 可结合class-transformer实现更优雅的解决方案
3.3 应用拦截器
3.3.1 控制器级应用
// user.controller.ts
import { SerializeInterceptor } from '../common/interceptors/serialize.interceptor';
@Controller('users')
@UseInterceptors(SerializeInterceptor)
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('signup')
async signup(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.userService.findById(+id);
}
}
typescript
应用场景:
- 需要精细控制哪些接口需要脱敏
- 不同控制器可能需要不同的脱敏策略
- 特定接口需要特殊处理时
3.3.2 全局应用
// main.ts
import { SerializeInterceptor } from './common/interceptors/serialize.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局注册拦截器
app.useGlobalInterceptors(
new SerializeInterceptor(),
// 可以组合其他全局拦截器
new LoggingInterceptor()
);
await app.listen(3000);
}
bootstrap();
typescript
最佳实践建议:
- 全局拦截器适合通用性强的处理逻辑
- 可以通过模块方式注册,提高可测试性:
// app.module.ts @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: SerializeInterceptor, }, ], }) export class AppModule {}
typescript - 全局拦截器执行顺序优先于控制器级拦截器
混合使用场景
@Controller('users')
@UseInterceptors(LoggingInterceptor) // 控制器级
export class UserController {
@Post('signup')
@UseInterceptors(CustomSerializeInterceptor) // 方法级
async signup(@Body() dto: CreateUserDto) {
// ...
}
}
typescript
💡 扩展思考:
- 如何实现不同接口的差异化脱敏策略?
- 如何结合装饰器实现更灵活的字段级控制?
- 在微服务架构中如何统一处理脱敏逻辑?
性能优化提示:
- 对于高性能要求的接口,可以考虑缓存脱敏结果
- 使用WeakMap避免内存泄漏
- 对于大量数据,可以采用流式处理
4. 拦截器执行顺序实验
4.1 实验设计与实现
实验准备
我们需要创建两个拦截器来验证执行顺序:
- 全局拦截器:注册在应用级别
- 控制器拦截器:注册在控制器级别
// global.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { tap } from 'rxjs/operators';
@Injectable()
export class GlobalInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
console.log('全局拦截器 - 前');
const now = Date.now();
return next.handle().pipe(
tap(() => {
console.log(`全局拦截器 - 后,耗时:${Date.now() - now}ms`);
})
);
}
}
// controller.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { tap } from 'rxjs/operators';
@Injectable()
export class ControllerInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
console.log('控制器拦截器 - 前');
const now = Date.now();
return next.handle().pipe(
tap(() => {
console.log(`控制器拦截器 - 后,耗时:${Date.now() - now}ms`);
})
);
}
}
typescript
实验配置
// main.ts (全局注册)
app.useGlobalInterceptors(new GlobalInterceptor());
// user.controller.ts (控制器级注册)
@Controller('users')
@UseInterceptors(ControllerInterceptor)
export class UserController {
@Get()
findAll() {
console.log('路由处理逻辑');
return [{ id: 1, name: '测试用户' }];
}
}
typescript
4.2 执行顺序分析与验证
预期执行流程
实际控制台输出
全局拦截器 - 前
控制器拦截器 - 前
路由处理逻辑
控制器拦截器 - 后,耗时:5ms
全局拦截器 - 后,耗时:8ms
text
关键发现
- 洋葱模型:拦截器执行遵循洋葱模型,全局→控制器→路由→控制器→全局
- 耗时统计:后置处理时间包含前置处理到后置之间的所有逻辑耗时
- 嵌套关系:全局拦截器包裹控制器拦截器
4.3 高级实验场景
场景1:多级拦截器嵌套
@Controller('users')
@UseInterceptors(ControllerInterceptor, AnotherInterceptor)
export class UserController {
@Get()
@UseInterceptors(MethodInterceptor)
findAll() {
// ...
}
}
typescript
执行顺序:
- 全局拦截器前
- 控制器拦截器1前
- 控制器拦截器2前
- 方法拦截器前
- 路由逻辑
- 方法拦截器后
- 控制器拦截器2后
- 控制器拦截器1后
- 全局拦截器后
场景2:异常情况测试
intercept(context: ExecutionContext, next: CallHandler) {
console.log('拦截器前');
return next.handle().pipe(
catchError(err => {
console.log('拦截器捕获异常');
throw err;
}),
tap(() => console.log('拦截器后'))
);
}
typescript
异常时的执行顺序:
- 拦截器前
- 抛出异常
- 拦截器捕获异常
- (不会执行tap后置逻辑)
4.4 实验结论
- 顺序规则:
- 全局拦截器最先执行最后结束
- 注册顺序决定同级别拦截器执行顺序
- 后注册的拦截器更靠近业务逻辑
- 性能影响:
- 每个拦截器会增加约0.5-2ms的延迟
- 嵌套越深性能开销越大
- 设计建议:
- 调试技巧:
- 使用
console.time()
/console.timeEnd()
精确测量 - 在拦截器中添加请求ID方便追踪
- 结合Nest的
ExecutionContext
获取更多上下文信息
- 使用
4.5 生产环境建议
- 拦截器分层:
- 全局层:监控、日志等基础设施
- 控制器层:业务通用逻辑(如脱敏)
- 方法层:特定功能增强
- 性能优化:
// 使用memoization优化重复计算 const memoized = memoize(expensiveOperation); return next.handle().pipe( map(data => memoized(data)) );
typescript - 最佳实践:
- 避免在拦截器中实现复杂业务逻辑
- 保持拦截器职责单一
- 为拦截器编写单元测试
扩展思考:
- 如何实现异步拦截器?
- 在微服务场景下拦截器执行顺序有何不同?
- 如何动态启用/禁用拦截器?
5. 安全实践扩展
5.1 增强脱敏方案
动态配置方案
// 支持从环境变量读取敏感字段配置
const sensitiveFields = process.env.SENSITIVE_FIELDS?.split(',') || [
'password',
'token',
'ssn',
'creditCard',
'cvv'
];
// 支持正则表达式匹配
const sensitivePatterns = [
/pass(word|code)?/i,
/(auth|access)_?token/i,
/^cc_/
];
function sanitizeData(data: any) {
if (!data) return data;
// 处理普通字段
sensitiveFields.forEach(field => {
if (data[field] !== undefined) {
delete data[field];
}
});
// 处理正则匹配字段
Object.keys(data).forEach(key => {
if (sensitivePatterns.some(pattern => pattern.test(key))) {
delete data[key];
}
});
return data;
}
typescript
类型安全增强
// 定义敏感数据类型
type SensitiveData = {
[key: string]: any;
password?: string;
token?: string;
};
// 使用泛型保证类型安全
function sanitize<T extends SensitiveData>(data: T): Omit<T, keyof SensitiveData> {
const result = { ...data };
sensitiveFields.forEach(field => delete result[field]);
return result as Omit<T, keyof SensitiveData>;
}
typescript
5.2 深度脱敏处理
递归脱敏优化版
function deepSanitize(obj: any, depth = 5): any {
if (depth <= 0 || !obj || typeof obj !== 'object') return obj;
// 处理数组
if (Array.isArray(obj)) {
return obj.map(item => deepSanitize(item, depth - 1));
}
// 处理对象
const result = { ...obj };
Object.keys(result).forEach(key => {
if (sensitiveFields.includes(key)) {
delete result[key];
} else {
result[key] = deepSanitize(result[key], depth - 1);
}
});
return result;
}
typescript
使用class-transformer的优雅方案
import { Exclude, Expose, classToPlain } from 'class-transformer';
// 定义安全DTO
@Exclude()
class SafeUserDto {
@Expose()
id: number;
@Expose()
username: string;
// 自动排除
password: string;
@Expose({ name: 'maskedPhone' })
get phone(): string {
return this._phone?.replace(/^(\d{3})\d+(\d{4})$/, '$1****$2');
}
constructor(private _phone: string) {}
}
// 使用方式
const user = { id: 1, username: 'test', password: '123', phone: '13800138000' };
const safeData = classToPlain(new SafeUserDto(user.phone));
// 输出: { id: 1, username: "test", maskedPhone: "138****8000" }
typescript
5.3 性能优化方案
备忘录模式缓存
const sanitizeCache = new WeakMap();
function cachedSanitize(data: any) {
if (!data || typeof data !== 'object') return data;
if (sanitizeCache.has(data)) {
return sanitizeCache.get(data);
}
const result = deepSanitize(data);
sanitizeCache.set(data, result);
return result;
}
typescript
流式处理大数据
import { from, map } from 'rxjs';
function streamSanitize(dataStream: Observable<any>) {
return dataStream.pipe(
map(chunk => {
if (Array.isArray(chunk)) {
return chunk.map(item => deepSanitize(item));
}
return deepSanitize(chunk);
})
);
}
typescript
5.4 安全审计增强
脱敏日志记录
function sanitizeWithAudit(data: any) {
const originalKeys = Object.keys(data);
const result = sanitizeData(data);
const removedKeys = originalKeys.filter(
key => !Object.keys(result).includes(key)
);
if (removedKeys.length) {
auditLog(`Removed sensitive fields: ${removedKeys.join(', ')}`);
}
return result;
}
typescript
5.5 混合脱敏策略
分级脱敏方案
const sanitizeStrategies = {
strict: ['password', 'token'],
moderate: ['email', 'phone'],
lenient: ['address']
};
function applySanitizeProfile(data: any, profile: keyof typeof sanitizeStrategies) {
const fields = sensitiveFields.concat(sanitizeStrategies[profile]);
return sanitizeData(data, fields);
}
typescript
组合使用示例
💡 生产环境建议:
- 对于金融类应用,建议采用多层脱敏策略
- 敏感字段配置应该支持热更新
- 定期检查脱敏规则的有效性
- 结合静态代码分析工具检测未脱敏的接口
扩展阅读:
6. 总结与作业
6.1 核心要点总结
NestJS 核心组件对比
技术 | 关键能力 | 适用场景 | 典型应用示例 |
---|---|---|---|
拦截器 | 修改响应数据 | 接口脱敏/日志记录 | 敏感字段过滤、响应时间统计 |
管道 | 验证/转换输入数据 | DTO验证/数据格式化 | 请求参数校验、数字类型转换 |
守卫 | 访问控制 | 身份认证/权限检查 | JWT 验证、角色权限拦截 |
关键特性对比
💡 设计原则:
- 单一职责:每个组件只解决一个问题
- 执行顺序:守卫 → 管道 → 控制器 → 拦截器
- 组合使用:例如先用守卫鉴权,再用管道校验数据格式
6.2 课后作业
作业要求
1️⃣ 基础任务
- 创建三个拦截器并标记执行顺序:
nest g interceptor global-logger --flat nest g interceptor controller-logger --flat nest g interceptor route-logger --flat
bash - 在日志中输出唯一标识(如
[GLOBAL]
、[CONTROLLER]
、[ROUTE]
)
2️⃣ 进阶任务
- 测试以下注册方式的执行顺序差异:
// 方式1: 全局注册 app.useGlobalInterceptors(new GlobalLogger()); // 方式2: 控制器级注册 @UseInterceptors(ControllerLogger) @Controller('test') // 方式3: 路由级注册 @Get() @UseInterceptors(RouteLogger)
typescript - 记录控制台输出并分析嵌套关系
3️⃣ 扩展任务
- 实现动态敏感字段配置:
@UseInterceptors(new SanitizeInterceptor(['token', 'ssn']))
typescript - 支持从环境变量读取配置:
SENSITIVE_FIELDS="password,creditCard"
bash
示例代码参考
// 动态配置拦截器
@Injectable()
export class SanitizeInterceptor implements NestInterceptor {
constructor(private readonly fields: string[]) {}
intercept(context: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
map(data => {
this.fields.forEach(field => delete data[field]);
return data;
})
);
}
}
// 使用方式
@Get('profile')
@UseInterceptors(new SanitizeInterceptor(process.env.SENSITIVE_FIELDS.split(',')))
getProfile() { ... }
typescript
验证方法
- 发送包含敏感字段的请求:
curl http://localhost:3000/users/1
bash - 检查响应是否已脱敏
- 查看控制台日志顺序是否符合预期
6.3 学习资源推荐
- 官方文档
- 扩展阅读
- 《Node.js 安全最佳实践》- OWASP 指南
- 《Enterprise Angular》- 拦截器设计模式
- 工具推荐
💡 作业提交:
- 将代码上传至 GitHub 并提交执行日志截图
- 用 Markdown 表格对比不同注册方式的执行顺序差异
↑