두가지 방법
1. findOne 메소드를 이용해서 이미 같은 유저 이 름을 가진 아이디가 있는지 확인 하고 없다면 데이터를 저장하는 방법입니다.
하지만 이 방법은 데이터베이스 처리를 두번 해줘야 합니다.
2. 데이터베이스 레벨에서 만약 같은 이름을 가진 유저가 있다면 에러를 던져주는 방법입니다
두번째 방법으로 구현
- user.entity.ts에서 원하는 유니크한 값을 원하는 필드 값을 정해 주시면 됩니다.
테스트
- 이미 있는 유저를 다시 생성하려 하면 아래와 같이 에러가 나옵 니다. 하지만 그냥 500 에러를 던져버립니다.
- Nest JS에서 에러가 발생하고 그걸 try catch 구문인 catch에서 잡아주지 않으면 에러가 Controller 레벨로 가서 그냥 500 에러를 던져 버립니다.
- 이러한 이유 때문에 try catch 구문으로 에러를 잡아줘야합니다.
비밀번호 암호화 하기
이번 시간에는 유저를 생성할 때 현재는 비밀번호가 그대로 데이 터베이스에 저장됩니다.
그래서 비밀번호를 암호화 해서 저장을 하는 부분을 구현해주겠습니다.
bcryptjs
이 기능을 구현하기 위해서 bcryptjs 라는 모듈을 사용하겠습니 다.
npm install bcryptjs --save
import * as bcrypt from 'bcryptjs';
비밀번호를 데이터베이스에 저장하는 방법
1. 원본 비밀번호를 저장 (최악) - 1234 ====> 1234
2. 비밀번호를 암호화 키(Encryption Key)와 함께 암호화 (양방향)
- 어떠한 암호를 이용해서 비밀번호를 암호화 하고 그 암호를 이용하여 복호화도 가능
-https://www.online-toolz.com/tools/text-encryption-decryption.php
- 1234 ====>gUuFwNo4zkMV+erdGtBlf5NunNgcELQuiCFJmCU4F+E=
- gUuFwNo4zkMV+erdGtBlf5NunNgcELQuiCFJmCU4F+E= ====> 1234
- 암호화 키가 노출되면 알고리즘은 대부분 오픈되어있기 때문에 위험도 높음
3. SHA256등으로 해시(Hash)해서 저장 (단방향)
- 1234 ====> 03ac674216f3e15c761ee1a5e255f067953623c8b3...
- https://emn178.github.io/online-tools/sha256.html
- 레인보우 테이블을 만들어서 암호화된 비밀번호를 비교해서 비밀번호 알아냄
레인보우 테이블
1234 ====> 03ac674216f3e15c761ee1a5e255f067953623c8b3
letmein ==> 1c8bfe8f801d79745c4631d09fff36c82aa37fc4cce4f
등등등 ... 대부분 유저들은 비슷한 암호를 사용
A 유저 비밀번호 1234 === 03ac674216f3e15c761ee1a5e255f067953623c8b3
B 유저 비밀번호 1234 === 03ac674216f3e15c761ee1a5e255f067953623c8b3
5. 솔트(salt) + 비밀번호(Plain Password)를 해시Hash로 암호화 해서 저장
암호화할 때 원래 비밀번호에다 salt를 붙인 후에 해시로 암호화를 한다.
예) 1234 =====> salt_1234
letmein ====> salt_letmein
A 유저 비밀번호 1234 ===> kenfuduWssW_1234 ======>
bcrypt를 이용해서 비밀번호를 암호화한 후 데이터베이스에 저장 하는 부분을 구현하겠습니다.(소스코드 구현)
JWT (JSON Web Token)에 대하여
JWT란 무엇인가요?
- 당사자간에 정보를 JSON 개체로 안 전하게 전송하기위한 컴팩트하고 독립적인 방식을 정의하는 개 방형 표준 (RFC 7519)입니다.
- 이 정보는 디지털 서명이되어 있 으므로 확인하고 신뢰할 수 있습니다.
- 간단하게 얘기하자면 정보를 안전하게 전할 때 혹은 유저의 권 한 같은 것을 체크를 하기 위해서 사용하는데 유용한 모듈입니 다
서버에서 요청과 같이 온 headers랑 payload 를 가져오고 서버안에 가지고 있는 Secret을 이용해서 Signature 부분을 다시 생성합니다.
그래서 그 둘이 일치하면 통과가 됩니다.
sdlKxwRJSMeKKF2Q....
sfewfwefRJSMeKKF2Q....
클라이언트에서 온 Headers + 클라이언트에서 온 Payload + 서버에서 가지고 있는 Secret Text
PassPort,JWT 이용해 토큰 인증 후 유저 정보 가져오기
필요한 모듈들 설치하기
@nestjs/jwt
- nestjs에서 jwt를 사용하기 위해 필요한 모듈
@nestjs/passport
- nestjs에서 passport를 사용하기 위해 필요한 모듈
passport
- passport 모듈
passport-jwt - jwt 모듈
npm install @nestjs/jwt @nestjs/passport passport passport-jwt --save
애플리케이션에 JWT 모듈 등록하기
1. auth 모듈 imports에 넣어주기
Secret
- 토큰을 만들 때 이용하는 Secret 텍스트( 아무 텍스트나 넣어줘도 됩니다)
ExpiresIn
- 정해진 시간 이후에는 토큰이 유효하지 않게 됩니다.
60 * 60 은 한시간 이후에는 이 토큰이 더 이상 유효하지 않게 됩니다
애플리케이션에 Passport 모듈 등록하기
1. auth 모듈 imports에 넣어주기
로그인 성공 시 JWT를 이용해서 토큰 생성해주기 !!!
1. Service 에서 SignIn 메소드에서 생성해주면 됩니다.
auth 모듈에 JWT를 등록해주었기 때문에 Service에서 JWT를 가져 올 수 있습니다.
2. Token을 만드려면 Secret과 Payload가 필요합니다.
Payload 에는 자신이 전달하고자 하는 정보를 넣어주시면 됩니다.
Role 정보든, 유저 이름이든, 이메일이든....하지만 Sensitive한 정보는 넣으시면 안됩니다.
이렇게 Payload를 이용해서 JWT에서 토큰을 만들 때 사용하는 Sign 메소드를 이요해서 토큰을 생성해줍니다
Passport, Jwt 이용해서 토큰 인증 후 유저 정보 가져오기
저번 시간에 JWT를 이용해서 유저가 로그인 할 때 토큰을 생성해줬 습니다.
그래서 이제는 그 유저가 요청을 보낼 때 그 요청 안에 있는 Header에 토큰을 넣어서 요청을 보내는데 요청 안에 Payload가 있습니다.
그리고 payload 안에 유저 이름을 넣어줬습니다.
그리고 토큰이 유효한 토큰인지 서버에서 secret text를 이용해서 알아내면 payload 안에 유저 이름을 이용해서
데이터베이스 안에 있는 유저 이름에 해당하는 유저 정보를 모두 가져올수 있습니다.
이 러한 처리 를 쉽게 해주는게 Passport 모듈 입니다.
그래서 Passport 모듈을 이용해서 이 부분을 구현해보겠습니다
현재 해야하는 부분은 5번과 6번 부분입니다.
토큰을 가지고 요청을 보낼때 서버에서 그 토큰이 유효(valid)한 것인지 체크한 후 만약 유효한것이면
payload 안에 들어있는 username을 이용해서 데이터베이스에 있는 유저인지 체크합니다.
있는 유저라면 유저 객체를 데이터베이스에서 가져옵니다. 없는 유저라면 에러를 보냅니다.
위에 과정을 구현하는 순서
- @types/passport-jwt 모듈
(passport-jwt 모듈을 위한 타입 정의 모듈)
1. jwt.strategy.ts 파일 생성
방금 만든 JwtStrategy를 사용하기 위해서
AuthModule Providers 항목에 넣어주고 다른 곳에서도 JwtStrategy와 PassportModule 도
사용해줘야 하기 때문에 exports항목에도 넣어줍니다.
요청안에 유저 정보(유저 객체)가 들어가게 하는 방 법
validate 메소드에서 return 값을 user 객체로 주었습니다. 그래서 요청 값안에 user 객체가 들어있으면 하는데 현재 요청을 보낼때 는 user 객체가 없습니다. 어떠한 방식으로 가질수있나요 ?!
UseGuards
UseGuards안에 @nestjs/passport에서 가져온 AuthGuard()를 이 용하면 요청안에 유저 정보를 넣어줄수있습니다.
NestJS에서 Middleware들에 대해서
Nest JS에는 여러가지 미들웨어가 있습니다.
Pipes, Filters, Guards, Interceptors 등의 미들웨어로 취급되는 것들이 있는데 각각 다른 목적을 가지며 사용되고 있습니다.
각각의 미들웨어가 불러지는(called) 순서
middleware -> guard -> interceptor (before) -> pipe -> controller -> service -> controller
-> interceptor (after) -> filter (if applicable) -> client
커스텀 데코레이터
요청안에 유저가 들어가게 하려면....
req.user 하면 유저 객체를 얻을수 있다
req.user가 아닌 바로 user라는 파라미터로 가져올수있는 방법 은 ??
커스텀 데코레이터를 이용하면 됩니다.
인증된 유저만 게시물 보고 쓸 수 있게 만들기
이번 시간에는 인증된 유저만 게시물을 보고 쓰고 업데이트 하는 작업을 할 수 있게 만들겠습니다.
유저에게 게시물 접근 권한 주기
1. 인증에 관한 모듈을 board 모듈에서 쓸 수 있어야 하기에 board module에서 인증 모듈 imports 해오기
(이렇게 되면 AuthModule에서 export 하는 어떠한 것이든 board Module에서 사용 가능하게 됩니다 )
2. UseGuards(AuthGuard())를 이용해서 요청을 줄 때 올바른 토큰을 가지고 요청을 주는지 본 후에 게시물에 접근 할 권한을 줍니다.
그리고 이 AuthGuard는 각각의 라우트 별로 줄 수도 있고 한번에 하나의 컨트롤러 안에 들어있는 모든 라우트에 줄 수도 있습니다.
현재는 board 컨트롤러 안에 있는 모든 라우트에 AuthGuard를 적용해보겠습니다
이렇게 되면 이제 올바른 토큰을 넣어서 요청을 주지 않으면 401 Unauthorized 에러가 나게 됩니다.
게시물에 접근하는 권한 처리
유저와 게시물의 관계 형성 해주기
현재는 유저나 게시물에 생성할 때 그 둘의 관계에 대해서는 설 정해준게 없습니다.
게시물을 생성 할 때도 어떤 유저가 생성해 줬는지 정보를 같이 넣어주어야 합니다.
그래서 이 유저와 게시 물 관계 부분을 처리해주겠습니다.
Type은 엔티티
게시물을 생성 할 때 유저 정보 넣어주기
'모카 스터디 > Nest' 카테고리의 다른 글
NestJS를 위한 express 핵심 원리 (0) | 2023.08.01 |
---|---|
Nest JS 로그 및 설정 [John Ahn] (0) | 2023.07.30 |
Nest JS TypeORM 연동 [John Ahn] (0) | 2023.07.30 |
Nest JS DTO 및 Pipe [John Ahn] (0) | 2023.07.30 |
Nest JS 기본 [John Ahn] (0) | 2023.07.30 |