Cookie,Session和SSE—Nestjs文档更新(译)
作为活跃的Nodejs开发框架,Nestjs处于快速迭代与更新中,在最新的Nestjs文档中,作者更新了Cookie,Session和SSE(服务器事件推送)内容,将安全部分单独成章并增加了权限、加密与哈希内容。更新的内容包括。
Cookies
一个
HTTP cookie
是指存储在用户浏览器中的一小段数据。
Cookies
被设计为创建一种可靠的机制让网站来记录状态信息。当用户再次访问网站时,发出的请求会自带
cookie
。
在
Express
中使用(默认)
首先安装需要的包(以及
TypeScript
用户需要的类型包):
$ npm i cookie-parser
$ npm i -D @types/cookie-parser
安装完成后,将
cookie-parser
配置为全局中间件(例如在
main.ts
文件中)。
import * as cookieParser from 'cookie-parser';
// somewhere in your initialization file
app.use(cookieParser());
可以向
cookieParser
中间件中传递一些参数:
-
secret
: 一个字符串或者数组,用来给cookie
签名。如果不指定这个选项,将不解析签名的cookie
。如果提供了一个字符串,那么它会被用来作为secret
。如果提供了一个数组,将尝试依次使用其元素来作为secret
解析cookie
。 -
option
:一个作为第二个参数传递给cookie.parse
的对象,参见[cookie](https://www.npmjs.org/package/cookie)
来了解更多内容。
该中间件将从请求的头文件中解析Cookie
并将其数据作为req.cookies
暴露出来。如果提供了secret
,将暴露为req.signedCokkies
。这些属性以cookie
名称和属性的键值对保存。
当提供了secret
时,该中间件将解析并验证所有签名的cookie
并将其值从req.cookies
移动到req.signedCookies
。签名cookie
是指包含s:
前缀的cookie
。验证失败的签名cookie
值会被替换为false
而不是被篡改过的值。
当这些完成后,就可以从路径处理程序中读取cookie
了,例如:
@Get()
findAll(@Req() request: Request) {
console.log(request.cookies); // or "request.cookies['cookieKey']"
// or console.log(request.signedCookies);
}
@Req()
装饰器从@nestjs/common
中引入,@Request
从express
中引入。
要在输出的响应中附加cookie
,使用Response#cookie()
方法:
@Get()
findAll(@Res({ passthrough: true }) response: Response) {
response.cookie('key', 'value')
}
如果你想把相应处理逻辑留给框架,需要将passthrough
参数设置为true
,如上所示。参见 这里
@Res()
装饰器从@nestjs/common
中引入,@Response
从express
中引入。
在
Fastify
中使用
首先安装需要的包:
npm i fastify-cookie
安装完成后,注册`fastify-cookie`插件。
```TypeScript
import fastifyCookie from 'fastify-cookie';
// somewhere in your initialization file
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
app.register(fastifyCookie, {
secret: 'my-secret', // for cookies signature
});
当这些完成后,就可以从路径处理程序中读取
cookie
了,例如:
@Get()
findAll(@Req() request: FastifyRequest) {
console.log(request.cookies); // or "request.cookies['cookieKey']"
}
@Req()
装饰器从@nestjs/common
中引入,FastifyRequest
从fastify
中引入。
要在输出的响应中附加cookie
,使用FastifyReply#setCookie()
方法:
@Get()
findAll(@Res({ passthrough: true }) response: FastifyReply) {
response.setCookie('key', 'value')
}
要了解更多
FastifyReply#setCookie()
,可参见
这里
。
如果你想把相应处理逻辑留给框架,需要将passthrough
参数设置为true
,如上所示。参见 这里
@Res()
装饰器从@nestjs/common
中引入,FastifyReply
从fastify
中引入。
创建一个自定义装饰器(跨平台)
可以创建一个自定义装饰器,来提供一个方便易用、声明清晰的方式来处理
cookie
。
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Cookies = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return data ? request.cookies?.[data] : request.cookies;
);
这个
@Cookie()
装饰器将解析所有
cookie
,或者从
req.cookie
对象中解析一个命名的
cookie
并用其值来填充解析的参数。
现在就可以使用该装饰器,从路径处理程序中读取
cookie
了,例如:
@Get()
findAll(@Cookies('name') name: string) {}
Session(会话)
HTTP session
提供了一个用于在不同请求间存储信息的方法,这在
MVC
架构应用中非常有用。
在
Express
中使用(默认)
首先安装需要的包(以及
TypeScript
用户需要的类型包):
$ npm i express-session
$ npm i -D @types/express-session
安装完成后,将
express-session
配置为全局中间件(例如在
main.ts
文件中)。
import * as session from 'express-session';
// somewhere in your initialization file
app.use(
session({
secret: 'my-secret',
resave: false,
saveUninitialized: false,
);
在生产环境中,有意的默认不在服务器端提供会话存储。因为这在很多场合下会造成内存泄漏,不能扩展到单个进程,因此仅用于调试和开发环境。参见 官方仓库 。
secret
用于加密该会话 IDcookie
,它可以是一个字符串用于单一加密,或者数组用来多重加密。如果提供了一个数组,只有第一个元素被用来加密会话 IDcookie
,其他元素将被用于验证签名请求。密码本身不应该过于容易被人工解析,最好使用一组随机字符。
使能resave
选项会强制重新保存会话即使在请求过程中它未被修改过。其默认值为true
,但不赞成使用默认值,在未来这个默认值将被修改。
类似地,使能saveUninitialized
选项将强制存储一个未初始化的会话。一个未初始化的会话可能是一个新的尚未修改的会话。配置为false
用于登陆会话是很有用的,可以减少服务器存储,或者遵循法律规定在存储用户cookie
前需要获得用户授权。配置为false
在一个客户端在无会话情况下建立多个请求的状况下会很有用。参见 这里 。
还可以给session
中间件传递更多参数,参见 API 文档 。
注意secure:true
是推荐选项。然而,它需要启用了https
的网站,也就是说,HTTPS
对安全cookie
来说是必须的。如果配置了secure
,但是通过HTTP
访问网站,将不会保存cookie
。如果你的node.js
在代理之后,并且启用了secure:true
选项,你需要在express
中配置trust proxy
选项。
当这些完成后,就可以从路径处理程序中读取session
了,例如:
@Get()
findAll(@Req() request: Request) {
req.session.visits = req.session.visits ? req.session.visits + 1 : 1;
}
@Req()
装饰器从@nestjs/common
中引入,@Request
从express
中引入。
另外,你也可以使用@Session()
装饰器来从请求解压一个session
对象:
@Get()
findAll(@Session() session: Record<string, any>) {
session.visits = session.visits ? session.visits + 1 : 1;
}
在
Fastify
中使用
首先安装需要的包:
$ npm i fastify-secure-session
安装完成后,注册`fastify-secure-session`插件。
```TypeScript
import secureSession from 'fastify-secure-session';
// somewhere in your initialization file
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
app.register(secureSession, {
secret: 'averylogphrasebiggerthanthirtytwochars',
salt: 'mq9hDxBVDbspDR6n',
});
你也可以预先生成一个key
(参见 指南 )或者使用 key 变化 。
当这些完成后,就可以从路径处理程序中读取session
了,例如:
@Get()
findAll(@Req() request: FastifyRequest) {
const visits = request.session.get('visits');
request.session.set('visits', visits ? visits + 1 : 1);
}
@Req()
装饰器从@nestjs/common
中引入,FastifyRequest
从fastify
中引入。
另外,你也可以使用@Session()
装饰器来从请求解压一个session
对象:
@Get()
findAll(@Session() session: secureSession.Session) {
const visits = session.get('visits');
session.set('visits', visits ? visits + 1 : 1);
}
@Session()
装饰器从@nestjs/common
中引入,secureSession.Session
从fastify-secure-session
中引入。(引入语句:import * as secureSession from 'fastify-secure-session'
).
事件
Event Emitter 事件发射器
包(
@nestjs/event-emitter
)提供了一个简单的观察者实现,允许你订阅和监听在你应用中发生的不同事件。事件服务器是将应用程序的不同部分解耦的一个伟大的方法,因为一个事件可以被不同的监听者监听,且他们之间并不互相依赖。
EventEmitterModule
在内部使用
eventmitter2
包。
开始
首先安装依赖。
$ npm i --save @nestjs/event-emitter
安装完成后,在
root
的
AppModule
中引入
EventEmitterModule
并且在
forRoot()
静态方法中运行:
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
imports: [
EventEmitterModule.forRoot()
export class AppModule {}
.forRoot()
调用与初始化事件发射器并注册应用程序中各个声明的事件监听器。注册发生在
onApplicationBootstrap
生命周期钩子开始的时候,以确保所有模块都已加载,所有定时任务已声明。
配置文件在
EventEmitter
实例中,在
forRoot()
方法中传递配置对象,如下:
EventEmitterModule.forRoot({
// set this to `true` to use wildcards
wildcard: false,
// the delimiter used to segment namespaces
delimiter: '.',
// set this to `true` if you want to emit the newListener event
newListener: false,
// set this to `true` if you want to emit the removeListener event
removeListener: false,
// the maximum amount of listeners that can be assigned to an event
maxListeners: 10,
// show event name in memory leak message when more than maximum amount of listeners is assigned
verboseMemoryLeak: false,
// disable throwing uncaughtException if an error event is emitted and it has no listeners
ignoreErrors: false,
});
分派事件
要分派一个事件(例如
fire
), 使用标准构造函数注入
EventEmitter2
。
constructor(private eventEmitter: EventEmitter2) {}
EventEmitter2
从@nestjs/event-emitter
包中导入。
然后在类中使用它:
this.eventEmitter.emit(
'order.created',
new OrderCreatedEvent({
orderId: 1,
payload: {},
);
监听事件
要声明一个事件监听器,用
@OnEvent()
装饰器装饰一个方法,装饰器在包含要执行代码的方法之前,如下:
@OnEvent('order.created')
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
// handle and process "OrderCreatedEvent" event
}
事件订阅器不能是请求范围的。
在简单的事件发射器中,第一个参数可以是字符串
或者符号
,在通配的事件发射器中,可以是字符串|符号|数组<字符串|符号>
。第二个参数(可选的)是一个监听器的选项对象( 参见这里 )。
@OnEvent('order.created', { async: true })
handleOrderCreatedEvent(payload: OrderCreatedEvent) {
// handle and process "OrderCreatedEvent" event
}
要使用命名空间或者通配符,传递
wildcard
选项到
EventEmitterModule#forRoot()
方法中。当命名空间/通配符启用时,事件可以是句点隔开的(
foo.bar
)形式或者数组(
['foo','bar']
),句点也可以配置为一个配置属性(
delimiter
)。命名空间启用时,你可以使用通配符订阅事件。
@OnEvent('order.*')
handleOrderEvents(payload: OrderCreatedEvent | OrderRemovedEvent | OrderUpdatedEvent) {
// handle and process an event
}
注意,这样的通配符仅对一个块有效。参数
order.*
将匹配例如
order.creted
和
order.shipped
事件,但不会匹配
order.delayed.out_of_stock
。要监听这样的事件,使用
多层通配符
模式(例如
**
)。见
EventEmitter2
文档
。
@OnEvent('**')
handleEverything(payload: any) {
// handle and process an event
}
EventEmitter2
类提供了一些有用的方法来和事件交互,例如waitFor
和onAny
,参见 这里 。
服务器端事件发送
服务器端事件发送(
SSE
)是一个服务器推送技术,用来使客户端在
HTTP
连接下自动接收服务器更新消息。每个消息以一个由一对新行符号作为结束的文字块发送(参见
这里
)。
使用
要在路径中使能服务器端事件发送(路径在控制器类中注册),用
@Sse()
装饰器注释该方法处理程序。
@Sse('sse')
sse(): Observable<MessageEvent> {
return interval(1000).pipe(map((_) => ({ data: { hello: 'world' } })));
}
@Sse()
装饰器从@nestjs/common
中导入,Observable, interval, 和 map
从rxjs
中导入。
服务器端事件发送路径必须返回Observable
流。
在上述示例中,我们定义了一个命名的sse
来生成实时更新,这些事件可以通过[EventSource API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
监听。
sse
方法返回一个Observable
并发送多个MessageEvent
(在本例中,它每秒发射一个新的MessageEvent
)。MessageEvent
应该与下列接口相匹配。
export interface MessageEvent {
data: string | object;
id?: string;