[Nest] nest guard
nest 프로젝트를 진행하며 로그인하여 jwt 발급 및 인증까진 쉽게 이해하며 진행했다 생각했는데
페이지 권한 제어로 들어가니 guard동작에 대해 헷갈려 정리해보려 한다.
jwt란?
jwt는 jason web token으로 json 포맷을 활용하여 사용자에 대한 속성을 저장하는 web token이다.
토큰 자체를 정보로 사용하는 self-contained 방식이다.
jwt구조는 Header, Payload, signature 으로 이루어져있다.
헤더는 토큰의 타입과 알고리즘 방식을 지정해주고
페이로드는 토큰의 정보가 들어있다. (토큰 발급자, 토큰 제목, 토큰 만료시간 등)
시그니처는 토큰을 인코딩 하거나 유효성 검증시 사용하는 고유한 암호화 코드이다.
nestjs guard
nestjs에서 guard는 특정 상황들에 따라서,
주어진 request가 route handler에 의해 handling 될지 말지 결정하는 역할을한다.
쉽게 말하면 컨트롤러가 요청을 처리하기 전 요청에 문제가 없는디 확인하는 것이다.
(guard는 모든 middleware 다음에 실행되고 intercepter나 pipe 이전에 실행된다.)
guard는 인증된 사용자의 접근 제어와 요청의 보안을 담당한다.
인증된 사용자의 접근 제어, 권한 부여, 요청의 유효성 검사에 사용된다.
(인증 관련해서는 middleware인 passport가 담당한다 -> 따로 정리)
passport에서 토큰의 검증과 해독을하고, 이 결과를 가지고 guards는 요청의 인증상태 확인
canActivate 인터페이스
canActivate는 nestjs에서guard를 구현할때 사용하는 인터페이스로
canActivate에 메서드를 정의하여 특정 조건을 검사하고 요청이 허용되는 여부를 결정하게 된다.
코드적용
일단 내가 짠 코드에 기반해서 이해하려니 계속 꼬이는거 같다.
(jwt 사용과 guard사용 할때 누가 어떤 역할 해야하는지)
아예 잘못된거 같아서 다시 정리하여 처음부터 천천히 적용 해보겠다.
먼저 jwtStrategy 클래스이다.

일단 첫째로 막힌 부분이 이 jwtStrategy가 어디에 쓰이는지 모르겠어서 찾아보았다.
일단 등록은 애플리케이션의 모듈 설정 파일에서 해주고있었다.

위처럼 provider로 jwtStrategy를 등록하여 줬었다.
헷갈린 이유는 각각의 역할을 정확히 이해하지 못해서 그런거 같다.
jwtStrategy의 역할은 jwt를 검증하고 검증된 정보를 사용하여 요청하는데 사용된다.
하지만 지금 해당 postbox 모듈에선 토큰 검증이 아니라
로그인 정보 검증후 토큰을 생성해 반환해줘야 한다. (jwtService이용)
그렇기 때문에 위에 설정 해놓은 jwtStrategy는 쓸일이 없었고 어디에 사용되는지 알 수 없었다.ㅎ
정작 사용되어야할 모듈에는 설정을 안해놓고 오류나서 exports로 설정하여 외부 모듈에서 가져다 사용했다.
정리하면 postbox 모듈에선 토큰 검증이 필요없었고 jwtStrategy는 postbox모듈이 아니라
letter모듈에 만들어 줬어야 했다.
JwtStrategy를 옮겨주고 모듈 설정을 다시 해주었다.
letter모듈에 JwtStrategy사용 할 수 있도록 아래처럼 등록해주고

letterController에서 어떻게 토큰을 받고있었나 (로그인 성공하면 바로 이동하는 페이지)보았더니

위처럼 사용하고 있었다.
여기서 다시 AuthGuard()는 모듈에서 등록한 Stratregy를 자동으로 가져온다고 한다. (어라)
그래서 그 전 코드가 작동을 했던거 같다.
[NestJS] AuthGuard는 어떻게 JwtStrategy를 찾는걸까? 마법인가? 🥳
안녕하세요! 개발자 Jay입니다. 오늘은 Nest.js 스터디중 잘 이해가 안 되었던 것들에 대해 정리해보려고 합니다. 일단 갑자기 Nest.js 스터디를 하게된 건 회사에서 사용하는 백엔드 애플리케이션
jay-ji.tistory.com
그와 관련된 글이다.
Postbox 모듈에서 export로 passport, jwtStrategy를 외부에서 사용가능하게 해줬고
letter에서 AuthGuard()했을때 jwtStrategy가 적용 된것이다.
(그럼 원래 했던게 맞았네? 다시 돌아왔다..ㅎ)
guard의 사용
위에 코드에서 처럼 @Useguards() 데코레이터를 사용하는데
클래스에 전역으로 사용된 guard먼저 실행되고 그 후 컨트롤러에 사용된 guard가 실행된다.
Guard는 middleWare와 달리 실행 콘텍스트 인스턴스에 접근이 가능하기 때문에 인가에 사용된다.
guard 생성
그럼 다시 원래 하려고한 guard를 만들어 보겠다.
필요한 guard는 로그인 후 이동하는 api가 지금은 로그인을 하지않아도 api요청만으로도 접근이 가능하다.
그래서 이를 막아줄 MyAccessGuard를 구현 해 보겠다.
간단한 흐름은 guard가 실행되면 http요청에 접근해 헤더값을 가져온다.
jwtService를 사용해 헤더에서 postboxId를 추출하고 이 값을 요청의 postboxId값과 비교한다.
이 두 값이 일치할 경우에만 접근을 허용한다.

위처럼 구현 하려했으나
controller에서 요청을 받을때 id값을 받아 오지않고 커스텀 데코레이터를 만들어 받아온 헤더로 id값을
추출하고 있었다.
그냥 토큰이 있는것만 확인하고 넘기는게 현재 코드 였던거 같아 (AuthStrategy가 있었기때문에)
수정 해보도록 해야는데, 프론트까지 건드리게 되어버렸다..
요청에 PostBoxId를 넣을 수 있을지.. 프론트는 잘 모르는데..
router.push 해줄때 id를 같이 보내주면 되나? 아니면 지금 페이지 이름으로 된걸 id로 밨꿔줘야 하나?
.
.
.
일단 기능은 완성하였다. 이게 좋은 방법인지는 잘 모르겠으나 일단 한 거대로 정리해보겠다.
프론트의 경우 위에서 말한거 처럼 페이지를 이름으로 요청하던것을 id 요청으로 바꾸었다.
바뀐 백엔드 api를 보면 아래와 같다.

원래 이거처럼 토큰에서 id를 추출하여 사용자를 찾고있었다. (커스텀 데코레이터사용)
즉 권한확인은 하지않고 토큰으로 한번 접근하고 나면 api조정으로 다른 페이지로 접근이 되었다.
(데이터는 토큰으로 불러오기 때문에 본인 데이터만 불러와지고 현재 접속자의 이름 정도만 바뀌긴 했는데..
그래도 막아 주려면 이렇게 하는거 일까나..?)

퀴리로 현재 로그인요청하고 있는 사용자의 id 받고이것을 MyAccessGuard에서 토큰에서 추출한 id와
비교를한후 일치하는 경우에만 ture를 리턴해 컨트롤러를 실행하도록 변경하였다.
이렇게 해주면 한번 로그인 하여 해당 api로 페이지 이동한 상태에서 api조절을해도 access denied 된다.

guard코드로 http request에서 토큰과 url의 id값을 가져와서 비교하여 일치하는 경우에만 ture를 리턴해준다.
하고 나니 크게 어려운 부분은 없어보이는데 생각보다 삽질을 많이해서 오래걸렸다.
그리고 일반적으로 이런 방식으로 하는지도 잘 모르겠어서 다른 프로젝트들을 찾아 보려한다.
+ 바꾸고 나니 프론트 렌더링에도 문제 생겨서 고쳐야한다..