3-3 接口安全:引入拦截器对接口进行脱敏处理
本节引入 NestJS 拦截器(Interceptor)概念,讲解其在响应数据脱敏场景中的应用,并对比守卫、管道、拦截器三种机制的职责划分。
接口脱敏的必要性
注册接口直接响应用户完整信息(包括密码),存在严重安全隐患。最直接的处理方式:
// 方式一:手动删除敏感字段(不推荐)
const user = await this.userRepository.create(dto);
delete user.password;
return user;
typescript
问题:字段多时容易遗漏,字段重命名后可能导致删除失败。
拦截器核心概念
拦截器是 NestJS 中面向切面编程(AOP)的核心组件,提供以下能力:
- 在函数执行前后绑定额外逻辑
- 转换函数返回的结果
- 转换函数抛出的异常
- 扩展/重写函数行为
拦截器接口定义:
interface NestInterceptor<T, R> {
intercept(context: ExecutionContext, next: CallHandler): Observable<R>;
}
typescript
| 参数 | 说明 |
|---|---|
context | 执行上下文,与守卫完全相同,可获取请求对象 |
next | 调用处理程序,next.handle() 返回 RxJS Observable |
关键:必须调用 next.handle(),否则路由处理程序不会执行。
守卫 vs 管道 vs 拦截器
请求生命周期中的位置:
请求 → 中间件 → 守卫 → 管道 → 控制器/服务 → 拦截器 → 响应
↑ ↑
授权判断 响应转换
(能否访问?) (数据长什么样?)
text
| 组件 | 位置 | 职责 | 能否访问上下文 |
|---|---|---|---|
| 管道 (Pipe) | 控制器之前 | 数据校验、转换 | 是 |
| 守卫 (Guard) | 控制器之前 | 授权判断 | 是 |
| 拦截器 (Interceptor) | 控制器前后皆可 | 日志、响应转换、缓存 | 是 |
创建响应拦截器
使用 CLI 生成拦截器:
nest g interceptor common/interceptors/serialize --flat --no-spec
bash
基础结构:
// common/interceptors/serialize.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class SerializeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('拦截器执行之前');
return next.handle().pipe(
map((data) => {
console.log('拦截器执行之后', data);
// data 即为路由处理程序的返回值
// 可在此处进行脱敏处理
return data;
}),
);
}
}
typescript
next.handle() 返回 RxJS Observable,通过 pipe + map 操作符可以对响应数据进行转换。
使用拦截器
路由级别:
@UseInterceptors(SerializeInterceptor)
@Post('signup')
signUp() { ... }
typescript
控制器级别:
@Controller('auth')
@UseInterceptors(SerializeInterceptor)
export class AuthController { ... }
typescript
全局级别:
// main.ts
app.useGlobalInterceptors(new SerializeInterceptor());
typescript
拦截器层级与执行顺序
全局拦截器(前置)
→ 控制器拦截器(前置)
→ 路由拦截器(前置)
→ 控制器方法执行
→ 路由拦截器(后置:map)
→ 控制器拦截器(后置:map)
→ 全局拦截器(后置:map)
text
在拦截器内删除敏感字段的示例:
return next.handle().pipe(
map((data) => {
delete data.password;
return data;
}),
);
typescript
小结
| 知识点 | 要点 |
|---|---|
| 拦截器本质 | AOP 组件,基于 RxJS Observable |
next.handle() | 必须调用,否则路由不执行 |
map 操作符 | 在路由执行后转换响应数据 |
| 脱敏方式 | map 内部 delete 或 class-transformer 序列化 |
| 三个层级 | 全局 > 控制器 > 路由,后置 map 反向执行 |
| 与 Guard 区别 | Guard 负责授权,Interceptor 负责响应转换 |
↑