자동로그인 안전하게 구현하기
안전한 자동 로그인을 구현하는 방법
사용자가 구분을 해야 하는 모바일 앱 서비스는 대부분 로그인한 상태를 유지합니다. 덕분에 사용자는 로그인을 불편하게 다시 하지 않고 서비스를 이용할 수 있습니다.
로그인을 유지하기 위해선 대표적으로 쿠키, 세션, 토큰을 사용합니다. 쿠키는 사용자가 관리하는 로그인 상태정보이고, 세션은 서버가 관리하는 상태정보 입니다. 그리고 토큰은 사용자가 관리하는 상태정보이지만, 서버와 통신할 때 서버가 토큰의 서명 값을 검증하여 변조 여부를 확인합니다. 즉, 토큰을 사용하면 쿠키의 변조 여부를 서명 값을 검증하여 확인할 수 있고 서버에서 상태 정보를 유지하지 않고 로그인 정보를 관리할 수 있습니다.
로그인을 장기간 유지하기 위해 토큰에 유효기간을 설정하지 않을 수 있지만 토큰이 탈취당하면 로그인을 우회하고 권한이 없는 기능과 정보에 접근 할 수 있는 위험이 발생하게 됩니다. 예를 들어 공용 PC 환경에서 로그인을 한 뒤 로그인 정보를 무효화 하지 않을 경우엔 악의적인 사용자는 공용 PC안 인터넷 접속 기록을 확인하여 세션값을 탈취하여 인증을 우회할 수 있습니다.
그렇다면 어떻게 로그인 정보를 안전 하게 유지할 수 있을까요? 이때 사용할 수 있는 방법 중 하나가 RTR(Refresh Token Rotate) 전략입니다.
RTR(Refresh Token Rotate) 방식
RTR(Refresh Token Rotate)는 토큰 방식을 응용한 전략으로 로그인 성공 시 서버에서 두 개의 토큰을 발급합니다. 이 두 개의 토큰을 각각 Access Token, Refresh Token이라고 합니다.
Access Token은 권한이 필요한 요청을 보낼 때 사용합니다. 대표적으로 로그인 하고나서 회원 개인의 권한으로 실행해야 할 기능들이죠. 예를 들면 여러 사람이 함께 쓰는 게시판에서 자신의 글을 올리거나 수정, 삭제할 때 Access Token을 검증하여 해당 회원이 정당한 권한이 있는지 검증합니다. 해당 토큰은 회원의 요청이 정당한지 검증하기 위해 사용하는 중요한 정보이기 때문에 짧은 유효기간을 갖게 됩니다.
Refresh Token은 유효기간이 만료된 Access Token을 재발급하는데 사용합니다. RTR 전략은 Access Token를 재발급 하면서 Refresh Token의 유효기간도 함께 재갱신합니다. RTR의 핵심은 자주 서비스를 사용하는 사용자들의 로그인은 유지하고 앱을 사용하지 않은 회원의 로그인 정보를 만료 시켜 서비스의 보안을 사용자 경험을 해치지 않으면서 강화시키는 것입니다.
토큰 명 | 용도 | 유효 기간 |
---|---|---|
Access Token | 권한 검증이 필요한 요청이 보낼 때 사용 ex) 마이페이지, 내 글 작성 |
5분 ~ 30분 |
Refresh Token | 짧은 유효기간을 가진 Access Token을 재갱신하는데 필요 | 14일 ~ 30일 |
RTR(Refresh Token Rotate) 사용 시 주의해야 할 점
Refresh Token는 긴 유효기간을 가지고 있고, 재갱신을 할 때 마다 새로운 Access Token과 Refresh Token이 갱신됩니다. 만약 Refresh Token을 탈취 할 수 있으면 로그인 없이 다른 사용자의 권한을 재갱신 기능을 악용하여 Access Token을 계속 생성할 수 있게 됩니다. 이를 방지하려면 어떻게 해야 할끼요?
1. 전송구간 암호화
웹 서비스는 HTTP 프로토콜을 바탕으로 제공됩니다. HTTP 자체는 암호화 통신이 제공되지 않기 때문에 지나가는 통신 구간 중 패킷 스니핑을 한다면 반드시 토큰 정보가 공격자 손에 들어오게 됩니다. 토큰 정보를 암호화 하는건 어떨까요? 하지만, 토큰 자체가 사용자임을 증명하는 수단이기 때문에 암호화 되어 알아보지 못하더라도 복호화 없이 사용할 수 있습니다.
통신구간을 암호화 하기 위해선 HTTPS를 사용할 수 있습니다. HTTPS는 통신구간을 암호화 하기 때문에 스니핑을 방지할 수 있습니다. 패킷 모니터링 도구인 Wireshark에서 HTTP통신을 모니터링하면 쿠키안에 있는 토큰 정보를 획득하는 것을 확인할 수 있습니다. 반면, HTTPS 환경에선 TLS 암호화 되어 있어서 패킷을 획득할 수 없게 됩니다.
2. 스크립팅으로 가져오지 못하게 막는다.
HTTPS가 제공되는 환경이더라도 웹 어플리케이션의 취약점을 활용하여 Refresh Token을 탈취할 수 있습니다. XSS(Cross-Site Scripting)은 입력 값에 HTML과 Javascript를 삽입하여 공격자가 의도한 기능을 실행하는 공격입니다.
이 공격을 이용해 Refresh token을 탈취하면 HTTPS가 적용되어 있더라도 통신 매커니즘상 정당한 절차로 보내는 공격이기 때문에 암호화 통신과 상관없게 됩니다.
특히 토큰을 전송할 때 Authorization: Bearer
헤더를 사용하는 환경에서는 Refresh Token을 브라우저 LocalStorage
에 보통 저장합니다. 이는 javascript를 활용하면 쉽게 가져올 수 있기 때문에 XSS 공격에 취약합니다.
따라서, 쿠키에 토큰 정보를 저장하고 javascript에서 가져오지 못하게 막는 httpOnly flag
를 붙일 수 있습니다. XSS 취약점이 발견되더라도 토큰을 직접 가져올 수 있는 방법은 막히게 됩니다.
쿠키를 사용할 때 보안을 강화할 수 있는 또다른 방법으로 Secure flag
를 설정한다면 브라우저가 HTTPS 환경에서만 쿠키를 전달하기 때문에 좀 더 효과적으로 방어할 수 있습니다.
Refresh Token 재사용 방지하기
위의 두 가지 보안 정책은 두 위협을 효과적으로 막을 순 있겠지만 그럼에도 불구하고 탈취되면 어쩌죠? 소 잃고 외양간을 고치는 방법 말고 탈취한 Refresh Token의 가치를 없앨 수는 없을까요?
Refresh Token이 재사용 되었음을 방지하기 위해서는 서버에서 Refresh Token 정보를 관리해야 합니다. 아래의 그림처럼 로그인 요청이 들어올 때 데이터베이스를 이용해서 발급한 Refresh Token을 저장합니다.
사용자에게 재발급 요청이 들어오면, 이전에 재발급에 사용한 Refresh Token인지 검증하고 새로운 Refresh Token을 전달할 때 새롭게 발급한 Refresh Token으로 갱신하면 됩니다.
이렇게 갱신하게 된다면, 악의적인 사용자가 탈취한 Refresh Token으로 동일하게 재갱신을 하려고 할 때 데이터베이스가 이미 새로운 토큰으로 업데이트 되어 있어서 훔친 토큰이 무용지물로 만들 수 있게됩니다.
그런데, 재갱신 요청한 Refresh Token이 탈취 된 것인지 정상 요청인지 어떻게 서버쪽에서 알 수 있을까요? 정상 사용자가 먼저 재갱신하는 경우와 악의적인 사용자가 먼저 재갱신하는 경우 후자의 훔친 토큰을 비정상 요청임을 어떻게 검증할 수 있을까요?
결론부터 말하자면 불가능합니다. 악의적인 사용자가 훔친 토큰은 사용자가 발급한 토큰과 값이 동일하기 때문에 서버 측에선 구분할 수 없기 때문입니다.
그렇다면 훔친 토큰을 어떻게 무효화 시킬 수 있을까요? 이럴땐 정상 사용자와 악의적인 사용자의 차이를 고민해봐야 합니다.
악의적인 사용자는 왜 토큰을 탈취해야 할 수 밖에 없을까요? 그 이유는 정상 사용자의 계정으로 로그인하기 힘들기 때문입니다. 그래서 보안 시스템이 덜 안전한 토큰을 공격 타겟으로 잡은 것이죠.
즉, 토큰이 탈취 되었을 경우 정상 사용자에게 정당한 로그인을 요구한다면 계정 정보를 모르는 악의적인 사용자는 토큰을 다시 획득할 수 없게 되는 것이죠. 데이터베이스에서 재갱신한 토큰 정보를 완전히 제거하고 새롭게 로그인 할 때 재갱신을 하게 된다면 사용자에게는 다시 로그인하는 부담감이 들겠지만 탈취한 토큰을 효과적으로 무효화 할 수 있게됩니다.
결론
Refresh Token Rotate 전략은 보안 정책을 잘 세운다면 로그인을 오랫동안 유지하면서 이용자의 토큰을 효과적으로 보호할 수 있는 기법입니다. 탈취할 수 있는 경로를 막는 것과 함께 보호해야 할 정보의 가치를 무효화 하여 보안성을 강화 할 수도 있었습니다. 서버 개발자는 사용자가 앱 서비스를 사용하는데 불편함이 없으면서도 내부 로직에서도 신뢰할 수 있는 서비스를 만들 수 있도록 고민하는 것이 좋은 개발자가 되는 길이라고 생각합니다.
다음 글에서 RTR 전략을 구현하면서 멀티로그인으로 확장 가능하게 설계하고 해당 방식의 한계점을 고민해봅시다.