본문 바로가기

[Web] 안전하게 로그인 처리하기

들어가며

항상 프로젝트를 진행하면서, 회원가입 기능을 만들 때, JWT를 무지성으로 활용했습니다. 왜 JWT를 사용해야 하는지, JWT를 사용하면 어떤 문제가 있는지, 왜 세션은 고려하지 않았던 것인지 단 한 번도 제대로 생각해본 적 없다는 생각이 들었습니다. 기본적인 부분도 모르는 개발자라는 생각이 들었습니다. 이번 기회에 JWT는 어디에 저장해야 하며, JWT를 잘 활용하는 방법은 무엇인지에 대해 정리해보려 합니다. 

 

 

 

 

 

 


 

 

 

 

 

응 뚫리면 그만이야~

 

 

 

세션과 토큰 탈취

웹 서비스에서 인증 인가를 구현하기 위해 쿠키/세션 또는 JWT를 활용합니다. 세션과 JWT는 다양한 공격 방식으로 인해 노출이 될 수 있는데, 이에 대해 크게 두 가지 공격 방식을 알아보겠습니다.

 

 

XSS(Cross Site Scripting)

여기서 XSS(Cross Site Scripting)이란 다양한 공격 방식이 있지만, 간단하게 생각해서 공격자가 의도하는 악의적인 js 코드를 피해자 웹 브라우저에서 실행시키는 것입니다. 이 방법으로 피해자 브라우저에 저장된 중요 정보들을 탈취할 수 있습니다. 

 

 

CSRF(Cross Site Request Forgery)

CSRF란 정상적인 request를 가로채 피해자인 척하고 백엔드 서버에 변조된 request를 보내 악의적인 동작을 수행하는 공격을 의미합니다. 예를 들어 특정 공격자가 특정 링크를 클릭하도록 유도하거나 img 태그를 보도록 유도합니다. 그럼 특정 유저가 로그인된 상태로 링크를 클릭하거나 img 태그를 보면 사용자의 의도와 관계없이 http request를 보냅니다. 그럼 이 request는 정상적으로 서버에 동작을 수행합니다. 내가 쓰지 않은 광고성 글이 페이스북에 올라가는 것과 같습니다. 

 

위의 공격 방식으로 세션과 토큰을 탈취할 수 있는데, 그렇다면, 안전하게 로그인을 처리하기 위해서는 어떻게 해야 할까요? 이에 대해 알아보겠습니다.

 

 

 

 

 

 


브라우저 저장소 종류와 보안 이슈

웹에서는 무언가를 저장하기 이해 크게 쿠키와 로컬 스토리지 등을 활용합니다. 각 장소의 보안 이슈에 대해 알아보겠습니다.

 

 

쿠키

쿠키는 HTTP 통신의 무상태성을 보완해주기 위해 나온 것으로 서버가 클라이언트에 값을 저장하고, 읽을 수 있도록 해줍니다. 만약 토큰을 쿠키에 저장한다면 XSS 공격으로부터 로컬 스토리지에 비해 안전합니다. 쿠키의 httpOnly 옵션을 사용하면 JS에서 쿠키에 접근 자체가 불가능합니다. 그래서 XSS 공격으로부터 쿠키 정보를 탈취할 수 없습니다. 

 

하지만 XSS 공격으로부터 완전히 안전한 것은 아닙니다. httpOnly 옵션으로 쿠키의 내용을 볼 수 없다 해도 JS로 Request를 보낼 수 있으므로 자동으로 Request에 실리는 쿠키의 특성상 사용자의 컴퓨터에서 요청을 위조할 수 있기 때문입니다. 그리고 httpOnly 방식도 충분히 뚫릴 수 있기 때문에 완전히 안전하진 않다는 특성이 있습니다. 

 

또한 CSRF 공격에 취약합니다. 쿠키는 자동으로 http request에 담기기 때문에 공격자가 request url만 안다면 사용자가 관련 link를 클릭하도록 유도하여 request를 위조하기 쉽습니다. 

 

 

 

로컬 스토리지

로컬 스토리지는 CSRF 공격에 비교적 안전합니다. 자동으로 request에 담기는 쿠키와는 다르게 js 코드에 의해 헤더에 담기므로 XSS 공격으로 뚫지 않는 이상 공격자가 정상적인 사용자인 척 request를 보내기가 어렵습니다.

 

하지만 XSS에 취약하다는 단점이 있습니다. 공격자가 localStorage에 접근하는 js 코드 한 줄만 주입하면 localStorage를 공격자가 내 집처럼 드나들 수 있습니다. 

 

그렇다면 어디에 어떻게 저장하는 것이 좋을 수 있는지에 대해 알아보겠습니다.

 

 

 

 

 

 

 


 

Refresh Token 활용하기

모든 방식은 완벽할 순 없지만, 그럼에도 조금이라도 보안에 신경 쓰기 위해서 refresh token을 사용해야 합니다. refresh token을 활용하는 방법은 다음과 같습니다.

 

 

로그인 구성하기

refresh token을 httpOnly 쿠키로 설정해서 클라이언트에 보내고, accessToken 값은 JSON payload로 받아와서 웹 애플리케이션 내 로컬 변수로 이용합니다.

 

 

출처 : 프론트에서 안전하게 로그인 처리하기 (ft. React)

 

 

이런 방식을 사용하는 경우, refresh token이 CSRF에 의해 사용된다 하더라도 공격자는 accessToken을 알 수 없습니다. CSRF는 정상 사용자가 img 태그를 보거나 link를 클릭하도록 유도하여 사용자 대신 request를 보내는 방법입니다. refresh token은 쿠키로 저장되어 있기 때문에 CSRF를 통해 access token을 사용자 대신 요청할 순 있지만 response는 사용자가 받기 때문에 공격자는 이 access token을 얻지 못합니다. 그러므로 access token을 알아야 공격자가 요청을 위조해서 보낼 수 있는데, access token을 얻을 수 없으니 요청을 위조하지 못합니다. 이를 통해 CSRF 취약점 공격을 방어하고, XSS 취약점 공격으로 저장된 유저 정보 읽기는 막을 수 있습니다. 하지만 XSS 취약점을 통해 API 콜을 보낼 때는 무방비하기에, XSS 공격 자체를 막기 위해 노력해야 합니다.

 

그럼 만약 로그인이 만료되거나 로그인을 연장 처리하려면 어떻게 해야 할까요? 이에 대해 알아보겠습니다.

 

 

 

 

 

로그인 만료, 로그인 연장 처리하기

서버에서는 응답 값으로 accessToken을 넘기면 클라이언트에서는 애플리케이션 내 로컬 변수에 값을 저장합니다. 그러다 브라우저가 꺼지거나 페이지가 리프레시되는 등의 리로드를 하면 accessToken은 사라집니다. 또한 accessToken이 만료가 되면 accessToken을 사용하지 못합니다. 그럼 다시 accessToken을 받아오기 위해서 어떻게 해야 할까요?

 

은행 사이트 같이 보안이 중요하다면 다시 로그인하도록 로그인 페이지로 이동시킬 수 있고, 유저 모르게 서버에서 새로운 accessToken을 받아와서 로그인이 연장되도록 할 수 있습니다. 그렇다면 유저가 다시 로그인하지 않도록 조용히 자동으로 로그인을 연장하는 기능(silent refresh)을 알아보겠습니다.

 

 

 

출처 : 프론트에서 안전하게 로그인 처리하기 (ft. React)

 

 

 

만약 accessToken이 만료됐거나 페이지 리로드로 accessToken이 로컬 변수에서 사라졌다면 쿠키에 담긴 refreshToken을 서버로 전달합니다. 그럼 서버에서는 refreshToken을 확인하고, 문제가 없다면 refreshToken을 재생성해서 다시 쿠키에 refreshToken을 담습니다. 그 후 accessToken을 응답 값으로 넘겨주면, 클라이언트에서는 다시 accessToken을 로컬 변수로, refreshToken을 쿠키로 활용합니다. 그럼 전달받은 accessToken을 갖고 다시 로그인하면 사용자는 다시 아이디, 비밀번호를 입력해서 들어가지 않아도 서비스를 이용할 수 있습니다. 

 

 

 

 

 

 


부하 문제

위의 내용을 종합해보면, accessToken은 애플리케이션 내부에 저장이 되고, 클라이언트에서 accessToken의 만료 시간을 계속 확인해서, 만약 만료가 되거나, 만료가 되기 직전에 RefreshToken을 활용해서 accessToken을 재발급받습니다. 또한 RefreshToken이 만료될 경우에도 다시 재발급받습니다. 만약 JWT를 활용할 때 로그아웃을 구현하거나, 혹은 제한을 위해 블랙리스트 기법을 활용하려면 RefreshToken을 DB에 저장해서 활용해야 합니다. RefreshToken을 DB에 저장시켜서 활용한다면, 세션과 동일하게 부하 문제가 발생할 수 있습니다. 그렇게 된다면 토큰의 본연의 장점이 사라질 수 있습니다.

 

그렇지만 세션은 항상 DB를 조회해야 한다면, JWT를 활용한다면 DB 조회 횟수 정도는 줄일 수 있다는게 제 생각입니다. 토큰이 만료가 되지 않거나 또는 accessToken이 사라지지 않았다면 서버 쪽으로 RefreshToken을 계속 확인할 필요는 없어집니다. 그러므로 토큰이 만료됐을 때, 또는 accessToken이 없을 때만 새로 토큰을 발급받기 위해 RefreshToken을 활용하기 때문에 부하에 대한 부담은 조금은 줄일 수 있지 않을까 생각합니다.

 

 

 

 

 

 

 


 

RefreshToken 탈취

완벽한 보안은 없습니다. 그렇기에 쿠키에 저장된 RefreshToken도 당연히 탈취당할 수 있습니다. 만약 그렇다면 어떻게 해결할 수 있을까요? 

 

저 또한 완벽한 답을 알 수 없지만, 결국은 RefreshToken을 서버사이드로 저장한 후, 활용하는 방법(로그아웃, 블랙리스트 기능과는 별개로) 을 고려해볼 수 있을 것입니다. DB에 RefreshToken을 저장하고, 이때 index 값을 쿠키에 저장해서 활용하는 방법입니다. 이렇게 한다면 RefreshToken을 외부에 노출시키지 않을 수 있고, 외부에는 index 값만 노출되게 됩니다. index 값 또한 알아볼 수 없도록 hash 값을 생성해서 사용한다면 더욱 보안에 유리할 수 있습니다. 물론 DB에 저장해서 활용함으로 부하가 늘어날 수 있다는 단점이 생깁니다. 

 

위 방식은, RefreshToken을 노출시키지 않을 뿐, 해커가 쿠키에 저장된 refreshToken의 index 값을 탈취해서, 서버에 accessToken을 달라고 요청할 수 있습니다. 또한 RefreshToken의 보안을 강화하더라도, AccessToken이 탈취되었다면 AccessToken의 만료시간까지는 토큰의 활용을 제한할 수 없다는 문제가 있습니다. 이 문제를 해결하기 위해 accessToken에 대해 보안을 강화하는 방법으로 MAC Address를 암호화해서 부여하는 방법과 IP 주소를 비교하는 방법이 있는데, 이 방법은 아래 링크를 통해 확인할 수 있습니다.

 

 

 

 

[JWT] JWT 보안에 대한 고찰

0. 들어가기에 앞서 프로젝트에 JWT를 적용하며 고민했던 것들에 대해 정리합니다. 다양한 의견들 남겨주시면 감사하겠습니다. 1. Access token과 Refresh token Refresh Token에 대해 조금만 확인해보면 DB에

velog.io

 

 

 

 

 

 

 


세션 vs 토큰

정리를 하면서 의문이 드는 부분이 생겼습니다. 쿠키를 사용하면 CSRF 공격에 취약해질 수 있다고 위에 작성했는데, 아래의 링크의 방법들을 토대로 CSRF 방어에 대해 신경쓸 수 있다면, 토큰과 세션 방식 둘 다 쿠키를 사용한다고 가정했을 때 세션으로 로그인 처리를 하는 것 또한 괜찮은 방법이 아닐까 생각했습니다. 그럼 토큰과 세션 방식 중에서 어떤 것을 선택해야할 지 궁금해졌습니다.

 

 

 

 

[Web] JWT 토큰을 알아보자

들어가며 항상 프로젝트를 진행하면서, 회원가입 기능을 만들 때, JWT를 무지성으로 활용했습니다. 왜 JWT를 사용해야 하는지, JWT를 사용하면 어떤 문제가 있는지, 왜 세션은 고려하지 않았던 것

overcome-the-limits.tistory.com

 

 

세션을 사용하면, 세션 저장소를 활용해야 하기에 서버에 추가적인 저장공간이 필요하고, 이는 자연스럽게 부하를 증가시키는 요인이 될 수 있습니다. 또한 사용자가 늘어나면 많은 트래픽을 처리해야 하며, 세션을 분산시키는 시스템을 설계해야 하는데 이 과정이 어렵고 복잡한 특징이 있었습니다. 

 

토큰을 활용하면 JWT를 발급하고 검증만 하면 되기에 추가 저장소가 필요 없다는 점, 이는 Stateless한 서버를 만드는 입장에서 큰 강점이며, 만약 세션을 활용하면 세션 저장소를 왕복해야만 하는데, JWT를 활용하면 데이터베이스를 왕복해서 인증할 필요가 더욱 줄어든다는 점도 강점입니다. 또한 서버를 확장하거나 유지 보수하는데도 유리합니다. 토큰 기반으로 하는 다른 인증 시스템에 접근이 가능하다는 측면도 있습니다.

 

그러나 발급된 JWT에 대해서는 돌이킬 수 없으며, 유효기간이 완료될 때까지는 계속 사용이 가능합니다. 또한 Payload에 정보를 제한적으로 담을 수 있으며 JWT는 HTTP를 통해 전송하기 때문에 페이로드 크기가 클수록 데이터 전송에 있어서 비용이 커진다는 단점이 있습니다. 

 

 

이 부분은 계속해서 정리해보고 싶습니다. 

 

 

 

 

CSRF(Cross-Site Request Forgery) 공격과 방어

 

junhyunny.github.io

 

Spring Security Session과 CSRF에 대해..

Intro 지금까지 스프링 시큐리티의 개요와 인증, 인가에 대해서 정리했었다. 이제 CSRF와 세션에 대한 내용이 남았다. 정리하려고 여기저기 살펴보니, 사실 내용이 많지는 않았다. 그래서 이 포스

changrea.io

 

 

 

 

 

 


 

 

마치며

공부를 하며 그동안 아주 기초적인 것들을 제대로 공부하지 않았다는 것을 깨닫습니다. 좋은 기술을 배우는 것도 좋지만, 기술의 기반이 되는 기초적인 지식을 먼저 쌓는 것이 중요하다는 것을 다시금 깨달았습니다. 기초가 튼튼한 개발자가 되고 싶습니다.

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

출처

 

JWT는 어디에 저장해야할까? - localStorage vs cookie

이번에 지하철 미션을 만들면서 JWT를 클래스 property에 저장했었는데 리뷰어 분께 해당 부분을 피드백 받으면서 어디에 JWT를 저장하는 것이 좋을까 에 대해 고민해보게 되었다. 0. 기본 지식 JWT Js

velog.io

 

🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)

localStorage냐 쿠키냐 그것이 문제로다

velog.io

 

쉬어가는 페이지 - 슬라이딩 세션과 리프레시 토큰

토큰을 사용하면 서버에 사용자의 상태를 저장하지 않는다는 장점이 있는 반면 공격자가 토큰을 탈취한 경우 토큰을 즉시 무효화시키지 못하는 보안 취약점을 가집니다. 이를 방지하 ...

wikidocs.net

 

The Ultimate Guide to handling JWTs on frontend clients (GraphQL)

JWTs are becoming a popular way of handling auth. This post aims to demystify what a JWT is, discuss its pros/cons and cover best practices in implementing JWT on the client-side, keeping security in mind.

hasura.io

 

JWT 혹은 OAuth2 의 refresh 토큰을 어디다 저장해야 할까? | 두글 블로그

요즘 네이버로그인, 카카오 로그인이나 구글 로그인등등 소셜 미디어(Social media) 사용자 로그인 처리를 하다보니 로그인된 상태가 끊임없이 유지되는 것을 구현해야 되더군요. 그러려면 결국 리

doogle.link

 

 

[JWT] JWT 보안에 대한 고찰

0. 들어가기에 앞서 프로젝트에 JWT를 적용하며 고민했던 것들에 대해 정리합니다. 다양한 의견들 남겨주시면 감사하겠습니다. 1. Access token과 Refresh token Refresh Token에 대해 조금만 확인해보면 DB에

velog.io