인증과 인가
- 인증 (Authentication): 인증은 시스템이 사용자의 신원을 확인하는 과정입니다. 인증의 가장 일반적인 형태는 아이디와 비밀번호를 통한 로그인입니다. 인증을 제대로 수행하지 않으면, 잘못된 사용자가 시스템에 접근하거나 민감한 정보를 열람할 수 있습니다.
- 인가 (Authorization): 인가는 이미 인증된 사용자가 특정 리소스에 접근하거나 특정 작업을 수행할 권한이 있는지를 확인하는 과정입니다. 예를 들어, 일반 사용자와 관리자는 서로 다른 권한을 가지며 인가 과정을 통해 해당 권한을 검증합니다.
세션과 JWT의 기본 개념
- 세션: 세션은 서버가 사용자의 정보를 일정 시간 동안 유지하기 위한 메커니즘입니다. 웹은 본래 상태가 없는(stateless) 구조이기 때문에, HTTP 프로토콜 자체는 사용자의 이전 활동을 기억하지 않습니다. 세션을 통해 서버는 사용자의 상태 정보를 일정 시간 동안 저장하고 추적할 수 있게 됩니다.
- JWT: JSON 객체를 사용해서 토큰 자체에 정보를 저장하는 Web Token 입니다. 주로 HTTP 헤더에 포함되어 서버와 클라이언트 사이에서 정보를 교환하는 데 사용됩니다.
Session
- 유저가 웹브라우저를 키고 http요청을 보내면(최초요청시) 서버는 해당 요청에 따른 controller method를 찾습니다.
- 그리고 이때 응답 header에게 쿠키(sessionId)를 담아줍니다.
- 그럼 웹브라우저가 sessionId를 받아서 쿠키저장소에 sessionId를 저장해서 넣어줍니다
- 두번째 요청부터는 요청 header에 sessionId를 달아서 요청이갑니다.
이 sessionId가 있고, 유효하면 인증이 완료된것이고, 그 이후부터는 인증을 따로 수행하지않아도 됩니다. - sessionId는 두가지 상황에서 사라집니다.
- 세션의 종료시간을 서버가 날렸을때 브라우저 종료시 쿠키저장소가 날아가면서 없어짐
- 특정시간이 지나면서 서버쪽에서 세션이 사라짐
Session의 장단점
- 클라이언트가 너무 많아서 서버가 동시접속자를 처리할수있는한계가 있다고 가정하면 서버를 여러대 늘리거나 스케일 업을 해야합니다. 이 과정에서 비교적 한계가 없고, 가용성을 제공해주는데에 적합한 스케일 아웃을 사용하는데요. 이때 로드밸런싱을 사용합니다. 로드밸런싱을 사용하는 경우 세션방식은 다음과 같은 문제를 가질 수 있습니다.
- 각 서버에 세션저장소가 따로있는경우엔 a서버에서는 인증이 되어 세션저장소에 sessionId가 있는 사용자여도 b서버로 처리가 되는 경우, sessionId가 유효하지 않다고 판단될 수 있습니다.
- 이런경우 세 가지 해결책을 사용하며 그에 대한 장단점은 다음과 같습니다.
- 첫요청은 무조건 a서버에서 처리하게 하는것(고정 세션 방식)
- 트래픽이 집중될수있습니다.
- 하나의 서버에 장애 발생하면 해당 서버 사용하는 사용자들 세션정보를 잃어버랍니다.
- 정합성 이슈를 해결할 수 있지만 스케일 아웃의 장점인 가용성과 트래픽분산이 제대로 안됩니다.
- 세션을 매번 복제
- 정합성이슈해결, 가용성~트래픽분산까지 확보하는 방식입니다.
- 하지만 많은 메모리필요, 서버수에 비례해서 네트워크 트래픽이 증가합니다(성능 이슈 발생 가능성 있음)
- 별도의 서버에 정보를 저장하고 이를 각 서버에서 접근
- 가용성측면: 서버하나에 장애가 발생해도 별도의 세션저장소가 존재해서 서비스 계속 이용가능합니다.
- 정합성문제: 로컬 세션 저장소의 불일치가 발생하지 않습니다
- 성능적문제: 세션 저장소가 하나여서 데이터 정합성 해결을 위한 별도의 세션 복제할 필요가 없어서 성능문제 해결 가능합니다
- 단, 세션 저장소도 해당 서버에 장애 발생하면 모든 세션 이용이 불가능해서 가용성을 확보하기 위해 세션 저장소를 하나 더 구성해야할 수 있습니다.
- 위와 같은 이유로 캐시 저장소를 별도의 서버로 분리하기로 결정하였습니다.
- 첫요청은 무조건 a서버에서 처리하게 하는것(고정 세션 방식)
JWT
- JWT는 Header, Payload, Signature 로 구성되어 있습니다.
- Header : Signature를 해싱하기위한 알고리즘 정보.
- Payload : 서버와 클라이언트가 주고받는 시스템에서 실제 사용될 정보.
- Signature : 토큰의 유효성 검증을 위한 문자열
JWT의 장단점
- 크기: JWT 토큰은 필요한 모든 정보를 내장하므로 크기가 클 수 있습니다. 이는 네트워크 비용을 증가시킬 수 있습니다.
- 복잡성: 유효한 서명을 유지하려면 서버와 클라이언트 양쪽에서 암호화/복호화가 필요할 수 있습니다.
- 보안: JWT 토큰이 탈취되면, 유효기간이 만료될 때까지 악용될 수 있습니다.
- 반면 JWT 방식은 확장성이 높습니다.
- JWT는 필요한 사용자 정보를 토큰 자체에 포함하므로 중앙 집중식 세션 관리 없이도 시스템의 모든 서비스에서 쉽게 유효성을 검사할 수 있습니다. 즉, 서버를 확장할 시 편하다는 이점을 가지고 있습니다.
- 세션 정보는 매 요청 시에 서버에서 검증되며, 이 때 메모리, 파일 시스템, 데이터베이스 등에 세션 정보가 저장되어 있어서 이 저장장치를 확인하는 일을 매번 거칩니다. Jwt를 사용하면 이 비용을 줄일 수 있습니다.
- 또한 JWT는 웹 환경뿐만 아니라 다양한 개발 플랫폼에서의 인증을 간편하게 관리할 수 있는 큰 장점이 있습니다. 특히 모바일 디바이스 환경에서는 쿠키가 기본적으로 제공되지 않을 수 있기 때문에, JWT는 이러한 플랫폼에서도 안정적인 인증 상태를 유지하는 데 매우 유용합니다.
- 웹과 달리 모바일 환경에서는 쿠키를 지원하지 않을 수 있으므로, 서버는 클라이언트에게 JWT를 평문 형태로 response body에 담아 전달합니다. 이렇게 하면 추가적인 라이브러리나 복잡한 설정 없이도 인증을 관리할 수 있습니다. (보안 측면에서는 HTTPS 프로토콜을 통해 안전하게 통신을 보장합니다.)
- 즉, JWT는 웹과 모바일, 다양한 개발 플랫폼에서 간편하고 안정적인 인증 방식을 제공하므로, 플랫폼의 다양화가 요구되는 현재의 개발 환경에 매우 적합한 인증 방식입니다.
데이터베이스 선정
디스크 기반 데이터베이스 vs 인메모리 데이터베이스
- 디스크 기반의 데이터베이스는 데이터의 지속성이 뛰어나지만, I/O 연산 때문에 상대적으로 느릴 수 있습니다.
- 반면, 인메모리 데이터베이스는 빠른 속도를 제공하지만, 데이터의 지속성이 보장되지 않을 수 있습니다.
세션과 같은 일시적인 데이터는 사용자의 응답 속도를 최적화하기 위해 인메모리 데이터베이스에 저장하는 것이 적합하다고 생각하였습니다. - Memcached vs Redis의 비교에 대해서는 아래 글에 정리를 해놓았습니다.
- https://flrefly.tistory.com/7
RefreshToken 도입
- RefreshToken의 필요성
- 세션 기반 인증의 단점 중 하나는 서버의 확장성입니다. 서버를 확장할 때마다 세션 정보를 동기화하는 것이 복잡한 작업이며, 이로 인해 많은 기업들이 Stateless한 JWT 인증 방식을 선호하게 되었습니다.
- "그렇다면 RefreshToken을 레디스에 저장하면, 세션과 동일한 문제가 발생하지 않을까?"라는 의문이 들 수 있습니다.
- 그러나 레디스는 높은 I/O 성능과 클러스터링 지원으로 확장성을 높일 수 있습니다. 따라서, 여러 서버 간의 세션 정보 동기화 문제가 덜 발생합니다. 하지만 결국 Stateless한 JWT의 장점을 일부 상실하게 됩니다. 그 이유는 RefreshToken을 상태로 관리해야 하기 때문입니다. 그럼에도 매번 데이터베이스를 조회하는 비용을 줄여주는 점을 감안하여 JWT 방식을 선택하였습니다.
- Access Token과 Refresh Token의 사용
- AccessToken만을 사용하여 유효 기간을 길게 설정하면, 만약 토큰이 탈취당한다면 그 유효 기간 동안 악의적인 행위를 할 수 있게 됩니다. 반면, 짧은 유효기간의 AccessToken과 함께 RefreshToken을 사용하면, AccessToken이 만료될 경우 RefreshToken으로 새로운 AccessToken을 발급받을 수 있습니다. 이렇게 하면 보안성을 높이면서도 사용자는 계속 로그인 상태를 유지할 수 있습니다.
- 일반적으로 요청에는 AccessToken만 포함시키고, AccessToken이 만료되었을 때만 RefreshToken을 사용하는 방식이 채택됩니다. 이는 다음과 같은 이유 때문입니다.
- 잠재적인 보안 문제: RefreshToken을 자주 전송하게 되면 탈취의 위험이 커집니다.
- 비효율적인 사용: 만약 AccessToken이 아직 유효하다면, RefreshToken은 필요 없게 됩니다
보안
- HTTPS 사용
- 데이터가 클라이언트와 서버 사이에서 전송될 때 암호화되므로, 중간자 공격이나 데이터 스니핑을 통한 정보 유출을 방지할 수 있습니다.
- 전송되는 데이터가 중간에 변조되지 않았음을 보장합니다. 이를 통해 공격자가 데이터를 조작하는 것을 방지할 수 있습니다.
- 서버 뿐만 아니라 클라이언트도 인증할 수 있으므로, 상대방이 누구인지 확실하게 알 수 있습니다. 이를 통해 중간자 공격을 방지할 수 있습니다.
- 세션 쿠키나 토큰 같은 인증 정보도 암호화되어 전송되므로, 세션 고정 방식을 방지 할 수 있습니다. 왜냐하면 HTTPS를 사용하면 데이터가 암호화되어 전송되므로, 세션 정보를 포함한 모든 데이터가 암호화돼서 세션 식별자를 중간에서 가로채거나 탈취하기가 어려워지기 때문입니다. 그러나 HTTPS만으로는 세션 고정 공격을 완전히 막을 수는 없습니다. 공격자가 다른 방법으로 세션 식별자를 알아내거나, 사용자가 이미 공격자에게 준 세션 식별자로 로그인하는 경우에는 여전히 세션 고정 공격이 가능합니다.
- 보안 플래그 설정
- Secure 플래그: Secure 플래그가 설정된 쿠키는 HTTPS 연결을 통해서만 전송됩니다. 이는 쿠키가 HTTP 통신에 의해 노출되는 것을 방지합니다. 이를 통해 중간자가 정보를 가로채는 공격도 막을 수 있습니다.
- HttpOnly 플래그: HttpOnly 플래그가 설정된 쿠키는 JavaScript를 통해 접근할 수 없습니다. 이렇게 하면 Cross-Site Scripting (XSS) 공격을 통해 쿠키를 탈취하는 것을 어렵게 만듭니다.
- *XSS: 웹 애플리케이션의 보안 취약점을 이용하여 공격자가 악성 스크립트를 웹 페이지에 삽입하는 공격 방법입니다. 방문하는 사용자의 브라우저에서 이 악성 스크립트가 실행되면, 사용자의 세션 토큰, 쿠키, 개인 정보 등을 탈취하거나 사용자를 다른 악성 웹사이트로 리디렉션하는 등의 행위를 할 수 있습니다.
- Access Token의 짧은 유효 기간을 고려하더라도 Refresh Token이 탈취 당했을 가능성 또한 고려해야합니다
- 이때 Refresh Token Rotation 방식과 토큰일치여부를 확인하여 보안을 강화할 수 있는데요. 이 방식들에 대한 예시를 들어보겠습니다.
- Refresh Token Rotation 방식: 새로운 Access Token을 발급받을 때마다 기존의 Refresh Token을 무효화하고 새로운 것으로 교체하는 방식입니다. 따라서 만약 탈취된 Refresh Token가 있다면 그 Refresh Token는 곧 무효화될 것입니다. 각각의 리프레시 토큰은 단 한 번만 사용할 수 있고, 새로운 액세스 토큰을 발급받을 때마다 새로운 리프레시 토큰도 같이 발급받게 되어, 보안성이 높아집니다.
- 토큰일치여부확인: 정상 유저는 자신이 가지고 있던 원래의 리프레시 토큰을 사용해서 액세스 토큰을 갱신하려고 합니다. 그런데 서버에는 이제 그 리프레시 토큰이 아니라, 해커에 의해 갱신된 새로운 리프레시 토큰이 저장되어 있습니다. 정상 유저가 제출한 리프레시 토큰과 서버에 저장된 리프레시 토큰이 일치하지 않기 때문에, 이 상황을 탐지할 수 있습니다.
- 정상 유저의 리프레시 토큰과 서버에 저장된 리프레시 토큰이 일치하지 않으면, 그것은 누군가가 리프레시 토큰을 탈취해서 사용했다는 의미입니다. 따라서 이를 통해 서버는 해커의 침투를 감지할 수 있고, 정상 유저에게 다시 로그인을 하도록 유도할 수 있습니다. 정상 유저가 다시 로그인하는 순간 기존 토큰이 무효화되면서 해커의 공격을 막을 수 있습니다.
- Refresh Token이 탈취된 다음과 같은 경우에 대해 생각해봅시다.
- 유효기간이 긴 Refresh Token이 탈취된 경우
- refresh token rotation 사용
- 탈취한 Refresh Token으로 정상 유저보다 먼저 Access Token을 재발급받는 경우(정상유저보다 먼저 refresh token을 재발급)
- 토큰 일치 여부 확인 방식 사용(윗 내용 참고)
- 한 명의 사용자에 refresh token이 여러개 생성되는 경우
- 예를 들어, 사용자 'A'가 스마트폰과 노트북으로 로그인했다면:
- 1) 처음 스마트폰으로 로그인할 때 Refresh Token1을 발급받습니다.
2) 이후 노트북으로 로그인할 때 새로운 Refresh Token2을 발급받습니다. - 이 경우 Refresh Token1은 자동으로 무효화되고, Refresh Token2만이 유효하게 됩니다.
- 이 방식을 사용하면, 만약 Refresh Token1이 탈취되었다고 하더라도, 노트북으로 로그인할 때 새로운 Refresh Token2이 발급되면서 탈취당한 Token1은 무효화되므로 문제가 해결됩니다.
- 즉, RTT(refresh token rotation)와 토큰일치여부를 동시에 고려해서 보안을 강화할 수 있습니다.
- 하지만 위의 방법들도 단점이 존재하는데요.
- 세션고정공격에 취약: 세션 고정(Session Fixation) 공격에서는 공격자가 먼저 세션 ID를 발급받거나 특정 세션 ID를 사용합니다. 그 후 이 세션 ID를 희생자에게 어떻게든 전달합니다. 희생자가 이 세션 ID로 로그인하면, 공격자는 이미 그 세션 ID를 알고 있기 때문에 희생자와 동일한 권한으로 웹 서비스를 사용할 수 있게 됩니다.
- RefreshToken을 탈취당한 상태에서 오랫동안 사용자가 로그인하지 않는다면 서버는 계속해서 해커의 RefreshToken만 재발급하고 토큰 비교 과정에서 이상을 탐지할 일도 없어집니다.
- 탈취당할 경우
- 탈취당할 경우에 대해서도 대비책이 필요할 수 있습니다. 이와 같은 문제에 대해 "블랙리스트" 방식이 쓰이는데요.
- 블랙리스트: 매 요청에 대해 블랙리스트에 해당되어있으면 접근을 차단하는 방법입니다.
- 하지만 위 방식은 JWT의 stateless한 장점을 잃게 합니다. 또한 매 요청에 대해 데이터베이스를 조회해야합니다. 이 방식 대신 보안에 관련된 정보를 수정하거나 탈퇴하는 등의 중요한 명령을 수행하는 경우 비밀번호 입력을 한번 더 받아 보안을 강화하는 등의 대안을 사용할 수 있습니다. 또한 다음과 같은 방법들도 사용해 볼 수 있습니다.
- 의심스러운 활동이 감지되면 사용자에게 알림을 보냅니다
- 중요한 작업 수행 시 두번째 인증방식을 요구하여 보안을 더욱 강화합니다
- 특정시간 동안 너무 많은 공격자의 요청은 일시적으로 차단하여 탈취된 토큰을 사용하여 공격하더라도 토큰의 피해범위를 줄일 수 있습니다
결론
- JWT를 사용하면 Stateless한 구조와 높은 확장성을 얻을 수 있지만, Refresh Token이나 블랙리스트 같은 상태를 관리해야 하는 부분에서는 주의가 필요합니다. 또한 Refresh Token Rotation과 같은 방식을 활용할 수도있지만 추가적인 보안 장치(두번째 인증 등)를 고려할 수도 있었습니다. 하지만 많은 방식에 trade-off가 존재했고, 그 과정에서 자신의 논리대로 옳은 선택을 하는 것이 어려운 일임을 알게되었습니다.
- 매요청에 대해 저장장치를 조회하게 되는 일이 어느정도의 성능 부하를 가져다 주는지도 실제 테스트를 해봐야겠다고 생각이 들었습니다.
- 그리고 보안을 강화하기 위해 추가적인 인증 절차를 더한다면, 이것이 사용자 경험에 어떤 영향을 미치는지도 실제 테스트를 통해 알아봐야겠다고 생각이 들었습니다.
- 의심스러운 활동에 대해 사용자에게 알림을 보내면 보안 강화에 좋을 것이라고 판단하였는데, 의심스러운 활동을 어떻게 정의하고, 그런 활동이 감지되면 어떻게 대응할 것인지에 대한 전략 구축이 필요해 보입니다.
- 다음 포스트에서는 이러한 내용에 대해 다뤄보려고 합니다.