시작하며
JWT 인증 방식은 세션 인증 방식과 다르게 인증 대상에 대한 정보를 DB에 저장하지 않고 토큰만으로 로그인 여부와 같은 인가 처리를 할 수 있다고 알려져 있습니다. 이러한 특징은 stateful한 세션과 다르게 stateless한 성격을 가지기 때문에 더 가벼운 방식으로 동일한 기능을 구현할 수 있습니다.
하지만 JWT 인증 방식만을 사용하는 것이 Session 인증 방식을 완벽히 대체할 수 있는가에 대한 의문이 들었습니다. 그래서 JWT 인증 방식의 인증 방식과 이에 따른 단점을 고려해 보면서 제가 가진 의문점에 대한 답을 찾아보기로 했습니다.
JWT 암호화 방법
우선 JWT 토큰은 아래 세가지 데이터 영역으로 나뉩니다.
- Header
- Payload
- Signature
Header
header에는 JWT에서 사용할 데이터 타입과 암호화 방식을 저장합니다.
1
2
3
4
{
"typ": "JWT",
"alg": "HS256"
}
타입은 JWT를 사용하니 JWT가 들어가고 알고리즘은 HS512 방식을 사용한다는 의미로 해석할 수 있습니다.
Payload
payload에는 실제 필요한 데이터를 저장합니다.
1
2
3
4
5
6
{
"sub": "12",
"exp": 1660932040,
"auth": "ROLE_MANAGER",
"name": "manager_name"
}
저장할 데이터의 종류와 방식은 이전 프로젝트에서 JWT 방식 적용 방법을 설명하는 게시글에서 확인할 수 있습니다.
Signature
Signature는 1,2번에서 설명한 Header와 Payload 값과 서버에서만 가지고 있는 개인키를 가지고 데이터 조작에 대한 검증을 할 수 있는 문자열을 의미합니다.
Signature 생성 방법은 다음과 같습니다.
1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
자세히 설명하자면 아래와 같이 생성합니다.
- header + “.” + payload + 서버 개인키
- 1번 값을 header에서 지정한 해싱 알고리즘으로 해쉬로 생성
- 생성한 해쉬를 base64 인코딩
해쉬란, 암호화만 가능하고 복호화는 불가능한 문자를 의미합니다. Signature는 데이터의 동일성만 확인하기 위한 목적이므로 해싱 알고리즘을 사용하는 것입니다.
JWT의 검증 방식
서버가 JWT을 클라이언트로부터 전달받으면 서버는 JWT에 담긴 header와 payload 값을 가지고 동일한 방식으로 새로운 signature를 생성해봅니다.
이때 클라이언트가 전달한 signature 값과 서버에서 새로 생성한 signature가 동일 여부에 따라 인가 처리를 해주는데 만약 두 값이 같으려면 서버 개인키만을 사용해야만 합니다.
하지만 클라이언트가 데이터를 조작했다면, 서버의 개인키를 클라이언트가 알 수 없기 때문에 동일한 서버 개인키로 signature를 생성할 수 없었을 것이므로 서버에서 생성한 signature와 동일할 수 없습니다.
그래서 signature가 다르다면 서버에선 클라이언트가 임의로 데이터를 조작했다고 판단해 인가 처리를 거부할 수 있습니다.
JWT 탈취 시 해결 방법
JWT의 장점은 세션과 같은 전통적인 방법에서 필요한 로그인 회원 정보를 저장할 저장소가 필요하지 않다는 것입니다.(stateless 성격) 즉, 토큰에 대한 권한을 클라이언트에게 이관시키고 서버에선 토큰 발행 및 검증에 대해서만 확인해줍니다. 이를 통해 로그인 검증을 위해 DB와의 통신을 해야 하는 리소스를 절약해 트래픽이 몰리는 경우 발생할 수 있는 과부화를 방지할 수 있습니다. 이와 더불어 토큰 내부에 원하는 데이터를 추가할 수 있고 조회할 수 있으면서 클라이언트가 직접 조작하지 못하고 read-only의 성격을 유지할 수 있다는 장점이 있습니다.
하지만 JWT 인증 방식의 치명적인 단점은 생성한 이후로는 서버쪽에서 확인을 위한 어떠한 처리를 하지 않는 목적이 있기에 탈취된 토큰에 대한 검증을 할 수 없다는 것입니다.
그러다보니 이에 대한 확실한 해결책을 내놓을 수는 없다고 볼 수 있지만 몇 가지 대비책은 마련할 수 있습니다.
토큰 유효기간을 짧게 지정한다.
말 그대로 토큰의 유효기간을 짧게 설정해 탈취된 토큰을 사용할 수 있는 시간을 줄이는 방법입니다.
다만 문제는 정상적으로 토큰을 사용해야 하는 사용자들도 짧은 토큰 유효시간으로 인해 자주 로그인을 재시도해야 할 수 있다는 단점이 있습니다.
Refresh 토큰 발급한다.
위와 같은 문제를 보안하기 위해 토큰 재발급을 위한 refresh 토큰을 추가로 발급해주는 겁니다.
일반 access 토큰의 유효기간이 만료되었다면 클라이언트는 서버에 refresh 토큰을 전달하고 서버에선 refresh 토큰이 정상적인 값이라면 새로은 access 토큰을 발급해주도록 합니다.
그리고 refresh 토큰의 유효기간을 access 토큰보다 길게 설정해준다면 사용자는 로그인을 유지한 채로 access 토큰을 재발급 할 수 있게 됩니다.
HTTPS 통신 방식을 사용한다.
일반적인 HTTP 방식으로 통신할 경우, 클라이언트와 서버가 주고 받은 요청 정보를 제 3자가 확인할 수 있으므로 SSL 인증 절차를 추가해 서버와 클라이언트만이 알고 있는 대칭키를 사용하는 HTTPS 방식을 선택하는 것 또한 토큰의 탈취 위험을 낮추는 방법 중 하나라고 볼 수 있습니다.
마치며
JWT 인증 방식은 기존의 세션 인증 방식의 단점을 보안하는 방식으로 보았지만 JWT 토큰의 동작 방식과 단점에 대해 알아보니 완벽한 대체 방식으로 사용하기에는 무리가 있다고 보입니다. 토큰의 관리 자체를 클라이언트로 넘기다 보니 서버에선 이후에 발생할 수 있는 문제를 해결해줄 수 없고 해결하기 위해 토큰 정보를 따로 저장해둔다면 세션 방식과 크게 달라지지 않기 때문입니다.
그래서 두 방식의 장단점을 고려해 상황에 맞게 선택해 사용하는 것이 가장 적절할 것이며 이를 위해선 두 방식의 동작 원리에 대한 이해도가 중요하단 걸 느끼게 되었습니다.
아래는 간단하게 두 방식이 더 적합한 상황에 대한 개인적인 의견을 적어두었습니다.
- 세션 인증 방식을 사용해야 하는 경우
- 로그인 검증에 대해 엄격하게 제어해야 하는 경우
- 중복 로그인을 허용하지 않아야 하는 경우
- JWT 인증 방식을 사용해야 하는 경우
- 상대적으로 가벼운 절차의 로그인 검증이 필요한 경우
- 확장 가능성을 고려해 유연한 구조로 설계가 필요한 경우
위와 같은 특징을 고려해 JWT는 MSA와 같은 서버와 서버 사이의 통신이 빈번한 구조에 더 적합하다고 생각합니다. 이에 반해 세션 인증 방식은 검증 대상 정보를 공유하거나 확장할 필요가 없는 단일 서버에 더 어울리다고 볼 수 있습니다.