우리는 다음의 필요성을 통해 사용자의 ChatGpt 이용 횟수를 제한하고자 하였다.
그렇기 때문에, 우리는 사용자의 사용 횟수를 5회로 제한하고 몇일 뒤 제한이 해제되어 다시 이용할 수 있게 하기로 하였다.
서비스 이용을 제한하는 방법은 다양하겠지만 우리가 제공하는 서비스는 다양한 방법을 구사할 정도의 환경을 제공하지 못하였다.
서비스 이용을 제한하기 위해 가장 먼저 고려해야할 것은 사용자를 구별하는 것이다. 하지만 우리는 사용자 정보를 받아올 회원 가입 서비스도, 정보를 저장할 DB도 구축하지 않았기 때문에 다른 방안을 고안할 필요가 있었다.
그렇게 고안해낸 방식은 사용자의 IP를 조회하는 것이다. IP 조회는 다행히도 무료로 API 요청을 통해 조회해올 수 있었고 이를 토큰화 하여 쿠키에 저장시키는 방식을 채택하였다.
하지만 이러한 점은 몇 가지 문제점을 발생시켰다.
그래서 방식을 반대로 적용하기로 하였다. 처음부터 cookie에 토큰이 저장되지 않았을 경우에만 서비스를 이용할 수 있으며, 5회를 초과할 경우에 cookie에 토큰을 저장하게 된다면 사용자가 재접속을 하더라도 cookie에는 여전히 토큰이 남아있게 된다.
Nest에서 JWT 서비스를 사용하려면@nestjs/jwt
를 설치해야 한다.
$ npm install --save @nestjs/jwt
//gpt.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class GptService {
constructor(
private configService: ConfigService,
private jwtService: JwtService,
) {}
generateToken(payload: { ip: string }) {
const token = this.jwtService.sign(payload, {
secret: this.configService.get<string>('JWT_SECRET'),
});
return { token };
}
}
configService
에 대한 설정은 이 글을 참고해주세요.
generateToken
함수는 클라이언트에게 IP주소를 파라미터로 받아오면 JWT 토큰을 반환한다.
//gpt.controller.ts
@Post('/token')
generateToken(@Body() toeknDto: TokenDto, @Res() res: expRes) {
const { token } = this.gptService.generateToken(toeknDto);
res.cookie('gpt_token', token, {
domain: '.planbot.click',
secure: true,
maxAge: 7 * 24 * 60 * 60 * 1000, //7d,
sameSite: 'none',
path: '/',
});
return res.send({ token });
}
발급된 토큰은 클라이언트 cookie에 저장시켜준다. 참고로 @Res() res: expRes
부분은 Response
가 express
와 @nestjs/common
에서 중복으로 import되기 때문에, express
의 Response
를 expRes
로 변경하여 import 해주었다.
+) 응답을 express 타입으로 불러왔기 때문에, 기존에 nest에서 하던대로 return으로 반환하는 것이 아닌, res.send
로 반환해주어야 한다.
쿠키의 옵션값들은 여러가지가 있는데, 생각보다 간단하면서도 어려운 설정이다.
domain
쿠키에 접근할 수 있는 도메인을 설정한다. 현재 사용하고 있는 도메인은
www.planbot.click 이며 서버는 api.planbot.click를 사용하고 있기 때문에, 루트 도메인 값인 .planbot.click
을 설정해두었다.
secure
https에서 쿠키를 사용하기 위해 설정하였다.
maxAge
쿠키의 만료는 7일로 설정하였다.
sameSite
secure를 true로 설정하고 쿠키의 사용범위를 넓히기 위해서 설정하였다
path
쿠키에 접근 가능한 경로이다.
자세한 문서는 여기를 참고하세요
참고로 프로젝트를 진행하며 서버와 클라이언트 간 쿠키를 저장해보며 테스트를 해볼 것이다.
우리 또한 쿠키 설정하는 과정에서 여러 테스트를 진행해보았지만, 쿠키 설정도 완벽하고 요청에도 쿠키가 담겨있으며, 서버에도 쿠키가 잘 전달되지만 정작 개발자 도구에서 저장이 안되는 것을 확인하였다.
이 경우에는 꼭 쿠키를 한 번 다 제거해보고 다시 시도해보도록 하자.
( 별 것도 아니지만 꽤 오랜 시간 걸렸다.
쿠키에 토큰이 성공적으로 저장이 되었다면, 클라이언트의 매 요청마다 쿠키에 토큰이 담겨서 전달될 것이다.
그렇다면 우리는 이 토큰의 여부에 따라 서비스 제한을 시켜야 한다.
//token.auth.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class TokenGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const { gpt_token } = request.cookies;
if (!gpt_token) return true;
else {
throw new HttpException(
'5회 무료 이용이 끝났습니다.',
HttpStatus.FORBIDDEN,
);
}
}
}
guard
를 설정한 api에 요청이 들어올 때마다, request.cookies
에 토큰으로 저장한 gpt_token
이 포함되어 있는 지 확인한 후, 토큰이 발견될 경우 메세지를 날려주며 403 Forbidden 을 응답한다.