Just Do IT!

JWT에서 Access Token, Refresh Token이 필요한 이유 본문

개발 공부/Spring

JWT에서 Access Token, Refresh Token이 필요한 이유

MOON달 2024. 9. 11. 09:55
728x90
반응형

월요일부터 JWT 실습을 하고 있는데 token을 두 가지로 발급하는 이유에 대해 정리해보고자 한다.

 

JWT 토큰 인증 방식

어제 블로그글에도 정리했지만 JWT 토큰 인증 방식은 비밀키로 암호화를 해서 안전하게 통신한다.

JWT 토큰이 유저의 신원이나 권한을 결정하는 정보를 담고 있는 데이터 조각이니까.

 

JWT는 헤더, 페이로드, 서명 세 가지 정보를 base64로 인코딩한 값을 콤마('.')를 사이에 두고 이어붙인 형태로 생성된다.

  • 헤더: JWT 서명에 사용된 알고리즘을 담는다.
  • 페이로드: 토큰에 담긴 주체(Subject), 만료일(exp), 생성자(iss) 등을 담는다.
  • 시그니처: 헤더와 페이로드를 각각 base64로 인코딩한 후 콤마로 이어붙인다. 그리고 이를 헤더에 명시된 알고리즘으로 암호화한 값을 담는다.

이때 비대칭키 암호화 방식을 사용하기 때문에 서버측에서는 이 토큰을 받아서 시그니처를 복호화하여 디코딩하는 방식으로 토큰의 유효성을 검증할 수 있다.

 

 

그런데!

만약 이 토큰을 탈취했을 경우가 문제다. (보안 문제)

JWT 토큰을 탈취한 사람이 실제 주인인 것처럼 인증할 수 있고 서버에서 이를 구분할 수가 없기 때문이다.

그래서 유효 기간을 두고, 일정 시간이 지나면 토큰을 사용할 수 없도록 해야 한다.

 

또 여기서 문제.

유효기간이 짧은 경우 사용자가 로그인을 자주 해야 하므로 사용자 친화적이지 못하다.

물론 보안적으로는 아주 훌륭하다. 대표적으로 은행 어플이나, 은행 웹사이트인 경우 유효 기간이 나타나고 일정 시간 시간 이상 로그인하지 않는다면 자동으로 로그아웃됨으로서 보안을 유지한다.

 

그러나 그런 보안이 중요한 사이트라고 생각되지 않는다면, 사용자들은 당연히 불편해할 것이다.

그래서 나온 해결 방법이 바로 Access Token, Refresh Token 두 가지를 두는 것이다.

 

 

 

 

Access Token, Refresh Token

  • Access Token의 유효기간을 짧게 설정한다.
  • Refresh Token의 유효기간은 길게 설정한다. (필요하면 DB에 저장)
  • 사용자는 Access Token과 Refresh Token을 둘 다 서버에 전송하여 전자로 인증하고 만료됐을 시 후자로 새로운 Access Token을 발급받는다.
  • 공격자는 Access Token을 탈취하더라도 짧은 유효 기간이 지나면 사용할 수 없다.
  • 정상적인 클라이언트는 유효 기간이 지나더라도 Refresh Token을 사용하여 새로운 Access Token을 생성, 사용할 수 있다.

즉, 통신 과정에서 탈취당할 수 있는 Access Token의 만료 기간을 짧게 두고 Refresh Token은 주기적으로 재발급함으로써 피해를 최소화하는 것이다.

 

만약 유효기간이 만료되더라도 refresh token을 전송하여 다시 로그인할 필요 없이 access token을 재발급 받는 것이다.

또 같은 사용자가 여러 디바이스에서 접근하는 경우를 생각해서 각 디바이스 타입에 맞는 토큰이 필요할 것이다.

 

 

 

 

서버-클라이언트 통신

https://velog.io/@hyehyeonmoon/%EB%8F%99%EA%B8%80-JWT%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%97%90%EC%84%9C-%EB%B3%B4%EC%95%88%EC%9D%84-%EB%86%92%EC%9D%B4%EB%8A%94-%EB%B0%A9%EB%B2%95HTTPS-Cookie

  1. 로그인에 성공한 클라이언트는 Access Token, Refresh Token을 서버로부터 받는다.
  2. 클라이언트는 두 토큰을 로컬에 저장해놓는다.
  3. 클라이언트는 헤더에 Access Token을 넣고 API 통신을 한다. (=Authorization)
  4. 일정 시간이 지나면 Access Token은 만료된다.
    • 로그인한 사용자는 권한이 없는 사용자가 된다.
    • 클라이언트로부터 유효기간이 지난 Access Token을 받은 경우, 서버는 401 에러 코드로 응답하게 된다.
    • 401 에러 코드를 통해 클라이언트가 유효기간이 만료되었음을 알 수 있다.
  5. 헤더에 Access Token 대신 Refresh Token을 넣어 API를 재요청한다.
  6. Refresh Token으로 사용자의 권한을 확인한 서버는 응답쿼리 헤더에 새로운 Access Token을 넣어 응답한다.
  7. 만약 Refresh token도 만료된 경우 서버는 401 에러 코드를 다시 보내고, 이 때는 클라이언트가 재로그인해야 한다.

 

 

 

 

 

 

 

만약 Refresh Token이 탈취된다면?

OAuth에서는 Refresh Token Rotation을 제시한다.

Refresh Token Rotation은 클라이언트가 Access Token를 재요청할 때마다 Refresh Token도 새로 발급받는 것이다.

이렇게 되면 탈취자가 가지고 있는 Refresh Token은 더이상 만료 기간이 긴 토큰이 아니게 된다. 따라서 불법적인 사용의 위험은 줄어든다.

 

 

 

 

 

 

 

토큰은 어디에 저장해야 할까?

우선, 서버에서는 데이터베이스에 저장할 수 있다. 유효기간이 짧은 Access Token은 저장 여부가 각 프로젝트마다 다르겠지만, Refresh Token은 DB에 저장하고 변경 시에 주기적으로 바꿀 수 있다.

 

그럼 클라이언트에서는 어디에 저장할까?

session storage, local storage 같은 storage에 대한 접근 및 제어는 자바스크립트를 통해 이루어지기 때문에 XSS 등 스크립트 기반 공격이 가능하다. JWT는 특히 탈취되면 서버에서 구별할 수 없기 때문에 매우 위험하다.

 

  • XSS 공격 : 해커가 클라이언트 브라우저에 javascript를 삽입해 실행하는 공격
  • CSRF 공격 : 해커가 다른 사이트에서 우리 사이트의 API 콜을 요청해 실행하는 공격

 

그러므로 HTTPS에서 Secure Cookie와 HTTP Only 쿠키를 사용해 자바스크립트 기반 공격을 방어할 수 있다.

  • Secure cookie: 웹브라우저와 웹서버가 HTTPS로 통신하는 경우에만 웹브라우저가 쿠키를 서버로 전송하는 옵션
  • HTTP Only : 자바스크립트로 쿠키를 조회하는 것을 막는 옵션

 

어제 정리한 글에서도,

refresh token을 HTTP only cookie에 넣어주었다.

	public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
		// cookie > http only 설정 (다른 사람이 변경 불가능)
		// cookie에 담아두는 것이 안전하다
		Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken);
		refreshTokenCookie.setHttpOnly(true); // javascript에서 변경 못하도록 설정
		refreshTokenCookie.setSecure(false); // http가 아니어도 사용 가능 (지금은)
		refreshTokenCookie.setPath("/"); // cookie 사용 경로 (전체에 다 쓰인다)
		refreshTokenCookie.setMaxAge(1 * 24 * 60 * 60); // token 유효기간 1일
		response.addCookie(refreshTokenCookie);
	}

 

이렇게 cookie에 넣어주는 것이 보안적으로 더 적절한 선택이다.

 

 

 

 

 

 

 

 


로그인 부분이 이렇게 까다로운지 몰랐는데 생각해보면 보안 위험이 있는데 내가 너무 안일하게 생각했던 것 같다.

보안 위험도 생각해야 하고, 토큰 관련도 생각해보고...이번 기회에 더 알아갈 수 있어서 다행이다.

 

참고 자료

https://velog.io/@chuu1019/Access-Token%EA%B3%BC-Refresh-Token%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C

https://stackoverflow.com/questions/57650692/where-to-store-the-refresh-token-on-the-client

https://velog.io/@hyehyeonmoon/%EB%8F%99%EA%B8%80-JWT%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%97%90%EC%84%9C-%EB%B3%B4%EC%95%88%EC%9D%84-%EB%86%92%EC%9D%B4%EB%8A%94-%EB%B0%A9%EB%B2%95HTTPS-Cookie

728x90