NestJs - 로그아웃 프로세스 구현 / docker를 사용한 redis 본문
반응형
미래의 나에게
yarn add redis
yarn add cache-manager@4.1.0
yarn add cache-manager-redis-store@2.0.0
yarn add --dev @types/cache-manager-redis-store
yarn add jsonwebtoken
기존에 만들어 둔 로그인 프로세스의 경우,
refreshToken 이 만료되지 않으면 로그아웃이 되지 않는다는 단점이 있었다.
따라서 이번에는 제대로 된 로그아웃 프로세스를 구현하려고 하는데,
이번에는 redis 를 이용하여 API 를 만들어 보도록 한다.
about redis
2023.02.13 - [코딩/알쓸코잡] - Redis
구현 과정
- 1. 도커를 사용한 가상 머신 구동
- 2. logout API 작성
- 3. 작성한 API 를 통해 redis 에 accessToken, refreshToken 저장
- 4. 검증 로직 변경 (토큰 검증이 필요한 API일 경우, 오류 반환)
1. 도커를 사용한 가상 머신 구동
위에 있는 라이브러리와 도커를 설정한다.
도커는 아래와 같이 구현된다.
services:
my-backend:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./src:/myfolder/src
ports:
- 3000:3000
env_file:
- ./.env.docker
my-database:
#platform:linux/86_64
image: mysql:latest
environment:
MYSQL_DATABASE: 'mydocker'
MYSQL_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
my-redis:
image: redis:latest
ports:
- 6379:6379
이제부터는 도커를 통해 세 가지의 컴퓨터를 구동시킨다고 보면 좋다.
이때, redis 의 포트는 6379 를 쓴다는 것을 기억해두자.
2. logout API 작성
도커가 정상적으로 구동이 되었다면, 이제 logout API 를
이전에 로그인을 만들어 두었던 Auth.resolver 에 작성한다.
// auth.resolver.ts
@UseGuards(GqlAuthGuard('refresh'))
@Mutation(() => String)
logout(
@Context() context: IContext, //
): Promise<string> {
return this.authService.logout({ res: context.res });
}
// 필요한 것은 refreshToken / accessToken 이므로,
// 해당 데이터가 담겨있는 context 만 있어도 괜찮다.
3. 작성한 API 를 통해 redis 에 accessToken, refreshToken 저장
// auth.service.ts
import { Cache } from 'cache-manager';
import * as jwt from 'jsonwebtoken';
async logout({ res }): Promise<string> {
const accessToken = res.req.headers.authorization.split(' ')[1];
const refreshToken = res.req.headers.cookie.split('=')[1];
try {
// jsonwebtoken 을 활용한 검증 로직, 이떄 해당 로직이 실패하면 오류를 반환하게 된다.
const jwtAccess = jwt.verify(accessToken, process.env.JWT_ACCESS_KEY);
const jwtRefresh = jwt.verify(refreshToken, process.env.JWT_REFRESH_KEY);
// Cache-manager 을 통한 redis 저장 로직. (key, value, ttl)을 저장한다.
// accessToken 저장
await this.cacheManager.set(`accessToken:${accessToken}`,
'accessToken', {
// 토큰에 저장된 만료 시간을 의미한다. 만료 시간 - 시작 시간
ttl: jwtAccess['exp'] - jwtAccess['iat'],
});
// refreshToken 저장
await this.cacheManager.set(
`refreshToken:${refreshToken}`,
'refreshToken',{
ttl: jwtRefresh['exp'] - jwtRefresh['iat'],
},
);
} catch (error) {
throw new UnauthorizedException('로그아웃에 실패하였습니다.');
}
return '로그아웃에 성공했습니다.'; // 위 try&catch문이 정상적으로 작동할 경우 리턴
}
4. 검증 로직 변경 (토큰 검증이 필요한 API일 경우, 오류 반환)
// jwt-access.strategy.ts
import { CACHE_MANAGER, Inject, UnauthorizedException } from '@nestjs/common'; // CACHE_MANAGER
import { PassportStrategy } from '@nestjs/passport';
import { Cache } from 'cache-manager'; // cache-manager
import { ExtractJwt, Strategy } from 'passport-jwt';
export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') {
constructor(
@Inject(CACHE_MANAGER) // redis 에 접근하기 위한 DI
private readonly cacheManager: Cache, // redis 에 접근하기 위한 DI
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_ACCESS_KEY,
passReqToCallback: true, // 요청이 true 일 시, callback함수(여기서는 validate)로 전달
});
}
// 인자로는 req 를 추가로 받아, redis에 저장된 토큰이 존재하는지 파악한다.
async validate(req, payload) {
const accessToken = req.headers.authorization.split(' ')[1];
const redisAccessToken = await this.cacheManager.get(
`accessToken:${accessToken}`,
);
// redis에 해당 accessToken이 저장되어 있다면, 해당 오류를 반환한다.
if (redisAccessToken)
throw new UnauthorizedException('이미 로그아웃된 계정입니다.');
return {
email: payload.email,
id: payload.sub,
};
}
}
playground 에서 로그인을 요청한 경우
도커 내 redis 저장소에 접근해서 키 값을 확인한 이미지
docker ps // docker 내 컴퓨터 파악
docker exec -it 'container' /bin/bash // 해당 컨테이너에 접근
redis-cli // redis 저장소 내에 접근
key * // redis에 저장된 모든 키값 확인
로그아웃 후, 토큰이 필요한 타 API 를 요청한 경우
반응형
'개발 > nest.js' 카테고리의 다른 글
GCP 버킷에 이미지 올리는 방법 (0) | 2024.04.26 |
---|---|
class-validator 커스터마이징 (0) | 2023.11.30 |
NestJs - 결제 프로세스 구현 / 포트원 & 카카오페이 (0) | 2023.02.14 |
NestJs - 소셜로그인 구현(google login) (0) | 2023.02.12 |
NestJS - login process 구현 (refresh Token) (0) | 2023.02.12 |
Comments