티스토리 뷰

Web/정리글

Json Web Token 정리

구름뭉치 2021. 8. 16. 17:27

서버에서는 API 호출에 대한 클라이언트 검증을 위해 요청 클라이언트에 대해 인증 절차를 거치는데 이를 위해 다양한 방식이 존재한다.

이 중 하나의 방식이 jwt 방식인데  Jwt을 알아보기전에 세션 & 쿠키 인증 방식을 먼저 알아보자. 

 

세션 & 쿠키 인증 방식

세션

서버측에서 관리하는 사용자 정보를 말한다.

  • 서버와 클라이언트의 연결이 활성화 된 상태로 클라이언트가 서버에 접속하면 클라이언트 정보를 서버 단에 저장하게 된다.
  • 세션 ID를 발급해서 사용자를 구분한다.

쿠키

클라이언트측에서 관리하는 사용자 정보를 말한다.

  • 키와 값으로 구성된 사용자 정보 파일이다.
  • 유효시간을 명시해 줄 수 있다.
  • 클라이언트의 상태정보를 저장하고 참조할 수 있다. (HTTP의 무상태를 커버할 수 있다)

  1. 사용자가 로그인 요청을 한다.
  2. (회원가입된 사용자일 경우) 유일한 세션 ID를 생성하고, 사용자의 ID 및 사용자 정보를 세션 ID에 연결지어준다.
  3. 클라이언트가 세션 ID를 쿠키에 저장하도록 전달해준다.
  4. 클라이언트는 발급받은 세션 ID를 쿠키에 저장하고, 인증이 필요한 요청 때 마다 헤더에 쿠키를 실어 보낸다.
  5. 서버는 쿠키에 있는 세션 ID를 이용해서 사용자 정보를 가져와서 인증을 한다.
  6. 인증이 완료되면 원하는 응답을 보내준다.

서버는 세션 ID를 저장하기 위한 세션 저장소가 필요하고, 일반적으로 Redis를 많이 사용한다.

 

세션 & 쿠키 인증 방식의 단점

  • 서버가 세션을 저장하고 관리해야 된다는 단점이 있다.
  • 사용자의 수 만큼 세션을 발급해야되고 사용자 별로 세션 ID가 관리되므로 멀티 디바이스에 대한 고려가 필요하다.
  • 쿠키를 탈취당해서 해당 쿠키로 요청을 보내도 알 수가 없다. 방어를 위해 https로 헤더를 암호화하고 쿠키의 시간을 짧게하는 방법이 있다.

 

이런 단점들 때문에 요새는 Json Web Token의 토큰 기반 인증 방식이 선호되고있다. Jwt을 알아보자


Json Web Token

Jwt는 Claim(권한) 이라는 정보를 디지털 서명을 하고 비밀키로 조작 여부등을 판단하여 검증하는 방식이다.

  • 전자 서명 된 URL-safe(URL로 이용할 수 있는 문자로만 구성된)의 JSON (Base64url 인코딩을 사용한다)
  • 세션 아이디 대신에 토큰을 사용함으로서 서버측 부하를 낮추고 능률적인 접근 권한 관리가 가능하다.
  • 최초의 인증은 사용자 로그인을 통해서 이뤄지고 이때 토큰이 생성된다. 해당 토큰에는 사용자 PK, 권한 등이 들어간다.

 

JWT의 구조

jwt은 [ . ] 을 기준으로 header, payload, signature 세 부분으로 이뤄져있다.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJzdWIiOiJ1c2Vycy9Uek1Vb2NNRjRwIiwibmFtZSI6IlJvYmVydCBUb2tlbiBNYW4iLCJzY29wZSI6InNlbGYgZ3JvdXBzL2FkbWlucyIsImV4cCI6IjEzMDA4MTkzODAifQ
.1pVOLQduFWW3muii1LExVBt2TK1-MdRI4QjhKryaDwc

Header

토큰의 유형을 나타내는 정보가 들어있다.

  • 토큰의 타입과 해시 암호화 알고리즘
  • alg : 서명 시 사용하는 알고리즘 (ex. ES256)
{
	"alg": "ES256",
	"kid": "key id"
}

위와 같은 json 값을 직렬화하고 UTF-8과 Base 64 URL-safe로 인코딩해서 헤더를 생성한다.

payload

Jwt의 내용으로 토큰에 담을 Claim 정보를 포함하고 있다. 클레임은 여러개 들어갈 수 있고 토큰 생성자의 정보 (클라이언트 구분 값), 토큰 생성 시간, 만료 시간 등의 값이 들어간다.

{
    "sub": "userpk", //토큰 제목 (subject)
    "iss": "woonsik", // 발행한 사람 (issuer)
    "iat": "1586364327", // 발행 시 (issued at)
    "exp": "1999364327" // 만료 시 (expiration)
}

 

사용자가 정의한 클레임도 넣어줄 수 있다. name-value의 형태로 넣어야 한다.

Claims claims;
claims.put(ROLES, roles); // 권한 넣기

 

Signature

header와 payload를 합친 문자열을 서명한 값이다. 서명은 헤더에서 명시한 algorithm(SignatureAlgorithm.HS256)과 Base 64 URL-safef로 인코딩한 secretKey값을 이용해서 생성한다. 

secretKey = Base64UrlCodec.BASE64URL.encode(secretKey.getBytes(StandardCharsets.UTF_8));

 

최종적으로 header . payload . signautre 가 합쳐진 토큰이 생성된다.

 

이때 위의 header와 payload는 인코딩만 할 뿐 암호화 되지 않는다. 따라서 JWT의 헤더와 페이로드는 누구든 해시 알고리즘을 통해 디코딩하면 값을 볼수가 있다. 따라서 토큰에는 사용자를 구분할 수 있는 값만 넣어야지 비밀번호나 사용자를 특정할 수 있는 개인정보를 담아서는 안된다.

 

하지만!

마지막에 서명한 Verfiy Signature는 secretKey를 알지 못하면 복호화할 수가 없다. signature로 토큰의 조작여부를 검증하게 되는데 시크릿키를 알지못하면 아무리 payload를 조작해서 요청을 보내도 서명의 값이 다르므로 거부하게 되는것이다.

 

예를들어 A유저가 Z유저의 정보를 해킹하고 싶어서 자신의 토큰의 payload의 유저 pk를 Z유저의 pk로 바꿔서 요청한다고 하자.

서버는 토큰의 payload에 Z유저의 값이 들어있음에도 signautre는 정작 A유저의 header와 payload를 기반으로 시크릿키와 함께 조합되어 생성된 서명이므로 잘못된 서명임을 인지하고 거부하게 된다.

 

즉, 비밀키를 알아내지 못하는 이상 토큰을 조작하고 재서명을 할 수 없으므로 (올바른 서명값을 알아낼수 없으므로) 타 유저의 정보에 접근할 수가 없다.

 

토큰 기반 인증의 장점

  • 세션/쿠키 방식과 다르게 토큰을 따로 저장하거나 관리할 필요가 없다. 로그인 시 토큰을 발급해주고 요청이 올때 같이 토큰을 받아서 해당 토큰을 검증하고, 토큰에 들어있는 유저pk와 권한을 통해 학인해주면 된다.
  • 이러한 방식은 무상태 서버를 만들때 큰 장점이다. 상태를 저장하지 않아도 되므로 서버를 확장 / 유지 / 보수 할 때 유리하다.
  • OAuth2 의 토큰 기반 인증 방식을 사용할 수 있다. 따로 회원가입/로그인을 위한 로직을 구현하지 않고 OAuth2를 이용해서 토큰 기반으로 인증하는 구글 로그인, 카카오톡 로그인 등을 사용할 수 있다.

토큰 기반 인증의 단점

  • 한번 발급된 토큰은 값을 변경할 수가 없다. DB에서 유저 정보를 수정해도 해당 토큰의 값은 변경할 수가 없다. 관리자 앞으로 발행된 토큰이 있는데 관리자가 일반 회원으로 강등당해도 해당 토큰은 만료될 때까지 관리자 권한을 갖고 있다.
  • payload는 공개값이므로 정보가 제한적이다. 개인정보가 포함되지 않거나 암호화해서 넣어야한다.
  • JWT의 길이가 매우 기므로 인증요청이 매우 많아지면 데이터 트래픽의 부하가 올 수 있다.
  • 토큰이 탈취당하면 만료되기 전까지 마음놓고 해킹할수가 있다.

Access Token + Refresh Token 방식

그래도 토큰 자체를 탈취당하면 문제가 발생한다. 공격자가 사용자의 토큰을 탈취하면 해당 토큰이 만료되기 전까지는 해당 토큰으로 마음껏 액세스할 수 있게된다. 그렇다고 토큰의 만료시간을 짧게하면 만료될 때마다 사용자를 재로그인을 해야된다. 그렇다면 어떻게 사용자의 편의성도 올리면서 보안성도 올릴 수 있을까?

 

위 문제의 해법이 Access Token + Refresh Token 방식이다.

  • 액세스 토큰의 만료시간은 30분 ~ 1시간 정도로 짧게 준다.
  • 리프레시 토큰은 만료시간을 2주 정도로 넉넉히 잡아준다.
  • 액세스 토큰이 만료되었는데 사용자의 요청이 오면 해당 사용자의 리프레시 토큰을 본다.
  • 리프레시 토큰이 만료되지 않았다면 로그아웃 시키지 않고 액세스 토큰과 리프레시 토큰을 재발급해주고 로그인 상태를 유지한다.
  • 리프레시 토큰이 만료된 경우에만 로그아웃 시킨다.

 

스프링 부트 REST API에 AccessToken + RefreshToken 적용

2021.08.21 - [Web/스프링부트 RestAPI 프로젝트] - spring boot REST API Web 프로젝트 (10) - Jwt AccessToken + RefreshToken으로 보안성과 사용자 편의성 고도화하기

반응형

'Web > 정리글' 카테고리의 다른 글

JPA - 영속성 컨텍스트 정리  (0) 2021.09.06
HTTP vs. WebSocket 정리  (2) 2021.08.26
JPA 정리  (0) 2021.08.04
Servlet 정리  (0) 2021.07.28
Spring MVC Architecture 정리  (0) 2021.07.27
Comments
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday