我需要在我的Nest.js应用程序中从Stripe访问webhook请求的原始主体。
在this示例之后,我将以下内容添加到具有需要原始主体的控制器方法的模块中。
function addRawBody(req, res, next) {
req.setEncoding('utf8');
let data = '';
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', () => {
req.rawBody = data;
next();
});
}
export class SubscriptionModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(addRawBody)
.forRoutes('subscriptions/stripe');
}
}
在控制器中我使用@Req() req
and然后使用req.rawBody
来获取原始体。我需要原始主体,因为Stripe api的constructEvent正在使用它来验证请求。
问题是请求被卡住了。似乎req.on既不是数据也不是结束事件。所以next()
不是在中间件中调用的。
我也尝试使用像raw-body
这样的here,但我得到了相同的结果。在那种情况下,req.readable总是假的,所以我也被困在那里。
我想这是Nest.js的一个问题,但我不确定......
我昨晚遇到了类似的问题,试图验证一个Slack令牌。
我们使用的解决方案确实需要从核心Nest App禁用bodyParser,然后在使用原始请求主体向请求添加新的rawBody
密钥后重新启用它。
const app = await NestFactory.create(AppModule, {
bodyParser: false
});
const rawBodyBuffer = (req, res, buf, encoding) => {
if (buf && buf.length) {
req.rawBody = buf.toString(encoding || 'utf8');
}
};
app.use(bodyParser.urlencoded({verify: rawBodyBuffer, extended: true }));
app.use(bodyParser.json({ verify: rawBodyBuffer }));
然后在我的中间件中,我可以像这样访问它:
const isVerified = (req) => {
const signature = req.headers['x-slack-signature'];
const timestamp = req.headers['x-slack-request-timestamp'];
const hmac = crypto.createHmac('sha256', 'somekey');
const [version, hash] = signature.split('=');
// Check if the timestamp is too old
// tslint:disable-next-line:no-bitwise
const fiveMinutesAgo = ~~(Date.now() / 1000) - (60 * 5);
if (timestamp < fiveMinutesAgo) { return false; }
hmac.update(`${version}:${timestamp}:${req.rawBody}`);
// check that the request signature matches expected value
return timingSafeCompare(hmac.digest('hex'), hash);
};
export async function slackTokenAuthentication(req, res, next) {
if (!isVerified(req)) {
next(new HttpException('Not Authorized Slack', HttpStatus.FORBIDDEN));
}
next();
}
闪耀!
对于寻找更优雅解决方案的人,请关闭bodyParser
中的main.ts
为rawbody创建2个middlewares
,为json-parsed-body创建另一个import {Request, Response} from 'express';
import * as bodyParser from 'body-parser';
import {Injectable, NestMiddleware} from '@nestjs/common';
@Injectable()
export class JsonBodyMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => any) {
bodyParser.json()(req, res, next);
}
}
JSON-body.middleware.ts
import {Injectable, NestMiddleware} from '@nestjs/common';
import {Request, Response} from 'express';
import * as bodyParser from 'body-parser';
@Injectable()
export class RawBodyMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => any) {
bodyParser.raw({type: '*/*'})(req, res, next);
}
}
原始body.middleware.ts
app.module.ts
并将中间件应用于...
public configure(consumer: MiddlewareConsumer): void {
consumer
.apply(RawBodyMiddleware)
.forRoutes({
path: '/stripe-webhooks',
method: RequestMethod.POST,
})
.apply(JsonBodyMiddleware)
.forRoutes('*');
}
中的适当路线。
app.module.ts
req.rawbody
BTW express
已经从https://github.com/expressjs/express/issues/897长期被移除 - bodyParser
这是因为默认情况下使用bodyParser
中间件,并且在中间件启动时已经消耗了主体。你可以在你的main.ts
关闭const app = await NestFactory.create(AppModule, {bodyParser: false});
^^^^^^^^^^^^^^^^^^
:
bodyParser.json
您应该注意,在大多数其他情况下,您可能希望使用this thread,因此将此添加到所有其他路由。您可以使用负正则表达式从中间件中排除一个特定路由,请参阅例如qazxswpoi。