0. 들어가기 전
Spring Security 웹 보안 이해 : JWT를 이해하기 전
0. JWT란? JWT(JSON Web Token)는 정보를 안전하게 전송하기 위한 간단한 토큰 기반의 오픈 표준이다. Header(헤더), Payload(페이로드), Signature(서명) 세 부분으로 구성되어 있으며........... 는 잠시 제쳐두고
jinhos-devlog.tistory.com
해당 포스트를 보고 오길 바란다!
1. JWT란?
드디어 드디어 JWT가 무엇인지 본론으로 들어왔다...
(서론이 너무 길었다..ㅠㅠ)
JWT.IO
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
jwt.io
해당 사이트에 들어가보면, JWT에 대한 설명이 나와 있다.
- JWT로 주고받는 정보는 디지털 서명이 되어있으므로 확인하고 신뢰할 수 있다.
- JWT는 HMAC or RSA or ECDSA 알고리즘을 사용한다.
- JWT는 정보를 암호화하여 주고받을 수 있지만, 서명된 토큰에 중점을 둘 것이다.
2. JWT의 구조
Encoded 된 코드를 보면서 다음과 같은 형식을 띈다.
xxxxx.yyyyy.zzzzz
해당 코드를 Decoded해보면 세 구조로 나눠볼 수 있다.
앞에서부터 Header, Payload, Signature 에 해당한다.
1) HEADER
{
"alg": "HS256",
"typ": "JWT"
}
- 어떤 알고리즘으로 암호화 & 토큰의 타입
- JSON은 Base64Url로 인코딩 되어 있다!
- 사실 중요하지 않다. "헤더"라는 게 있구나!
2) PAYLOAD
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"userID": "12" // 개인 클레임
}
- Registered Claims (등록된 클레임)
- 특정한 의미를 가진 클레임으로, 표준적으로 정의된 클레임
- 예시: iss (발급자), sub (주체), aud (대상자), exp (만료 시간), iat (발급 시간), nbf (Not Before), jti (JWT ID) 등.
- Public Claims (공개 클레임)
- 사용하는 사람들이 원하는대로 정의 가능
- 충돌을 방지하기 위해 URI 형식을 따르는 것이 좋다.
- URI 형식 예시 :"https://example.com/claim"
- Private Claims (개인 클레임)
- 클라이언트와 서버 간에 합의, 정보를 공유하기 위해 생성된 맞춤 클레임 (Key : Value)
3) SIGNATURE
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
- Base64Url로 인코딩 된 header, payload, 시크릿 키를 가지고 HMACSHA256 알고리즘으로 암호화를 한 것 (서명)
- HMAC : secret_key 를 사용하여 토큰의 서명을 생성 -> 무결성 보장
- SHA256 : 256비트 길이의 해시 함수
3. JWT 생성 / 검증 방식
1) JWT 생성
2) JWT 검증
서버 입장에서는 사실 해당 payload의 정보를 모두 알고 싶은게 아니라, "JWT 토큰이 유효한 토큰인지" 를 알고 싶은 것이다.
이전 포스팅을 보면, Load Balancing을 할 때 나왔던 세션의 단점.
서버마다 세션 저장소를 둔다거나 스티키 세션을 사용한다거나
이러한 번거러운 해결책을 사용하지 않아도,
모든 서버가 🔑 secret_key 만 알고 있으면 인증을 마칠 수 있다!!!!!!
4. JWT의 단점
한 발자국만 더 생각해보자!
JWT는 발급된 후에는 내용을 변경할 수 없으며, 유효기간이 만료될 때까지 계속해서 사용할 수 있다.
따라서 토큰을 탈취당하면 대처하기 어렵다.
토큰은 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문!
즉, JWT 또한 암호화를 했다고는 하지만,
쿠키와 마찬가지로 탈취시에 문제가 있으며 로그아웃 처리가 쉽지 않다.
이를 해결하기 위해 추가적인 보안전략을 고려해야한다.
5. 추가적인 보안전략
첫 번째로 아주 쉽게 생각할 수 있는 것은 만료기간을 매우 짧게 설정하는 것이다!
토큰이 탈취되더라도 빠르게 만료되기 때문에 피해를 최소화할 수 있다.
그러나 사용자가 자주 재로그인해야 하는 불편함이 있다.
이 불편함만 해결한다면 우리가 고려했던 보안 문제를 대부분 해결할 수 있을 것 같다!
Refresh Token 사용하기
- Access Token을 짧은 주기로 설정한다면,
반대로 Refresh Token은 주로 장기적인 인증을 위해 사용된다. - 결론부터 말하면, client의 로그인 요청 시
서버는 Access Token과 함께 그보다 긴 만료 기간을 가진 Refresh Token을 발급하는 전략이다.
- 일반적으로, JWT에는 유효기간이 설정되어 있어서 만료되면 서버에 새로운 토큰을 요청해야 한다.
- 하지만 이 과정에서 인증이 필요한 사용자는 잠시 서비스를 이용할 수 없게 될 수도 있다.
- 이를 방지하기 위해 Refresh Token을 사용합니다.
(호텔에서 방 키(Access Token)를 잃어버려도 마치 신분증(Refresh Token)을 보여주면, 다시 방 키를 줄 수 있는 원리이다.)
- 만약 Access Token이 만료되면, 클라이언트는 Refresh Token을 사용하여 새로운 엑세스 토큰을 발급받을 수 있다.
- 이를 통해 사용자는 서비스 이용 중에도 로그인 상태를 유지할 수 있다.
- 자, 다시 보안의 딜레마에 빠졌다...
- 토큰 기반 인증방식에서 토큰은 Stateless하다.
- 서버가 토큰의 상태를 보관하지 않고 있기 때문에 한 번 발급한 토큰에 대해서 제어권을 가지고 있지 않다는 뜻이다.
- Refresh Token을 탈취당한다면, 오히려 몇주~몇달 (만료 기간) 동안 Access Token을 무제한 발급받을 수 있다는 뜻...
- 각각 어디에 저장해야 될까..? (보안전략)
6. 그렇다면, Token은 어디에 저장해야해???
- BackEnd라면 6-2 만 참고
6-1. Access Token (Front-End 고려)
서버가 발급해 준 Access Token은 클라이언트(웹 브라우저, 모바일 앱 등)에 저장한다고 했다.
보통 Local Stroage, Session Storage, Cookie 중에 저장한다.
유효 기간이 짧고, 민감한 정보를 담고 있지 않기 때문에
메모리에 저장되어도 보안상 큰 문제가 발생하지 않는다.
CSRF(Cross-Site Request Forgery) 공격을 막기 위해 SameSite 속성을 설정하는 것이 좋을 것이다.
로컬 스토리지와 세션 스토리지의 차이점은 '영구적이냐 아니냐' 정도이다.
로컬 스토리지에 저장된 데이터는 사용자가 지우지 않는 한 남아있지만,
세션 스토리지의 데이터는 새로고침을 하거나 브라우저 탭을 닫을 경우에는 제거된다.
따라서, 지속적으로 필요한 데이터(예: 자동 로그인)는 로컬 스토리지에 저장하고,
잠깐 필요한 정보(예: 일회성 로그인)는 세션 스토리지에 저장하는 것이 일반적이다.
하지만 이 두 스토리지는 치명적인 단점이 있는데, 바로 XSS(Cross Site Scripting) 공격에 취약하다는 것이다.
따라서, 스토리지에 민감한 정보를 저장하는 것은 안전하지 않다..
- Cookie
쿠키 또한 자바스크립트로 접근이 가능하지만, HTTP ONLY 옵션을 설정하여 자바스크립트로 접근하는 것을 방지할 수 있다.
하지만 쿠키에 토큰을 담으면 CSRF(Cross-Site Request Forgery) 공격에 취약해진다.
XSS 공격은 토큰의 값을 가져오지만, CSRF 공격은 로그인된 상태에서 특정 동작을 요청하게 만든다.
이는 CSRF 방어를 실시하여 어느 정도 상쇄할 수 있다.
https://jinhos-devlog.tistory.com/entry/Spring-Security-%EC%9B%B9-%EB%B3%B4%EC%95%88-%EC%9D%B4%ED%95%B4-JWT%EB%A5%BC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%A0%84#5.%20CORS%EC%97%90%20%EB%8C%80%ED%95%98%EC%97%AC-1
5. CORS에 대하여 참고
즉, CSRF는 쿠키에 저장된 토큰의 값을 직접적으로 가져오지는 않기 때문에 쿠키에 토큰을 저장하는 것이 합리적입니다.
6-2. Refresh Token
Refresh Token 은 보통 매우 긴 시간을 설정하기 때문에 보안적으로 매우 중요하다. 따라서 서버에서 관리하여야 한다.
과연 어디에 저장해야 잘 저장했다고 소문이 날까..?
1) DB
가장 생각하기 좋은 방법은 DB에 저장하는 방법.
그러나, 이는 추가적인 I/O 작업이 발생함을 뜻한다.
빠른 인증 처리라는 JWT의 장점을 상쇄시키는 꼴...
2) Redis
https://jinhos-devlog.tistory.com/entry/Database-Redis%EB%9E%80
[Database] Redis란?
1. "Redis"란? REmote DIctionary Server의 약자 Redis는 빠른 오픈 소스 in-Memory "키(key)-값(value)"를 가지는 NoSQL의 데이터 베이스이다. 🔗https://db-engines.com/en/ranking/key-value+store 해당 url에서 보면 key-value Store
jinhos-devlog.tistory.com
결론만 이야기하자면, Redis에 저장하는 방식이 일반적이다.
- 메모리 기반이며, 캐싱 기능을 제공하기 때문에 "빠르다!"
- 분산 서버 환경에서 "확장성이 좋다!"
- 데이터의 만료를 손쉽게 관리할 수 있는 기능을 제공한다.
따라서, "Refresh Token의 만료 기간을 유연하게 관리할 수 있다!"
결론
가장 좋은 방식은