NestJS - 8편 (JWT)
인증(Authentication) & 인가(Authorization)
- 인증(Authentication): 요청자가 자신이 누구인지 증명하는 과정
최근에는 JWT를 이용해 많이함. 미들웨어로 구현하는것이 좋은 사례임. - 인가(Authorization): 인증을 통과한 유저가 기능을 사용할 권리가 있는지 판별하는 것.
미들웨어는 실행 컨텍스트(ExecutionContext)에 접근하지 못하고 단순히 자신의 일만하고 next()를 호출하므로 다음에 어떤 핸들러가 실행될지 모르므로 가드(Guard)를 주로 이용함.
인증 실패시: 401 Unauthorized 또는 403 Forbidden 으로 응답 많이함.
인가 - 가드를 통한 인가(Authorization)
ex) 사용자 권한에 따라 접근 가능한 페이지 분리,
사용자 요금제에 따른 다른 기능 제공 등
CanActivate
인터페이스 구현
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return this.validateRequest(request);
}
private validateRequest(request: any) {
return true;
}
}
모든 Guard는 canActivate()를 구현해야한다.
현재의 request가 실행가능한 것인지 판단해 true 또는 false를 리턴한다.
인가 - 가드를 통한 권한 분리
특정 권한을 가지고 있는 유저만 접근 가능한 Guard를 구현하기.
가드 생성
ex)roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
가드를 적용할 컨트롤러
@Controller('user')
@UseGuards(RolesGuard)
export class UserController {}
// RoleGuard 대신 new RolesGuard() 를 사용할 수 있다.
하나의 메소드에만 적용하고싶다면 해당 메소드에 @UseGuards()
데코레이터를 붙이면된다.
인가 - 글로벌 가드 & 모듈 가드
일반적으로 글로벌 가드는 이용하지 않는다.(context 밖에서 벌어지므로 의존성 주입이 불가하므로)
인가 - 글로벌 가드
main.ts
파일
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
인가 - 모듈 가드
모듈
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
인가 - 권한 분리
그렇다면 권한별 분리는 어떻게 할까?
권한 데코레이터 생성
//roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
적용할 컨트롤러
@Post()
@Roles('admin')
async create(@Body() createUserDto: CreateUserDto) {
this.userService.create(createUserDto);
}
인가 - 가드 생성한 곳에서 분리한 권한 적용시키기
가드 생성
ex)roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return matchRoles(roles, user.roles);
}
}
인증
인증은 크게 2가지로 나눠진다. 세션 기반 인증과 토큰 기반 인증
세션
- 세션 기반 인증 : 로그인시 서버는 따로 생성해둔 세션DB에 해당 사용자를 저장시키고 이후 사용자로부터 요청이 올 때마다 세션 DB를 체크해 저장되어있는지 확인한다.
- 세션 인증 장점 : DB에 계속 저장중이므로 특정 사용자를 벤하거나 로그인된 다른 기기에서 다른기기의 로그아웃 등의 작업이 가능하다.
- 세션 인증 단점 : 요청마다 DB에 체크해야하므로 느리다(그래서 Redis를 주로 이용함). 세션 DB를 따로 두어야함. DB에 저장해야하므로 특정 길이 제한을 둔다.
토큰
- 토큰 기반 인증 : 로그인시 서버는 토큰을 생성해 보내주고 토큰을 따로 DB에 저장하지 않는다. 이후 사용자로부터 요청이 올 때마다 토큰에 대한 검증만 수행한다. JWT 를 주로 이용한다.
- 토큰 인증 장점 : 빠르다. Facebook, Google 등 계정으로 다른 서비스 로그인이 가능한 OAuth 구현이 가능하다. 따로 DB를 둘 필요가 없다.
- 토큰 인증 단점 : 토큰 자체가 정보이므로 탈취시 취약하다. ex) 토큰 유효기간 30분이라면 해당 사용자가 악의적인 사용자여도 내가 해당 사용자를 30분동안 벤하지 못함.
토큰이 대세이니 토큰을 알아보자!(JWT)
JWT(JSON WEB TOKEN)
- 헤더, 페이로드, 시그니처 3가지의 구성요소를 가며 .으로 구분된다
- 헤더와 페이로드는 base64로 인코딩되어있다.
헤더
{
"typ":"JWT",
"alg":"HS256"
}
typ: JWS와 JWE에 정의된 타입.
alg: 암호화 여부. 암호화 할경우 HS256등 명시. 암호화하지 않을 경우 "none"
페이로드
페이로드는 클레임(claim)이라고 불리는 정보들이다.
페이로드 - 등록된(Registered) 클레임
IANA JWT 클레임 레지스트리에 등록된 클레임이다.
필수는 아니지만 필수라고 생각하고 써야한다.(JWT 상호환성을 위해.)
- "iss"(Issuer, 발급자): . 누가 토큰을 발급(생성)했는지를 나타냅니다. 애플리케이션에서 임의로 정의한 문자열 또는 URI 형식을 가집니다.
- "sub" (Subject, 주제): 일반적으로 주제에 대한 설명을 나타냅니다. 토큰 주제는 발급자가 정의하는 문맥상 또는 전역으로 유일한 값을 가져야 합니다. 문자열 또는 URI 형식을 가집니다.
- "aud" (Audience, 수신자): 누구에게 토큰이 전달되는 가를 나타냅니다. 주로 보호된 리소스의 URL을 값으로 설정합니다.
- "exp" (Expiration, 만료 시간): 언제 토큰이 만료되는지를 나타냅니다. 만료 시간이 지난 토큰은 수락되어서는 안됩니다. 일반적으로 UNIX Epoch 시간을 사용합니다.
- "nbf" (Not Before): 정의된 시간 이후에 토큰이 활성됩니다. 토큰이 유효해 지는 시간 이전에 미리 발급되는 경우 사용합니다. 일반적으로 UNIX Epoch 시간을 사용합니다.
- "iat" (Issued At, 토큰 발급 시간): 언제 토큰이 발급되었는지를 나타냅니다. 일반적으로 UNIX Epoch 시간을 사용합니다.
- "jti" (JWT ID, 토큰 식별자): 토큰의 고유 식별자로써 같은 값을 가질 확률이 없는 암호학적 방법으로 생성되어야 합니다. 공격자가 JWT를 재사용하는 것을 방지하기 위해 사용합니다.
페이로드 - 공개(Public) 클레임
JWT 발급자가 공개해도 되는 클레임이다.
일반적으로 이름 충돌을 막기위해 IANA JWT 클레임 레지스트리에 이름을 등록한다.
보통 URI형식으로 정의한다.
{
"http://example.com/is_root": true
}
페이로드 - 비공개(Private) 클레임
JWT 발급자와 사용자간 서로 약속한 클레임이다.
이름 충돌에 주의해야한다.
시그니처는 이 토큰이 유효한 토큰인지 검사만 할 뿐 페이로드를 암호화하는것은 아니므로 비공개 클레임에 비밀번호 등 중요 정보를 절대! 넣으면 안된다.
시그니처
헤더와 페이로드를 base64로 인코딩하고 두 값을 .로 이어붙이고 헤더에서 정한 암호화 알고리즘(HS256 등)과 secret키로 암호화된 문자열이다.
시그니처는 이 토큰이 유효한 토큰인지 검사만 할 뿐 페이로드를 암호화하는것은 아니므로 비공개 클레임에 비밀번호 등 중요 정보를 절대! 넣으면 안된다.
유저 권한별 접근 기능 설정하기
참조 : NestJS로 배우는 백엔드 프로그래밍
'[NestJS]' 카테고리의 다른 글
# NestJS에서 Handlebars 이용하기 (0) | 2022.06.22 |
---|---|
[NestJS] - 7편 (미들웨어) (0) | 2022.04.21 |
[NestJS] - 6편 (DB연결하기 TypeORM 트랜잭션 마이그레이션) (0) | 2022.04.21 |
[NestJS] - 5편 (파이프) (0) | 2022.04.20 |
[NestJS] - 4편 (모듈~config파일) (0) | 2022.04.20 |