반응형

JWT를 이용해 인증, 인가를 구현하는 방법에 대해 설명한다.

 

로그인

  1. 로그인하기
    • 클라이언트에서 아이디, 패스워드 전달
  2. 서버에서 패스워드 암호화 후 DB에서 아이디, 패스워드 검증
  3. 서버에서 Access Token, Refresh Token 토큰 발행
    • Access Secret Key, Refresh Secret Key 각각 다르게 생성
  4. 서버에서 Refresh Token DB 저장
    • 추후 서버에서 강제 로그아웃하기 위해 필요
  5. 서버에서 Response 쿠키에 HttpOnly 사용상태로 리프레시 토큰 저장 후 클라이언트로 보내기
  6. 서버에서 클라이언트로 Access Token 값 보낸후 클라이언트에서 메모리(Redux) 저장

 

권한 필요한 API 통신

일반적으로 토큰은 요청 헤더의 Authorization 필드에 담겨서 보내지게 되는데 JWT에 해당하는 type은 Bearer입니다 → Authorization: <type> <credentials>

Global Guard에 Guards를 적용시키고 필요하지 않은 건 Public Decorator로 Pass하게 한다 (로그인, 회원가입 기능 등…)

Admin만 호출할 수 있는 게 있으면 Role에 담아서 보낸다

  1. 클라이언트에서 요청시 헤더에 Authorization: Bearer ${Access Token}을 붙여서 Access Token과 함께 API 요청을 하고 쿠키의 Refresh Token 내용을 같이 보낸다.
  2. 서버에서 Access Token을 검증하며 정상일 땐 그대로 처리한다 비정상일 땐 Access Token 만료 시나리오대로 진행한다.
  3. Refresh Token이 만료된 경우는 Refresh Token 만료 시나리오대로 한다.

 

권한에 따른 페이지 접근 처리

  1. Jwt의 payload 데이터에 해당 사용자의 역할 정보를 넣어서 암호화 시킨다.
  2. Access Token 발급 받을 때 Header에 Role 정보를 보낸다
  3. Role정보에 따라 페이지를 달리 처리한다
    • Next.js의 경우 admin이라는 라우팅하나 만들고 layout안에 검증식을 넣어놨다

 

Access Token 만료

잘못된 토큰 요청인지 만료되었는지를 클라이언트에게 보여줄 필욘 없기 때문에 유효하지만 않는지 체크만 하면 된다.

  1. 클라이언트에서 서버에 HTTP 요청을 보낸다
    • 모든 요청에는 Access Token은 Bearer에 담아서 보내고 Refresh Token은 쿠키에 담아 보낸다
  2. Access Token가 유효하지 않을 때 쿠키에 담아서 같이 보낸 Refresh Token 값으로 검증 후 정상일 경우 Access Token을 재발급하고 요청한 HTTP통신을 처리하며 Access Token값을 클라이언트에 Response Header로 보낸다
    • 조회 버튼 눌렀는데 발급만 해서 보내면 다시 또 조회버튼을 눌러야하니 사용자가 불편할 수도 있으니 이렇게 처리한다. 또한 HTTPS통신을 사용하면 Response Header값이 암호화 되어서 중간자 탈취에도 안전하다
  3. 클라이언트에서는 Fetcher의 Response에 Access Token키의 값이 왔을 때 Redux에 저장한다

 

Refresh Token 만료

Refresh Token이 만료된 경우 일반적으로 보안상의 이유로 사용자에게 재로그인을 요청하는 것이 일반적인 접근입니다.

재로그인을 통해 새로운 Access Token과 Refresh Token을 발급받는 것이 안전합니다.

  1. 클라이언트에서 Access Token이 만료되었을 때 쿠키에 담아서 보낸 Refresh Token에 대한 검증을 한다. 정상일 경우 Access Token을 재발급하지만 비정상인 경우 다시 로그인 하게끔 해야한다.
  2. 서버에서는 Cookie에 담긴 Refresh Token을 지운다.
  3. HTTPS통신으로 Header에 Refresh Token이 만료 되었다는 값을 담아서 보낸다.
    • HTTPS통신이라 암호화가 되어서 중간 탈취에 안전하다.
  4. 클라이언트는 Fetcher를 통해 Header에 만료되었다는 값이 들어오면 체크해서 Redux에 있는 Access Token을 초기화시키고 Login페이지로 이동시킨다.

 

자동 로그인

  1. 로그인 여부가 true이면 아래 내용 진행 (쿠키 저장을 브라우저 영역으로 저장한다)
  2. 페이지 처음 들어오면 Refresh Token이 존재하는 경우 같이 보낸다
  3. 서버에서 Refresh Token이 정상인 경우 Access Token 만료 시나리오를 따라간
  4. 서버에서 Refresh Token이 비정상인 경우 Refresh Token 만료 시나리오를 따라간다

 

새로고침시 로그인 유효 / 로그인 후 사용 가능한 페이지 접근

  1. 로그인 여부가 true이면 아래 내용 진행 (자동 로그인이 아니면 로그인시 세션 영역으로 만들어짐)
  2. 페이지 처음 들어오면 Refresh Token이 존재하는 경우 같이 보낸다
  3. 서버에서 Refresh Token이 정상인 경우 Access Token 만료 시나리오를 따라간다
  4. 서버에서 Refresh Token이 비정상인 경우 Refresh Token 만료 시나리오를 따라간다

 

 

로그아웃 (버튼)

  1. 서버에서 Refresh Token값이 있는 쿠키를 삭제한다.
  2. 클라이언트에서 메모리에 저장된 Access Token 삭제한다.
  3. 로그인 페이지로 이동시킨다.

 

강제 로그아웃

  1. 클라이언트는 항상 Refresh Token값이 정상인지랑 DB값하고 같은지 체크해야한다
  2. 블랙리스트 방식도 존재하지만 시간이 지나면 쓸모 없는 데이터(어차피 유효기간 지나서 인증 안 되는 JWT 등…)을 주기적으로 삭제해야하는 번거로움이 있기 때문에 선택 안 함
  3. 인메모리를 사용하면 더 효율적이긴하다
  • Token으로 클라이언트에게 주체가 넘어간 경우 서버에서 토큰을 폐기시킬 수 있는 방법은 쿠키로 보낸 Refresh Token값하고 DB에 저장된 Refresh Token값하고 같은지 체크해야한다
  • 로그인시 Redis 같은 인메모리DB에 token 정보를 저장하고, 로그아웃시 Redis에서 token 정보를 지움으로써 로그아웃 여부를 확인할 수 있다

 

소셜로그인

passport.js로 구현한다

  1. passport docs에 있는대로 처리하고 eamil정보를 받아서 따로 회원가입을 진행시키던가 강제로 회원가입 시켜서 회원정보정도는 관리해야한다
  2. 소셜로그인을 클릭한 이후에 동작은 로그인 프로세스와 동일하게 한다
    • 서버에서 Access Token, Refresh Token 토큰 발행
    • 서버에서 Refresh Token DB 저장
    • 서버에서 Response 쿠키에 Http Only 사용상태로 리프레시 토큰 저장 후 클라이언트로 보내기
    • 서버에서 클라이언트로 Access Token 값 보낸후 클라이언트에서 메모리(Redux) 저장

 

Access Token 탈취

  • 메모리에 저장하기 때문에 사용자가 직접 주지 않는 이상 탈취되진 않는다.
  • 기간을 짧게 잡아서 탈취되더라도 못 쓰게한다
  • 메모리에 담기 때문에 XSS나 CSRF 방지가 가능하다
  • HTTPS를 통해 패킷 탈취되어도 암호화가 되어서 안 보이게 된다

 

Refresh Token 탈취

  • 사용자는 리프레시 토큰을 쿠키에 저장해서 보낸다. (Http only, Secure 보안 사항 등록)
  • 리프레시 토큰은 DB에 저장한다
    • JWT 장점은 클라이언트가 인증 권한을 관리해서 서버에서 로그아웃 시킬 수 있는 방법이 없는데 DB나 Redis에 Refresh Token값을 저장하고 Refresh Token 검증시 저장값 하고 다르면 로그아웃시켜버리게 한다

 

XSS 방어로 쿠키 탈취 막기

  • XSS 방어 관련된 라이브러리 (Express의 Helmet 등…)
  • 웹 방화벽
  • Cookie의 Http Only 사용

 

CSRF 막기

  • SameSite설정 → 브라우저 호환 문제 발생으로 사용하기 힘듬
  • 쿠키에 인가 정보 넣지 않기 → 메모리 보관 (Private 변수, Redux)

 

 

🔗 참고 및 출처

https://inpa.tistory.com/entry/AXIOS-📚-CORS-쿠키-전송withCredentials-옵션

https://velog.io/@cada/토근-기반-인증에서-bearer는-무엇일까

https://velog.io/@hahan/JWT란-무엇인가

https://sokdak-sokdak.tistory.com/11

반응형