목차
준비 과정
CLI를 통한 모듈, 컨트롤러, 서비스 생성
•
nest g module auth
•
nest g congroller auth --no-spec
•
nest g service auth --no-spec
UserEntity 생성
•
유저에 대한 인증이니 유저가 필요
•
유저 데이터를 위한 유저 엔티티 생성
Repository 생성
•
User Entity를 생성, 수정, 삭제 등의 로직 처리를 위해 Repository 생성
•
생성된 User Repository 사용을 위해 module의 provider에 추가 + User Entity를 module에 import
I. 로직 구현
I. 회원가입
•
dto 생성을 통해 username, password로 회원가입을 할 수 있도록 구현
II. 유저 데이터 유효성 체크
•
유저를 생성할 때 원하는 이름의 길이, 비밀번호 길이를 갖는지 Column에 조건 생성
•
class-validator 모듈 사용
•
DTO 파일에서 Request로 들어오는 값을 정의해주고 있기 때문에 DTO파일의 값에 유효성 조건을 넣음
ValidationPipe
•
요청이 컨트롤러에 있는 핸들러로 들어왔을 때 DTO에 있는 유효성 조건에 맞게 체크해주기 위해 ValidationPipe를 넣어야 함
III. 유저 이름에 유니크 값 주기
•
이미 사용되는 유저 이름이 있는 경우 에러 발생시키기
I. Repository에서 findOne 메소드 사용
•
이미 같은 유저 이름을 가진 아이디가 있는지 findOne으로 확인 후 데이터를 저장
•
데이터베이스 처리를 두번 해주어야 함
II. 데이터베이스 레벨
•
데이터베이스 레벨에서 같은 이름이 있다면 에러 발생
•
이 방법으로 구현
•
user.entity.ts에서 원하는 유니크한 값을 원하는 필드 값을 정하면 됨
◦
이미 있는 유저를 다시 생성하려 하면 500 에러가 발생함
▪
NestJS에서 에러가 발생하고 try catch가 없으면 이 에러가 Controller 레벨로 가기 때문
▪
해결을 위해 try catch 문 사용
...
async createUser(authCredentialsDto: AuthCredentialsDto): Promise<void> {
const { username, password } = authCredentialsDto;
const user = this.create({ username, password });
try {
await this.save(user);
} catch (error) {
if (error.code === '23505') {
throw new ConflictException('Existing username');
} else {
throw new InternalServerErrorException();
}
}
}
}
TypeScript
복사
•
console.log(error)를 했을 경우 에러코드가 23505 이기 때문에 if 문을 위와 같이 사용
IV. 비밀번호 암호화
•
암호화하지 않으면 데이터베이스에 비밀번호가 그대로 저장됨
bcryptjs
•
암호화하는데 사용하는 모듈
•
npm install bcryptjs --save
•
import * as bcrypt from ‘bcryptjs’;
비밀번호를 데이터베이스에 저장하는 방법
1.
원본 비밀번호를 저장 → 최악
2.
비밀번호를 암호화 키(Encryption Key)와 함께 암호화 (양방향)
•
어떠한 암호를 이용해 비밀번호를 암호화
•
그 암호를 이용해 복호화도 가능
•
암호화 키가 노출되면 알고리즘은 대부분 오픈되어있기 때문에 위험도 높음
3.
SHA256 등 Hash로 암호화해서 저장 (단방향)
•
복호화가 불가능함
•
레인보우 테이블을 만들어서 암호화된 비밀번호를 비교해서 비밀번호를 알아낼 수 있음
◦
레인보우 테이블 - 대부분 유저들은 비슷한 암호를 사용하기 때문에 (키, 해쉬값)을 저장한 빅데이터
4.
솔트 + 비밀번호를 해시로 암호화해서 저장
•
암호화할 때 원래 비밀번호에 salt를 붙인 후 Hash로 암호화
•
같은 비밀번호라도 다른 비밀번호가 저장되어 알기 어려움
V. 로그인 구현
•
username이 있으며, password가 암호화된 password와 비교했을 때 같으면 로그인
VI. 인증된 유저만 게시물 보고 쓰게 하기
•
II. JWT 이후 과정
유저에게 게시물 접근 권한 주기
1.
인증에 관한 모듈을 board 모듈에서 쓸 수 있어야 함
a.
board module에서 인증 모듈 imports
•
AuthModule의 exports 부분을 board Module에서 사용 가능
2.
UseGuards(AuthGuard())를 이용해 올바른 토큰을 가지고 요청을 하는지 확인 후 게시물에 대한 접근 권한을 줌
a.
AuthGuard는 각각의 라우트 별로 줄 수 있고, 한번에 하나의 컨트롤러 안에 있는 모든 라우트에 줄 수 있음
II. JWT
•
로그인을 할 때 로그인한 고유 유저를 위한 토큰을 생성해야 함
•
토큰을 생성할 때 JWT라는 모듈을 사용
JWT (JSON Web Token)
•
당사자 간에 정보를 JSON 개체로 안전하게 전송하기 위한 개방형 표준(RFC 7519)
•
디지털 서명이 되어 있으므로 확인하고 신뢰할 수 있음
•
정보를 안전하게 전할 때, 또는 유저의 권한을 체크하기 위해 사용하는 모듈
JWT의 구조
•
Header
◦
토큰에 대한 메타 데이터 포함
◦
타입, 해싱 알고리즘 (SHA256, RSA, …)
•
Payload
◦
유저 정보, 만료 기간, 주제 등
•
Verify Signature
◦
토큰이 보낸 사람에 의해 서명되었으며 어떤 식으로든 변경되지 않았는지 확인하는데 사용되는 서명
JWT 사용 흐름
1.
Admin만 볼 수 있는 글을 Admin 유저가 보려 함
2.
요청을 보낼 때 보관하고 있던 Token을 Header에 넣어 같이 보냄
3.
서버에서 JWT를 통해 Token을 다시 생성한 후 비교
4.
통과가 되면 Admin 유저가 원하는 글을 볼 수 있음
I. JWT를 이용한 토큰 생성
•
필요 모듈
◦
@nestjs/jwt
▪
nestjs에서 jwt를 사용하기 위해 필요한 모듈
◦
@nestjs/passport
▪
nestjs에서 passport를 사용하기 위해 필요한 모듈
◦
passport
◦
passport-jwt
▪
jwt 모듈
npm install @nestjs/jwt @nestjs/passport passport passport-jwt --save
1.
애플리케이션에 JWT 모듈 등록
•
auth 모듈 imports에 넣어주기
•
secret - 토큰을 만들 때 이용하는 secret text (아무 텍스트나 가능)
•
expiresIn - 정해진 시간 이후 토큰이 유효하지 않게 됨. (60 * 60 은 한시간)
2.
애플리케이션에 Passport 모듈 등록
•
auth 모듈 imports에 넣어주기
3.
로그인 성공 시 JWT를 토큰에 넣어주기
•
Service에 SignIn 메소드에서 생성
•
Token을 만드려면 Secret과 Payload 필요
◦
Payload에는 자신이 전달하고자 하는 정보를 넣으면 됨
▪
민감 정보는 넣지 말것
II. Passport, JWT 이용해서 토큰 인증 후 유저 정보 가져오기
•
토큰이 유효한 토큰인지 서버에서 secret-text를 이용해 알아내면 payload 안에 정보를 이용해 데이터베이스 안에 있는 정보를 가져올 수 있음
•
이 처리를 쉽게 해주는 것이 Passport 모듈
구현
1.
@types/passport-jwt 모듈 설치
•
passport-jwt 모듈을 위한 타입 정의 모듈
•
npm install @types/passport-jwt --save
2.
jwt.strategy.ts 생성
3.
jwtStrategy를 사용하기 위해 AuthModule Providers 항목에 넣어주고, 다른 곳에서도 JwtStrategy와 PassportModule도 사용해줘야 하기 때문에 exports항목에 넣어줌
4.
요청 안에 유저 정보(유저 객체)가 들어가게 해야 함
•
validate 메소드에 return 값으로 user 객체를 주었지만 요청을 보낼 때는 user 객체가 없음
•
이를 해결하기 위해 UseGuards를 사용
5.
UseGuards 안에 @nestjs/passport에서 가져온 AuthGuard()를 이용해 요청 안에 유저 정보를 넣음
@Post('/authTest')
@UseGuards(AuthGuard())
authTest(@Req() req) {
console.log('req', req);
}
TypeScript
복사
참고
I. Custom Decorator
•
req.user를 통해 유저 객체를 얻을 수 있음
•
req.user가 아닌 바로 user라는 파라미터로 가져올 수 있는 방법은 커스텀 데코레이터 사용
◦
사용 안해도 됨