▶ 들어가기 전
친구들과 학교를 위한 프로젝트를 진행하면서 로그인 로직을 구현할 일이 생겼습니다.
JWT를 사용하기로 했는데 JWT를 저장할 데이터베이스를 고민하다 보편적으로 세션 스토어나 캐시를 Redis로 많이 이용한다기에 Redis를 택했습니다. Redis를 사용해 본 경험이 처음이었고 생각보다 공수가 많았기에 정리를 남깁니다.
위와 같은 로직을 바탕으로 코드를 작성했습니다.
▶ Redis?
Redis는 현재 가장 인기 있는 Key-Value형 스토어로서 Redis의 다양한 특징 덕분에 고속I/O에 적합함으로 캐싱, 세션 스토어로서 널리 사용되고 있습니다.
- 고성능, 고속I/O
- Key-Value형 DB
- 인메모리형 DB
- 다양한 기능 제공 -> TTL 설정... 등
▶ redis.service.ts 작성
// redis.service.ts
import { Injectable } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisService {
private readonly redisClient: Redis;
constructor() {
this.redisClient = new Redis({
host: process.env.REDIS_URL,
port: parseInt(process.env.REDIS_PORT),
password: process.env.REDIS_PASSWORD,
});
}
async get(key: string): Promise<string> {
return this.redisClient.get(key);
}
async set(key: string, value: string): Promise<void> {
await this.redisClient.set(key, value, 'EX', 604800);
}
async update(key: string, value: string, ttl: number): Promise<void> {
await this.redisClient.set(key, value, 'EX', ttl);
}
async keys(): Promise<string[]> {
return this.redisClient.keys('*');
}
async allDataDelete() {
await this.redisClient.flushall();
}
async checkTTL(key: string) {
return this.redisClient.ttl(key);
}
}
우선 redis.service.ts를 작성 해주었습니다. redis.service.ts는 redis에서 CRUD 작업을 할 수 있도록 작성했습니다. 중간에 update 함수에 TTL을 인자로 전달받는 코드로 작성되어 있는데 redis는 KEEPTTL이라고 기존에 TTL을 유지하는 설정 값이 존재합니다. 하지만 무슨 이유인지 작동하지 않아 checkTTL 함수를 통해 TTL을 가져와서 직접 인자로 넘기는 방식으로 TTL을 유지했습니다.
처음에는 ioredis를 mongoose나 TypeORM처럼 auth.module.ts에 바로 주입하여 사용하고 싶었으나 제대로 작동하지 않길래 방법을 틀었습니다. ( redis.service.ts에 ioredis의 인스턴스와 강결합이 되어 있는 거 같아서 좀 찝찝합니다. ) @liaoliaots/nestjs-redis와 같은 라이브러리를 사용하여 등록하는 방법도 있었지만 정상적으로 작동 하지 않았습니다.
▶ redis.module.ts에 등록
#auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { EmailAuthCode, EmailAuthCodeSchema } from './schema/auth.schema';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt/jwt.strategy';
import { ConfigModule } from '@nestjs/config';
import { RedisService } from './redis.service';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
MongooseModule.forFeature([{ name: EmailAuthCode.name, schema: EmailAuthCodeSchema }]),
JwtModule.register({
secret: process.env.ACCESS_TOKEN_SECRET,
signOptions: { expiresIn: '3h' },
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, RedisService],
exports: [AuthService],
})
export class AuthModule {}
이후 auth.module.ts에 RedisService를 프로바이더로 등록해줬습니다. 이제 redis에 토큰을 저장하거나 읽어서 요청 헤더에 있는 토큰과 대조할 수 있습니다. 만약 redis에 토큰을 저장하려면 다음과 같이 코드를 작성하면 됩니다. ( 토큰을 대조할 때는 요청 헤더에서 토큰을 읽어와 비교만 해주면 됩니다. )
# auth.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { JwtService } from '@nestjs/jwt';
import { RedisService } from './redis.service';
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
private redisClient: RedisService,
) {}
async generateAllToken(params: SignInParams) {
const accessToken = await this.jwtService.signAsync({ id: params.id });
const refreshToken = await this.jwtService.signAsync(
{ id: params.id, refreshToken: true },
{
secret: process.env.REFRESH_TOKEN_SECRET,
expiresIn: '7d',
},
);
await this.redisClient.set(refreshToken, accessToken, 180);
return { accessToken, refreshToken };
}
async generateAccessToken(params: PostGenerateTokenParams) {
const accessToken = await this.jwtService.signAsync({ id: params.id });
await this.redisClient.update(params.refreshToken, accessToken);
return accessToken;
}
}
▶ 마치며
구석 한켠에 임시저장으로 박혀있던 글이었습니다. 그렇기에 글을 이어쓰며 어떻게 구현했는지 기억도 안나고 무슨 생각이었는지 기억이 잘 안나서 글이 조금 이상한 거 같습니다. 아직도 강결합이 되어있는 거 같아 마음이 아픕니다. 방법을 찾아보겠습니다.
'공부노트' 카테고리의 다른 글
AWS Lambda + API Gateway (0) | 2023.12.28 |
---|---|
AWS Lambda 입문하기 (3) | 2023.12.27 |