Nest.js
V8.1.2 |
|
Das Projekt basiert auf der Version Nest.js 8.x
, die sich von der Version Nest.js 9.x
unterscheidet. Im folgenden Artikel werden die Unterschiede speziell erläutert Auf die Erläuterung der Punkte und das Upgrade von V8
auf V9
wird hier nicht näher eingegangen. Nest.js 8.x
版本,与Nest.js 9.x
版本使用有所不同, 后面的文章专门整理了两个版本使用不同点的说明, 以及如何从V8
升级到V9
, 这里就不过多讨论。
首先,我们在Nest.js项目中连接Redis, 连接Redis需要的参数:
REDIS_HOST:Redis 域名
REDIS_PORT:Redis 端口号
REDIS_DB: Redis 数据库
REDIS_PASSPORT:Redis 设置的密码
将参数写入.env
与.env.prod
配置文件中:
使用Nest官方推荐的方法,只需要简单的3个步骤:
1、引入依赖文件
npm install cache-manager --save
npm install cache-manager-redis-store --save
npm install @types/cache-manager -D
Nest
为各种缓存存储提供统一的API,内置的是内存中的数据存储,但是也可使用 cache-manager
来使用其他方案, 比如使用Redis
来缓存。
为了启用缓存, 导入ConfigModule
, 并调用register()
或者registerAsync()
传入响应的配置参数。
2、创建module文件src/db/redis-cache.module.ts
, 实现如下:
import { ConfigModule, ConfigService } from '@nestjs/config';
import { RedisCacheService } from './redis-cache.service';
import { CacheModule, Module, Global } from '@nestjs/common';
import * as redisStore from 'cache-manager-redis-store';
@Module({
imports: [
CacheModule.registerAsync({
isGlobal: true,
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
return {
store: redisStore,
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
db: 0, //目标库,
auth_pass: configService.get('REDIS_PASSPORT') // 密码,没有可以不写
};
},
}),
],
providers: [RedisCacheService],
exports: [RedisCacheService],
})
export class RedisCacheModule {}
-
CacheModule
的registerAsync
方法采用 Redis Store 配置进行通信
-
store
属性值redisStore
,表示'cache-manager-redis-store' 库
-
isGlobal
属性设置为true
来将其声明为全局模块,当我们将RedisCacheModule
在AppModule
中导入时, 其他模块就可以直接使用,不需要再次导入
- 由于Redis 信息写在配置文件中,所以采用
registerAsync()
方法来处理异步数据,如果是静态数据, 可以使用register
3、新建redis-cache.service.ts
文件, 在service实现缓存的读写
import { Injectable, Inject, CACHE_MANAGER } from '@nestjs/common';
import { Cache } from 'cache-manager';
@Injectable()
export class RedisCacheService {
constructor(
@Inject(CACHE_MANAGER)
private cacheManager: Cache,
) {}
cacheSet(key: string, value: string, ttl: number) {
this.cacheManager.set(key, value, { ttl }, (err) => {
if (err) throw err;
});
}
async cacheGet(key: string): Promise<any> {
return this.cacheManager.get(key);
}
}
接下来,在app.module.ts
中导入RedisCacheModule
即可。
调整 token 签发及验证流程
我们借助redis来实现token过期处理、token自动续期、以及用户唯一登录。
- 过期处理:把用户信息及token放进redis,并设置过期时间
- token自动续期:token的过期时间为30分钟,如果在这30分钟内没有操作,则重新登录,如果30分钟内有操作,就给token自动续一个新的时间,防止使用时掉线。
- 户唯一登录:相同的账号,不同电脑登录,先登录的用户会被后登录的挤下线
token 过期处理
在登录时,将jwt生成的token,存入redis,并设置有效期为30分钟。存入redis的key由用户信息组成, value是token值。
// auth.service.ts
async login(user: Partial<User>) {
const token = this.createToken({
id: user.id,
username: user.username,
role: user.role,
});
+ await this.redisCacheService.cacheSet(
+ `${user.id}&${user.username}&${user.role}`,
+ token,
+ 1800,
+ );
return { token };
}
在验证token时, 从redis中取token,如果取不到token,可能是token已过期。
// jwt.strategy.ts
+ import { RedisCacheService } from './../core/db/redis-cache.service';
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly authService: AuthService,
private readonly configService: ConfigService,
+ private readonly redisCacheService: RedisCacheService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get('SECRET'),
+ passReqToCallback: true,
} as StrategyOptions);
}
async validate(req, user: User) {
+ const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
+ const cacheToken = await this.redisCacheService.cacheGet(
+ `${user.id}&${user.username}&${user.role}`,
+ );
+ if (!cacheToken) {
+ throw new UnauthorizedException('token 已过期');
+ }
const existUser = await this.authService.getUser(user);
if (!existUser) {
throw new UnauthorizedException('token不正确');
}
return existUser;
}
}
用户唯一登录
当用户登录时,每次签发的新的token,会覆盖之前的token, 判断redis中的token与请求传入的token是否相同, 不相同时, 可能是其他地方已登录, 提示token错误。
// jwt.strategy.ts
async validate(req, user: User) {
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
const cacheToken = await this.redisCacheService.cacheGet(
`${user.id}&${user.username}&${user.role}`,
);
if (!cacheToken) {
throw new UnauthorizedException('token 已过期');
}
+ if (token != cacheToken) {
+ throw new UnauthorizedException('token不正确');
+ }
const existUser = await this.authService.getUser(user);
if (!existUser) {
throw new UnauthorizedException('token不正确');
}
return existUser;
}
token自动续期
实现方案有多种,可以后台jwt生成access_token
(jwt有效期30分钟)和refresh_token
, refresh_token
有效期比access_token
有效期长,客户端缓存此两种token, 当access_token
过期时, 客户端再携带refresh_token
获取新的access_token
。 这种方案需要接口调用的开发人员配合。
我这里主要介绍一下,纯后端实现的token自动续期
实现流程:
- ①:jwt生成token时,有效期设置为用不过期
- ②:redis 缓存token时设置有效期30分钟
- ③:用户携带token请求时, 如果key存在,且value相同, 则重新设置有效期为30分钟
设置jwt生成的token, 用不过期, 这部分代码是在auth.module.ts
文件中, 不了解的可以看文章 Nest.js 实战系列第二篇-实现注册、扫码登陆、jwt认证
// auth.module.ts
const jwtModule = JwtModule.registerAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
return {
secret: configService.get('SECRET', 'test123456'),
- signOptions: { expiresIn: '4h' }, // 取消有效期设置
};
},
});
然后再token认证通过后,重新设置过期时间, 因为使用的cache-manager
没有通过直接更新有效期方法,通过重新设置来实现:
// jwt.strategy.ts
async validate(req, user: User) {
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
const cacheToken = await this.redisCacheService.cacheGet(
`${user.id}&${user.username}&${user.role}`,
);
if (!cacheToken) {
throw new UnauthorizedException('token 已过期');
}
if (token != cacheToken) {
throw new UnauthorizedException('token不正确');
}
const existUser = await this.authService.getUser(user);
if (!existUser) {
throw new UnauthorizedException('token不正确');
}
+ this.redisCacheService.cacheSet(
+ `${user.id}&${user.username}&${user.role}`,
+ token,
+ 1800,
+ );
return existUser;
}
到此,在Nest中实现token过期处理、token自动续期、以及用户唯一登录都完成了, 退出登录时移除token比较简单就不在这里一一上代码了。
在Nest中除了使用官方推荐的这种方式外, 还可以使用nestjs-redis
来实现,如果你存token时, 希望存hash
结构,使用cache-manager-redis-store
时,会发现没有提供hash
Zuerst verbinden wir Redis im Nest.js-Projekt. Die zum Verbinden von Redis erforderlichen Parameter: 🎜rrreee🎜Schreiben Sie die Parameter in .env
und .env.prod
Konfigurationsdateien: 🎜🎜 >🎜 🎜Verwenden Sie die von Nest offiziell empfohlene Methode, die nur drei einfache Schritte erfordert: 🎜🎜1 Abhängigkeitsdateien einführen 🎜rrreee🎜Nest
bietet eine einheitliche API für verschiedene Cache-Speicher und die integrierte Eine davon ist die Datenspeicherung im Speicher, aber Sie können auch cache-manager
verwenden, um andere Lösungen zu verwenden, z. B. die Verwendung von Redis
für das Caching. 🎜🎜Um das Caching zu aktivieren, importieren Sie ConfigModule
und rufen Sie register()
oder registerAsync()
auf, um die Antwortkonfigurationsparameter zu übergeben. 🎜🎜2. Erstellen Sie die Moduldatei src/db/redis-cache.module.ts
und implementieren Sie sie wie folgt: 🎜rrreee
registerAsync of <code>CacheModule
Die Methode verwendet die Redis Store-Konfiguration, um den Attributwert redisStore
zu kommunizieren
-
store
und gibt den „cache-manager-redis-“ an. store'-Bibliothek li>- Die Eigenschaft
isGlobal
wird auf true
gesetzt, um sie als globales Modul zu deklarieren, wenn wir RedisCacheModule einfügen. code> in <code>AppModule code> können andere Module ohne erneuten Import direkt verwendet werden
- Da die Redis-Informationen in die Konfigurationsdatei geschrieben werden, ist der
registerAsync()
Die Methode wird zur Verarbeitung asynchroner Daten verwendet. Wenn es sich um statische Daten handelt, können Sie register
🎜3 verwenden. Erstellen Sie eine neue redis-cache.service.ts-Datei zum Implementieren des zwischengespeicherten Lesens im Dienst. Schreiben Sie 🎜rrreee🎜Als nächstes importieren Sie <code>RedisCacheModule
in app.module.ts
. 🎜Token-Ausgabe- und Verifizierungsprozess anpassen
🎜Wir verwenden Redis, um die Token-Ablaufverarbeitung, die automatische Token-Erneuerung und die eindeutige Benutzeranmeldung zu implementieren. 🎜
- Ablaufverarbeitung: Geben Sie Benutzerinformationen und Token in Redis ein und legen Sie die Ablaufzeit fest.
- Automatische Erneuerung des Tokens: Die Token-Ablaufzeit beträgt 30 Minuten, wenn innerhalb dieser 30 Minuten kein Ablauf erfolgt Minuten Wenn es einen Vorgang gibt, melden Sie sich erneut an. Wenn innerhalb von 30 Minuten ein Vorgang erfolgt, wird der Token automatisch um eine neue Zeit verlängert, um eine Trennung während der Nutzung zu verhindern.
- Einzigartige Anmeldung für jeden Benutzer: Wenn sich dasselbe Konto und verschiedene Computer anmelden, wird der Benutzer, der sich zuerst anmeldet, von dem Benutzer, der sich später anmeldet, offline geschaltet
Token-Ablaufverarbeitung
🎜Speichern Sie beim Anmelden das von jwt generierte Token in Redis und legen Sie die Gültigkeitsdauer auf 30 Minuten fest. Der in Redis gespeicherte Schlüssel besteht aus Benutzerinformationen und der Wert ist der Tokenwert. 🎜rrreee🎜Wenn Sie das Token überprüfen, holen Sie sich das Token von Redis. Wenn Sie das Token nicht erhalten können, ist das Token möglicherweise abgelaufen. 🎜rrreeeNur Benutzeranmeldung
🎜Wenn sich ein Benutzer anmeldet, wird jedes Mal, wenn ein neues Token ausgegeben wird, das vorherige Token überschrieben. Beurteilung von Redis Ist das Token in der Anfrage dasselbe wie das übergebene Token? Wenn nicht, hat sich der Benutzer möglicherweise an einer anderen Stelle angemeldet und ein Token-Fehler wird angezeigt. 🎜rrreeeAutomatische Token-Erneuerung
🎜Es gibt viele Implementierungsoptionen, Sie können access_token
mit jwt in generieren Hintergrund (jwt-Gültigkeitszeitraum beträgt 30 Minuten) und refresh_token
, refresh_token
ist länger gültig als access_token
. Der Client speichert diese beiden Token zwischen >access_token Wenn der Code> abläuft, überträgt der Client refresh_token
, um ein neues access_token
zu erhalten. Diese Lösung erfordert die Mitarbeit des Entwicklers des Schnittstellenaufrufs. 🎜🎜Ich werde hier hauptsächlich die automatische Erneuerung von Token vorstellen, die vom reinen Backend implementiert wird🎜🎜Implementierungsprozess:🎜
- ①: Wenn jwt ein Token generiert, wird die Gültigkeitsdauer so eingestellt, dass sie nach der Verwendung abläuft
- ②: Wenn Redis das Token zwischenspeichert, legen Sie die Gültigkeitsdauer auf 30 Minuten fest
- ③: Wenn der Benutzer die Token-Anfrage trägt und der Schlüssel vorhanden und der Wert gleich ist, wird die Gültigkeitsdauer auf zurückgesetzt 30 Minuten
🎜Set jwt Das generierte Token läuft nicht ab. Dieser Teil des Codes befindet sich in der Datei auth.module.ts
Sie können den Artikel Nest.js Practical Series Teil 2 – Implementierung der Registrierung, Scan-Code-Anmeldung und JWT-Authentifizierung🎜rrreee🎜Nachdem die Token-Authentifizierung bestanden wurde, setzen Sie die Ablaufzeit zurück, da die Verwendung des cache-manager
den Gültigkeitszeitraum nicht direkt aktualisiert, sondern zurücksetzt: 🎜rrreee🎜An diesem Punkt Die Implementierung der Token-Ablaufverarbeitung, der automatischen Token-Erneuerung und der eindeutigen Benutzeranmeldung in Nest ist abgeschlossen. Das Entfernen des Tokens beim Abmelden ist relativ einfach, daher werde ich hier nicht einzeln auf den Code eingehen. 🎜🎜Zusätzlich zur offiziell empfohlenen Methode in Nest können Sie dies auch mit nestjs-redis
erreichen, wenn Sie den hash speichern möchten. Code>-Struktur, verwenden Wenn Sie <code>cache-manager-redis-store
verwenden, werden Sie feststellen, dass es keine Methode zum Speichern und Entfernen von hash
-Werten gibt (es erfordert einige Mühe, dies herauszufinden). . 🎜Hinweis: Wenn Sie stattdessen nest-redis
来实现redis缓存, 在Nest.js 8 版本下会报错, 小伙伴们可以使用@chenjm/nestjs-redis
verwenden oder auf die Lösung des Problems verweisen: Nest 8 + Redis-Fehler.
Zusammenfassung
Quellcode-Adresse: https://github.com/koala-coding/nest-blog
Weitere Programmierkenntnisse finden Sie unter: Programmierunterricht! !