Limitation de Taux
Une technique courante pour protéger les applications contre les attaques par force brute est la limitation de taux. Pour commencer, vous devez installer le package @nestjs/throttler
.
$ npm i --save @nestjs/throttler
Une fois l’installation terminée, le module ThrottlerModule
peut être configuré comme n’importe quel autre package Nest avec les méthodes forRoot
ou forRootAsync
.
app.module.ts
@Module({ imports: [ ThrottlerModule.forRoot([ { ttl: 60000, limit: 10, } ]), ],})export class AppModule {}
Ce qui précède définira les options globales pour ttl
, le temps de vie en millisecondes, et limit
, le nombre maximum de requêtes dans la période de ttl
, pour les routes de votre application qui sont protégées.
Une fois que le module a été importé, vous pouvez choisir comment vous souhaitez lier le ThrottlerGuard
. Tout type de liaison mentionnée dans la section gardes est acceptable. Si vous souhaitez lier la garde globalement, par exemple, vous pouvez le faire en ajoutant ce fournisseur à n’importe quel module :
{ provide: APP_GUARD, useClass: ThrottlerGuard,}
Définitions de Limitation de Taux Multiples
Il peut arriver que vous souhaitiez configurer plusieurs définitions de limitation de taux, comme pas plus de 3 appels en une seconde, 20 appels en 10 secondes, et 100 appels en une minute. Pour ce faire, vous pouvez configurer vos définitions dans le tableau avec des options nommées, qui peuvent être référencées plus tard dans les décorateurs @SkipThrottle()
et @Throttle()
pour changer les options à nouveau.
app.module.ts
@Module({ imports: [ ThrottlerModule.forRoot([ { name: 'court', ttl: 1000, limit: 3, }, { name: 'moyen', ttl: 10000, limit: 20, }, { name: 'long', ttl: 60000, limit: 100, }, ]), ],})export class AppModule {}
Personnalisation
Il peut y avoir un moment où vous souhaitez lier le garde à un contrôleur ou globalement, mais vouloir désactiver la limitation de taux pour un ou plusieurs de vos points de terminaison. Pour cela, vous pouvez utiliser le décorateur @SkipThrottle()
, pour annuler le limitation pour une classe entière ou une seule route. Le décorateur @SkipThrottle()
peut également prendre un objet de clés de chaîne avec des valeurs booléennes s’il y a un cas où vous souhaitez exclure la plupart d’un contrôleur, mais pas chaque route, et le configurer par ensemble de limitations si vous en avez plus d’un. Si vous ne passez pas d’objet, la valeur par défaut est d’utiliser { default: true }
.
@SkipThrottle()@Controller('users')export class UsersController {}
Ce décorateur @SkipThrottle()
peut être utilisé pour ignorer une route ou une classe ou pour annuler le saut d’une route dans une classe qui est ignorée.
@SkipThrottle()@Controller('users')export class UsersController { // La limitation de taux est appliquée à cette route. @SkipThrottle({ default: false }) dontSkip() { return 'Liste des utilisateurs avec limitation de taux.'; }
// Cette route ignorera la limitation de taux. doSkip() { return 'Liste des utilisateurs sans limitation de taux.'; }}
Il existe également le décorateur @Throttle()
qui peut être utilisé pour remplacer le limit
et le ttl
définis dans le module global, pour offrir des options de sécurité plus strictes ou plus lâches. Ce décorateur peut être utilisé sur une classe ou une fonction également. Avec la version 5 et ultérieure, le décorateur prend un objet avec la chaîne relative au nom de l’ensemble de limitation, et un objet avec les clés limit
et ttl
et des valeurs entières, similaire aux options passées au module racine. Si vous n’avez pas de nom défini dans vos options d’origine, utilisez la chaîne default
. Vous devez le configurer de la manière suivante :
// Remplacer la configuration par défaut pour la limitation de taux et sa durée.@Throttle({ default: { limit: 3, ttl: 60000 } })@Get()findAll() { return "Liste des utilisateurs avec un taux de limitation personnalisé.";}
Proxies
Si votre application fonctionne derrière un serveur proxy, vérifiez les options spécifiques de l’adaptateur HTTP (express et fastify) pour l’option trust proxy
et activez-la. Cela vous permettra d’obtenir l’adresse IP d’origine dans l’en-tête X-Forwarded-For
, et vous pouvez remplacer la méthode getTracker()
pour extraire la valeur de l’en-tête plutôt que de req.ip
. L’exemple suivant fonctionne avec express et fastify :
import { ThrottlerGuard } from '@nestjs/throttler';import { Injectable } from '@nestjs/common';
@Injectable()export class ThrottlerBehindProxyGuard extends ThrottlerGuard { protected async getTracker(req: Record<string, any>): Promise<string> { return req.ips.length ? req.ips[0] : req.ip; // individualiser l'extraction IP selon vos propres besoins }}
Websockets
Ce module peut fonctionner avec des websockets, mais nécessite une certaine extension de classe. Vous pouvez étendre le ThrottlerGuard
et remplacer la méthode handleRequest
comme suit :
@Injectable()export class WsThrottlerGuard extends ThrottlerGuard { async handleRequest(context: ExecutionContext, limit: number, ttl: number, throttler: ThrottlerOptions): Promise<boolean> { const client = context.switchToWs().getClient(); const ip = client._socket.remoteAddress; const key = this.generateKey(context, ip, throttler.name); const { totalHits } = await this.storageService.increment(key, ttl);
if (totalHits > limit) { throw new ThrottlerException(); }
return true; }}
Il y a quelques points à garder à l’esprit lors de l’utilisation de WebSockets :
- Le garde ne peut pas être enregistré avec
APP_GUARD
ouapp.useGlobalGuards()
- Quand une limite est atteinte, Nest émettra un événement
exception
, donc assurez-vous qu’il y a un auditeur en place pour cela.
GraphQL
Le ThrottlerGuard
peut également être utilisé pour travailler avec des requêtes GraphQL. Encore une fois, le garde peut être étendu, mais cette fois, la méthode getRequestResponse
sera remplacée :
@Injectable()export class GqlThrottlerGuard extends ThrottlerGuard { getRequestResponse(context: ExecutionContext) { const gqlCtx = GqlExecutionContext.create(context); const ctx = gqlCtx.getContext(); return { req: ctx.req, res: ctx.res }; }}
Configuration
Les options suivantes sont valides pour l’objet passé au tableau des options du module ThrottlerModule
:
name | Le nom pour le suivi interne de l’ensemble de limitation utilisé. Défaut à default si non passé |
---|---|
ttl | Le nombre de millisecondes pendant lesquelles chaque requête sera conservée en mémoire |
limit | Le nombre maximum de requêtes dans la période de TTL |
ignoreUserAgents | Un tableau d’expressions régulières des agents utilisateurs à ignorer lors de la limitation |
skipIf | Une fonction qui prend en entrée le ExecutionContext et renvoie un boolean pour court-circuiter la logique de limitation. Comme @SkipThrottler() , mais basé sur la requête |
Si vous devez configurer un stockage à la place, ou voulez utiliser certaines des options ci-dessus de manière plus globale, s’appliquant à chaque ensemble de limitation, vous pouvez passer les options ci-dessus via la clé d’options throttlers
et utiliser le tableau ci-dessous.
storage | Un service de stockage personnalisé pour suivre la limitation. Voir ici. |
---|---|
ignoreUserAgents | Un tableau d’expressions régulières des agents utilisateurs à ignorer lors de la limitation |
skipIf | Une fonction qui prend en entrée le ExecutionContext et renvoie un boolean pour court-circuiter la logique de limitation. Comme @SkipThrottler() , mais basé sur la requête |
throttlers | Un tableau d’ensembles de limitation, définis en utilisant le tableau ci-dessus |
Configuration Asynchrone
Vous souhaiterez peut-être obtenir votre configuration de limitation de taux de manière asynchrone au lieu de synchroniquement. Vous pouvez utiliser la méthode forRootAsync()
, qui permet l’injection de dépendance et des méthodes async
.
Une approche pourrait être d’utiliser une fonction de fabrication :
@Module({ imports: [ ThrottlerModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (config: ConfigService) => [ { ttl: config.get('THROTTLE_TTL'), limit: config.get('THROTTLE_LIMIT'), }, ], }), ],})export class AppModule {}
Vous pouvez également utiliser la syntaxe useClass
:
@Module({ imports: [ ThrottlerModule.forRootAsync({ imports: [ConfigModule], useClass: ThrottlerConfigService, }), ],})export class AppModule {}
Ceci est réalisable tant que ThrottlerConfigService
implémente l’interface ThrottlerOptionsFactory
.
Stockages
Le stockage intégré est un cache en mémoire qui garde une trace des requêtes effectuées jusqu’à ce qu’elles aient dépassé le TTL défini par les options globales. Vous pouvez intégrer votre propre option de stockage à l’option storage
du ThrottlerModule
, tant que la classe implémente l’interface ThrottlerStorage
.
Pour des serveurs distribués, vous pouvez utiliser le fournisseur de stockage de la communauté pour Redis pour avoir une source unique de vérité.
Helpers de Temps
Il existe quelques méthodes auxiliaires pour rendre les temps plus lisibles si vous préférez les utiliser plutôt que la définition directe. @nestjs/throttler
exporte cinq helpers différents, seconds
, minutes
, hours
, days
, et weeks
. Pour les utiliser, il suffit d’appeler seconds(5)
ou l’un des autres helpers, et le bon nombre de millisecondes sera renvoyé.
Guide de Migration
Pour la plupart des gens, envelopper vos options dans un tableau suffira.
Si vous utilisez un stockage personnalisé, vous devez envelopper votre ttl
et votre limit
dans un tableau et l’assigner à la propriété throttlers
de l’objet d’options.
Tout décorateur @ThrottleSkip()
devrait maintenant prendre un objet avec des propriétés string: boolean
. Les chaînes sont les noms des limitations. Si vous n’avez pas de nom, passez la chaîne default
, car c’est ce qui sera utilisé sous le capot.
Tous les décorateurs @Throttle()
devraient également maintenant prendre un objet avec des clés de chaîne, relatives aux noms des contextes de limitation (encore une fois, default
s’il n’y a pas de nom) et des valeurs d’objets contenant les clés limit
et ttl
.
Pour plus d’infos, consultez le Changelog.