Session, JWT
회원이 존재하는 서비스를 만들기 위해서는 사용자의 인증 정보를 관리하는 무언가가 필요하다. 흔히 세션을 사용하거나 인증 토큰을 사용해서 관리하게 되는데 오늘은 이 세션과 토큰을 알아보도록 하자.
Http 같은 Stateless한 프로토콜에서는 요청간에 연속성이 없어서 서버와 클라이언트가 이전에 주고받았던 요청과 응답에 대한 내용을 계속 기억하고 있을 수가 없다. 이런 상황에서 예를 들어 로그인을 통한 인증 기록이 있어야 서비스의 기능을 사용할 수 있다면 골치가 아파질 것이다. 서비스를 사용하려 클릭을 할 때마다 로그인을 동반해야할 수도 있기 때문이다. 이런 문제점을 해결하기 위해서 매 요청마다 이 클라이언트가 인증을 받은 클라이언트인지 알 수 있는 방법으로 인증 정보를 저장하고 있는 Cookie, Session, Token 이 있다.
Session
사용자가 웹 서버에 접속해 있는 상태를 하나의 단위로 보고 이것을 Session이라고 한다. Session에는 사용자가 요청과 응답을 보내고 받을 때 사용자와 서버 사이의 연결을 확인하기 위한 정보가 들어있다.
1.1 Session flow
클라이언트가 웹서버에 처음 접속하면 해당 클라이언트에 대한 유일한 Session ID를 발급하고 클라이언트의 세션 정보는 서버 내부나 데이터베이스와 같은 곳에 저장한다. 클라이언트가 이어서 해당 Session ID를 가지고 요청을 했을 때 서버는 내부적으로 Session ID를 key로 가지는 세션 정보를 기반으로 응답하게 된다.
- 클라이언트가 로그인 한다.
- 서버에서는 클라이언트의 정보를 Session에 담아 서버 메모리에 저장하고 Session의 key에 해당하는 Session ID는 쿠키에 담아 사용자에게 반환한다.
- 클라이언트는 서버에게 보내는 요청의 헤더에 Session ID를 담아 보낸다.
- 서버는 Session ID에 기반하여 인가 처리 후 응답을 해준다.
1.2 세션 관리를 위한 서버의 역할
Session의 가장 큰 특징은 바로 서버가 세션 데이터를 관리한다는 것이다.
- 세션 생성 : 요청이 들어왔을 때 세션이 없다면 만들어서 응답에 set-cookie로 넘겨준다.
- 세션 확인 : 요청이 들어왔을 때 세션이 있다면 해당 세션의 데이터를 가져온다.
- 세션 삭제 : 타임아웃이나 명시적인 로그아웃 API를 통해서 저장해둔 세션을 무효화한다.
1.3 Session 장점
- 쿠키에 클라이언트 정보를 그냥 담는다면 쿠키가 탈취되었을 때 사용자의 정보가 위변조 될 수 있지만 세션 ID는 아무런 의미 없는 문자열일 뿐, 진짜 정보는 서버에 저장되어 있으므로 쿠키가 탈취되더라도 사용자의 중요한 정보가 누출될 위험이 없다.
- 무효화해야하는 세션에 대해서 서버에서 세션을 삭제함으로서 깔끔하게 처리할 수 있다.
1.4 Session 단점
- 해커가 중간에 가로챈 쿠키로 사용자인척 Http 요청을 보낼 수 있다.
- Session Hijacking Attack
- CSRF Attack
- 기본적으로 Session을 저장할 때 서버의 자원을 사용하므로 사용자가 많아지면 서버에 부하가 많이 발생한다.
- 서버 scale-out에 대한 확장성이 좋지 않다.
- 기본적으로 접속하는 서버에 Session을 저장하게 되는데 만일 서버를 여러 대 두게 된다면 같은 클라이언트라도 서버마다 별도의 세션과 세션 Key가 발급되어 이에 대한 해결책이 필요하다.
- 세션은 결국 쿠키와 병행하여 사용해아하는데 쿠키 정책상 같은 도메인에서만 쿠키를 받을 수 있어 여러 도메인에서 쿠키를 관리하는 것이 어렵다.
- Cookie의 same-site 정책
1.4.1 Session 단점 해결하기
1. 해커가 중간에 가로채서 훔친 쿠키로 사용자인척 Http 요청을 보낼 수 있다.
- https를 사용하여 요청을 탈취해도 내부 정보를 읽을 수 없도록 한다.
- IP를 특정하여 관리하거나 Session에 만료시간을 두어 새로 발급하여 막을 수 있다.
2. 로그인한 모든 사용자가 서버에 접근하여 Session 서버의 자원을 사용하기 때문에 사용자가 많아지만 트래픽이 많아져 서버의 성능을 저하시키게 된다.
- 읽기 속도가 빠른 메모리 서버에 세션을 저장한다.
- 세션은 사용자가 로그인했을 때만 저장하고 있으면 되기 때문에 영속성이 필요없다. 따라서 읽기 속도가 빠른 Redis같은 데이터베이스를 활용할 수 있다.
3. 확장성이 좋지 않다.
- Session Clustering
- Sticky Session
- Session을 Database에 저장 ( ex. Redis, .. )
4. 세션은 결국 쿠키와 병행하여 사용해아하는데 쿠키 정책상 같은 도메인에서만 쿠키를 받을 수 있어 여러 도메인에서 쿠키를 관리하는 것이 어렵다.
이것은.....이것은..... 딱히 해결 방법이......... 흙흑
1.5 Spring에서 Session 으로 인증 정보를 관리하기
1.6 근데 아직 의문인 점
즉, 모든 요청에 대해서 cookie를 파싱해서 확인해야하고, 세션 생성 시에는 set-cookie값까지 설정해야 함.
그냥 응답 헤더에 박을 수 있는 거 아님?
→ 만일 이렇게 한다면 탈취되면?
그리고 https 해도 와이어샤크하면 다 보이던데 https 안전한거 맞음?
JWT
Json Web Token, JWT는 토큰에 사용자와 관련된 정보를 담고 있으며 클라이언트는 이 토큰을 Authorization 헤더에 담아서 서버에 요청을 보낸다. JWT는 디지털 서명이 존재하여 토큰의 내용이 위변조 되었는지 서버측에서 확인할 수 있다. 따라서 이 헤더를 수신한 서버는 토큰이 위변조 되었거나, 만료 시각이 지나지 않았는지 확인한 후 토큰에 담겨있는 사용자 인증 정보를 확인하여 사용자를 인가한다.
Session과 달리 인증에 대한 정보를 클라이언트가 '저장'하고 있게 되는 것! 다만 토큰을 생성하는 것은 당연하게도 서버라는 것을 헷갈리지 말자.
2.1 JWT flow
- 클라이언트가 로그인한다.
- 로그인에 성공하면 서버가 secret key를 이용하여 JWT를 발행하여 헤더나 쿠키에 담아 응답으로 보낸다.
- 클라이언트는 토큰을 잘 저장했다가 서버에 요청시 마다 토큰을 포함하여 보낸다.
- 서버는 토큰을 복호화하여 토큰이 위변조 되지는 않았는지, 만료시간이 지나지는 않았는지 등을 확인함으로서 클라이언트에 대한 인증을 실시한다.
- 토큰 검증 결과에 기반하여 응답을 보낸다.
2.2 JWT 구성요소
- Header
- alg : 해싱 알고리즘
- typ : 토큰의 타입
- Payload
- iss : 토큰 발급자, issuer
- sub : 토큰 제목, subject
- aud : 토큰 대상자, audience
- exp : 토큰의 만료시간, expiration. 시간은 NumericDate로 이루어져야 하고, 현재 시간보다 이후로 지정되어야 한다.
- nbf : 토큰 활성 날짜, 시간은 NumericDate로 이루어져야 한다. 이 날짜가 지나기 전까지는 토큰이 활성화 되지 않는다.
- iat : 토큰이 발급된 시간
- jti : JWT 식별자, 중복적인 처리를 방지하기 위해 사용된다.
- private claim : 위의 약속된 클레임(payload에 담긴 key-value 쌍) 외에 임의로 정한 클레임을 둘 수 있다.
- Signature
- Header의 인코딩 값과 Payload 인코딩 값을 합친 후, secret key로 헤더에 지정한 알고리즘을 통해 해싱한 값 을! base64로 인코딩한 값.
2.3 JWT 장점
- 확장성이 높다.
- 토큰을 서버에 저장하고 있는 것이 아니기 때문에 서버가 늘어나도 상관없다.
- 서버가 stateless 해진다.
- 클라이언트에 토큰이 저장되어 있기 때문에 사용자가 늘어나도 서버의 메모리나 데이터베이스에 부담이 되지 않는다.
2.4 JWT 단점
- 토큰은 클라이언트가 저장하고 있기 때문에 만일 토큰이 털리면 해당 토큰이 만료되기 전까지는 속수무책으로 피해를 입을 수 밖에 없다.
- 서버에 토큰을 저장하지 않아 서버에서 토큰을 삭제하거나 무효화할 수 없기 때문이다.
- 토큰에 실린 Payload는 별도로 암호화 되어있지 않으므로, 누구나 내용을 확인할 수 있다. 중간에 payload를 탈취하면 디코딩을 통해 데이터를 볼 수 있다.
- 토큰에 정보가 많이 담기면 토큰의 크기가 증가하여 네트워크에 부하를 줄 수 있다.
- 토큰을 담은 헤더의 크기가 커지므로 요청의 사이즈가 커진다.
2.4.1 JWT 단점 해결하기
- 토큰은 클라이언트가 저장하고 있기 때문에 만일 토큰이 털리면 해당 토큰이 만료되기 전까지는 속수무책으로 피해를 입을 수 밖에 없다.
- Access Token의 만료 시간을 짧게 잡고 Refresh Token을 사용한다.
- RTR 기법을 활용한다.
- IP 를 활용하여 Access Token을 관리한다.
- Redis를 활용하여 Token에 대한 무효화 기록을 저장하여 관리한다.
- 토큰에 실린 Payload는 별도로 암호화 되어있지 않으므로, 누구나 내용을 확인할 수 있다. 중간에 payload를 탈취하면 디코딩을 통해 데이터를 볼 수 있다.
- JWE를 통해 암호화 한다.
- payload에 중요한 데이터를 넣지 않는다.ㅎ
- 토큰에 정보가 많이 담기면 토큰의 크기가 증가하여 네트워크에 부하를 줄 수 있다.
- 필요한 정보만 담는다.ㅎ
정리
Session
- 세션 데이터를 서버에서 관리, 저장
JWT
- 토큰을 클라이언트에 저장
정리하면서 더 느끼지만 세션이라고 해서, JWT라고 해서 완벽한 인증 방식은 없다. 세션은 세션 ID를 탈취해도 세션 정보를 알아낼 수는 없지만 사용자가 늘어날수록 서버에 부하가 많이 가고 확장성을 고려했을 때 고려해줘야할 것들이 복잡하다. JWT는 클라이언트가 저장하므로 서버에 부하도 적고, 확장성을 고려했을 때도 편리하지만 페이로드가 늘어날수록 요청마다 네트워크 비용이 높아지고, 결정적으로 탈취당하면 payload 누출에 토큰을 제어하지도 못한다. 매 서비스에 맞는 인증 방식을 합리적으로 선택할 수 있는 기준을 잘 세워야겠다.