반응형
Login
- Basic $token으로 이메일, 패스워드 요청
- Basic $token인지 검사
- base64로 된 요청값을 utf-8을 이용하여 디코딩
- 디코딩 된 값은 <email>:<password> 형태로 되어 있음
- 이메일, 패스워드 검사 후 access, refresh token 발급
AuthController
// basic token
@Post('login') // 로그인
loginUser(@Headers('authorization') token: string) {
return this.authService.login(token);
}
- Headers pipe에서 authorization 헤더를 받아옴
- 받은 데이터를 AuthService에 넘겨줌
AuthService
async login(token: string) {
const { email, password } = this.parseBasicToken(token);
const user = await this.authenticate(email, password);
return {
refreshToken: await this.issueToken(user, true),
accessToken: await this.issueToken(user, false),
};
}
- parseBasicToken 메서드에서 Basic Token인지 검사
parseBasicToken(rawToken: string) { const basicSplit = rawToken.split(' '); if (basicSplit.length !== 2) { throw new BadRequestException('토큰 포맷이 잘못됐습니다.'); } const [basic, token] = basicSplit; if (basic.toLowerCase() !== 'basic') { throw new BadRequestException('토큰 포맷이 잘못됐습니다.'); } const decoded = Buffer.from(token, 'base64').toString('utf-8'); const tokenSplit = decoded.split(':'); if (tokenSplit.length !== 2) { throw new BadRequestException('토큰 포맷이 잘못됐습니다.'); } const [email, password] = tokenSplit; return { email, password, }; }- 토큰 타입과 Basic인지 검사
- base64로 인코딩 되어 있는 토큰값을 utf-8을 이용해서 디코딩
- 디코딩 된 값은 <email>:<password> 형태로 되어 있음
- 제대로 된 값이 들어왔는지 검사 후 반환
- parseBasicToken
- authenticate 메서드에서 email과 password를 검사
async authenticate(email: string, password: string) { const user = await this.userRepository.findOne({ where: { email, }, }); if (!user) { throw new BadRequestException('잘못된 로그인 정보입니다.'); } const passOk = await bcrypt.compare(password, user.password); if (!passOk) { throw new BadRequestException('잘못된 로그인 정보입니다.'); } return user; }- 이메일이 있는지, 올바른 패스워드인지 검사
- 올바른 이메일과 패스워드라면 user를 반환
- authenticate
- 검사가 모두 다 통과하면 issuToken 메서드에서 access, refresh 토큰 발급
async issueToken(user: { id: number; role: Role }, isRefreshToken: boolean) { const refreshTokenSecret = this.configService.get<string>( EnvVariableKeys.refreshTokenSecret, ); const accessTokenSecret = this.configService.get<string>( EnvVariableKeys.accessTokenSecret, ); return await this.jwtService.signAsync( { sub: user.id, role: user.role, type: isRefreshToken ? 'refresh' : 'access', }, { secret: isRefreshToken ? refreshTokenSecret : accessTokenSecret, expiresIn: isRefreshToken ? '24h' : 300, }, ); }- 환경변수를 관리하는 모듈에서 JwtSecret Key를 가져온 후 AccessToken, RefreshToken을 발급
- 파라미터로 받은 isRefreshToken이 true라면 RefreshToken을, 아니라면 AccessToken 발급
- 발급 받은 토큰을 반환
Authorization
AuthGuard는 Login, Register API를 제외한 모든 API에 대해서 검사를 진행하기 때문에 전역적으로 등록을 해주어야 함
AppModule
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth/guard/auth.guard';
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
이렇게 providers에 전역적으로 AuthGuard를 등록을 해주면 된다
PublicDecorator
전역적으로 AuthGuard를 등록을 하게 되면 Login API나 Register API도 AuthGuard를 타기 때문에 특별하게 다른 설정을 해주어야 한다
public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Public = (data: any) => SetMetadata('public', data);
위와 같이 커스텀 데코레이터를 만들어준 후에 AuthController에서 Login, Register 메서드에 해당되는 곳에 데코레이터를 등록만 해주면 된다
// basic token
@Public(true)
@Post('register') // 회원가입
async registerUser(@Headers('authorization') token: string) {
return await this.authService.register(token);
}
// basic token
@Public(true)
@Post('login') // 로그인
loginUser(@Headers('authorization') token: string) {
return this.authService.login(token);
}
AuthGuard(auth.guard.ts)
- 클래스명을 AuthGuard로 만들었는데, 이 클래스명은 @nestjs/core에도 있는 클래스이기 때문에 제대로 import를 해야됨
- 아래와 같이 reflector를 이용해서 PublicDecorator 붙여준 메서드는 무조건 true를 반환하여 AuthGuard를 타게 되더라도 통과가 될 수 있게 설정을 해주면 된다
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
export class AuthGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 만약에 Public 데코레이터가 존재하면 true를 반환
const isPublic = this.reflector.get('public', context.getHandler());
if (isPublic && typeof isPublic === 'boolean') {
return true;
}
const req = context.switchToHttp().getRequest();
if (!req.user || req.user.type !== 'access') {
return false;
}
return true;
}
}
반응형
'Backend > Backend Engineering' 카테고리의 다른 글
| MSA 공부하기 전에 기본 예습하기 using NestJS (7) | 2024.11.08 |
|---|---|
| 요청이 백엔드로 전달되는 과정 (0) | 2024.08.31 |