코드캠프 강의에서는 passport를 사용해서 jwt 인가를 처리했었다.
코드팩토리 강의에서는 Guard를 직접 만들어서 적용했다.
물론 남이 만들어놓은 기능을 가져다 사용하는게 쉽지만, 직접 만들어보는게 세부적인 내용을 알 수 있어서 좋고 커스텀 할 수도 있으니 알고 있는게 당연히 좋을 것이다.
(강의에서 작성했던 코드들은 아래에 첨부하겠다).
++ Guard의 기본적인 느낌의 내용은 이 글을, 실 사용적인 코드를 보려면 아래 링크의 글을 보기
https://springdream0406.tistory.com/187
Guard2
- Guard란?Guard는 권한등 조건을 확인한 후 요청이 라우트 핸들러로 전달될지 말지를 결정한다. 이 과정을 우린 흔히 인가 (Authorization)이라고 부르며, 요청을 보낸 사용자가 요청을 수행할 자격이
springdream0406.tistory.com
개인적인 생각으로 코드들의 단점 중 한개는, 다른 서비스에 작성된 코드를 가져다 사용하기 위해 의존성 주입이 되어있어, 해당 Guard를 사용하는 컨트롤러의 모듈들에 일일이 해당 서비스들이 포함된 모듈을 import 처리해줘야한다.
basic의 경우 auth만 해주면 되지만, bearer의 경우 auth와 users 두개를 해줘야하다보니 굉장히 번거러워 보인다.
때문에 실제 사용하게 되면 코드를 좀 변경해줘야할텐데,
우선 basic의 경우 로그인에 사용되었는데,
토큰 가져오는것과 디코드 하는 코드는 굳이 이 auth에 작성하지 말고 다른곳에 작성하면 되지 않을까 싶다.
그리고 user 데이터 조회해서 req에 넣을 필요없이, email과 password를 req에 넣은 후, 로그인 api에서 조회하고 return 처리해주면 될듯 싶다.
++ 로그인 api를 보니 디코드도 필요없어 보인다. 그냥 토큰검증만 하면 될듯싶다.
bearer는 access와 refresh때문에 거의 모든 api에서 사용될텐데,
token과 verify는 앞서 말한것과 같고, user데이터 조회역시 토큰에 해당 내용이 담겨 있기 때문에 유저데이터 조회할 필요없이, 토큰에서 값 가져다가 req에 집어넣어주면 될듯 싶다.
종합해서 정리하자면 guard에서는 딱 헤더의 데이터 추출해서 검증과 디코드 하는 과정만 거치고, 해당 데이터를 활용하는건 해당 api에서 처리하는게 좋아보인다.
이런 고칠점과 코드 중복 등 실제 사용하면 리팩토링을 좀 해주고 쓰도록 하자.
- basic-token.guard.ts
/**
* 구현할 기능
*
* 1) 요청객체 (request)를 불러오고
* authorization header로부터 토큰을 가져온다.
* 2) authService.extractTokenFromHeader를 이용해서
* 사용할 수 있는 형태의 토큰을 추출한다.
* 3) authService.decodeBasicToken을 실행해서
* email과 password를 추출한다.
* 4) email과 password를 이용해서 사용자를 가져온다.
* authService.authenticatieWithEmailAndPassword
* 5) 찾아낸 사용자를 (1) 요청 객체에 붙여준다.
* req.user = user
*/
@Injectable()
export class BasicTokenGuard implements CanActivate {
constructor(private readonly authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
// {authorization: 'Basic asdfasdfasdfjk'}
// asdfasdfasdfjk
const rawToken = req.headers['authorization'];
if (!rawToken) {
throw new UnauthorizedException('토큰이 없습니다!');
}
const token = this.authService.extractTokenFromHeader(rawToken, false);
const { email, password } = this.authService.decodeBasicToken(token);
const user = await this.authService.authenticateWithEmailAndPassword({
email,
password,
});
req.user = user;
return true;
}
}
@Post('login/email')
@UseGuards(BasicTokenGuard)
postLoginEmail(
@Headers('authorization') rawToken: string, //
) {
// email:password -> base64
// asdfasjdfhkajshdfkjasfdfkaakjhrasndfasdf -> email:password
const token = this.authService.extractTokenFromHeader(rawToken, false);
const credentials = this.authService.decodeBasicToken(token);
return this.authService.loginWithEmail(credentials);
}
- bearer-token.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthService } from '../auth.service';
import { UsersService } from 'src/users/users.service';
@Injectable()
export class BearerTokenGuard implements CanActivate {
constructor(
private readonly authService: AuthService,
private readonly usersService: UsersService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const rawToken = req.headers['authorization'];
if (!rawToken) {
throw new UnauthorizedException('토큰이 없습니다!');
}
const token = this.authService.extractTokenFromHeader(rawToken, true);
const result = await this.authService.verifyToken(token);
/**
* requset에 넣을 정보
*
* 1) 사용자 정보 - user
* 2) token - token
* 3) tokenType = access | refresh
*/
const user = await this.usersService.getUserByEmail(result.email);
req.user = user;
req.token = token;
req.tokenType = result.type;
return true;
}
}
@Injectable()
export class AccessTokenGuard extends BearerTokenGuard {
async canActivate(context: ExecutionContext): Promise<boolean> {
await super.canActivate(context);
const req = context.switchToHttp().getRequest();
if (req.tokenType !== 'access') {
throw new UnauthorizedException('Access Token이 아닙니다!');
}
return true;
}
}
@Injectable()
export class RefreshTokenGuard extends BearerTokenGuard {
async canActivate(context: ExecutionContext): Promise<boolean> {
await super.canActivate(context);
const req = context.switchToHttp().getRequest();
if (req.tokenType !== 'refresh') {
throw new UnauthorizedException('Refresh Token이 아닙니다!');
}
return true;
}
}
@Post()
@UseGuards(AccessTokenGuard)
postPosts(
@Req() req: any,
@Body('title') title: string,
@Body('content') content: string,
) {
const authorId = req.user.id;
return this.postsService.createPost(authorId, title, content);
}
- 코드캠프 인가 강의
학습 페이지
www.inflearn.com
- 코드팩토리 Guard 강의
학습 페이지
www.inflearn.com
https://springdream0406.tistory.com/119
jwt
- 설치yarn add @nestjs/jwt @nestjs/passport passport passport-jwtyarn add --dev @types/passport-jwt
springdream0406.tistory.com
'코딩 > Nest.js' 카테고리의 다른 글
Class Validator & DTO(Data Transfer Object) (0) | 2024.10.20 |
---|---|
커스텀 데코레이터 (1) | 2024.10.20 |
Header에 id:password (0) | 2024.10.18 |
소셜 로그인 (0) | 2024.06.15 |
GraphQL (미완) (0) | 2024.06.10 |