🙌 서론
관리자 페이지의 로그인 기능과 로그아웃 기능을 만들게 되었다.
로그인 후 해당 사용자의 요청에 대해서만 응답하는 방법을 고민하게 되었다. 그러다 JWT 토큰 방식을 알게 되었다.
서버에서 JWT 토큰을 발급받고 난 뒤 클라이언트에서는 JWT를 어디에 저장해 두어야 가장 좋을까?라는 고민하며 서칭을 한 내용을 정리해 둔다.
📒 기본 지식
1. JWT
Json Web Token의 약자로 모바일이나 웹의 사용자 인증을 위해 사용하는 암호화된 토큰을 의미한다.
서버에서 발급받은 JWT 토큰을 Request에 담아 사용자의 정보 열람, 수정 등의 작업을 수행할 수 있다.
2. JWT 로그인은 어떻게 이루어 지나?
- 유저가 로그인을 시도한다.
- 서버가 인증정보를 보내주는데, 암호화나 시그니처 추가가 가능한 데이터 패키지안에 인증 정보를 담아서 보내준다. 이 패키지가 Json Web Token 즉 JWT이다.
- 담기는 정보 중 accessToken과 refreshToken이 이후 유저 인증에 사용된다.
- 이 정보를 클라이언트에 저장해 둔다.
- 실질적인 인증 정보는 accessToken인데 일정 시간이 지나면 만료하는 구조를 가지고 있다.
- refreshToken을 이용해 로그인을 지속적으로 유지할 수도 있다. refreshToken을 서버에 보내면 그때마다 새로운 accessToken을 발급해 돌려준다. (refreshToken사용은 옵션)
- accessToken을 유저에게만 보여줄 수 있는 정보에 접근할 때 서버에 보면 된다.
- 서버는 그 토큰이 유효한지 확인하는 방식으로 인증을 마친다.
3. XSS(Cross Site Scripting)
- 공격자가 의도하는 악의적인 js 코드를 피해자 웹 브라우저에서 실행시키는 것이다
XSS라고 불리는 이유는 CSS가 이미 약자로 있기 때문이다.
code injection attack이라고도 한다.
이 방법으로 피해자 브라우저에 저장된 중요 정보들을 탈취 가능하다.
Javascript를 통해 사이트의 글로벌 변숫값을 가져오거나 그 값을 이용해 사이트인 척 API 콜을 요청할 수도 있다. 다시 말하면 공격자의 코드가 내 사이트의 로직인 척 행동할 수 있다는 거다.
4. CSRF
- 공격자가 다른 사이트에서 우리 사이트의 API 콜을 요청해 실행하는 공격이다.
- API 콜을 요청할 수 있는 클라이언트 도메인이 누구인지 서버에서 통제하고 있지 않다면 CSRF가 가능하다.(IP 허용)
- 공격자가 클라이언트에 저장된 유저 인증정보를 서버에 보낼 수 있다면, 제대로 로그인한 것처럼 유저의 정보를 변경하거나 유저만 가능한 액션들을 수행할 수 있다.
- 정상적인 Request를 가로채 피해자인 척하고 백엔드 서버에 변조된 Request를 보내 악의적인 동작을 수행하는 공격을 의미힌다.(피해자 정보 수정, 정보 열람)
▶ 내가 쓰지 않은 광고성 글이 SNS에 올라갈 수 있다.
- 공격자는 유저가 img를 열람하도록 하거나 link를 클릭하도록 유도한다.
- 이 action은 사용자 의도와는 관계없이 http request를 보낸다.
- 유저가 로그인이 되어있는 상태라면 이 request는 정상적으로 서버에 동작을 수행한다.
🙋♂️ XSS를 막으면 CSRF도 막히는 것 아닌가? NO
img 태그나 링크로도 사용자 브라우저에서 악의적인 Request를 보낼 수 있다.
사용자 브라우저의 js를 조작하는 XSS와는 성격이 다르다.
🙄 JWT? 그냥 아무 데나 저장하면 안 돼?
1. JWT는 개인정보나 다름없다.
보안 관련 대책 없이 JWT를 아무 곳에나 저장한다는 것은 우리 고객의 정보를 가져가도 상관없다는 의미와 같다.
2. JWT를 private variable에만 저장하면 안 될까?
js variable은 웹페이지를 새로고침 하면 사라지는 휘발성이다.
JWT를 private variable에 저장하는 것은 안되진 않지만 사용자 입장에서 화면을 새로 고침 할 때마다 새로 로그인을 시도해야 한다면 불편하고 귀찮을 것이다. 따라서 private variable에만 JWT를 저장하는 것은 피해야 한다.
😵 그럼 어디에 저장할 수 있을까?
- localStorage
- cookie
결론은 둘 중에 정답은 없다.
각각의 장단점이 존재하고 개발환경이 다르기 때문에(api를 사용해서 백엔드와 소통이 불가능한 경우), 무엇이 더 우월하다는 이야기는 할 수 없다.
그럼에도 Best Option은 존재한다.
1. localStorage에 저장
🥇 장점
CSRF 공격에는 안전하다.
그 이유는 자동으로 Request에 담기는 쿠키와는 다르게 JS코드에 의해서 헤더에 담기므로 XSS를 뚫지 않는 이상 공격자가 정상적인 사용자인 척 Request를 보내기는 어렵다.
🥉 단점
XSS에 취약하다.
공격자가 localStorage에 접근하는 JS코드 한 줄만 주입하면 localStorage를 공격자가 내 집처럼 드나들 수 있다.
2. cookie에 저장
🥇 장점
XSS 공격으로부터 localStorage에 비해 안전하다.
쿠키의 httpOnly 옵션을 사용하면 JS에서 쿠키에 접근 자체가 불가능하다.
그래서 XSS 공격으로 쿠키 정보를 탈취할 수 없다.(httpOnly 옵션은 서버에서 설정 가능)
하지만 XSS 공격으로부터 완전히 안전한 것은 아니다.
httpOnly 옵션으로 쿠키의 내용을 볼 수 없다 해도 JS로 Request를 보낼 수 있으므로 자동으로 Request에 실리는 쿠키의 특성상 사용자의 컴퓨터에 요청을 위조할 수 있기 때문이다.
공격자가 귀찮을 뿐이지 XSS가 뚫린다면 httpOnly cookie도 안전하진 않다.
🥉 단점
CSRF 공격에 취약하다.
자동으로 HTTP Request에 담아서 보내기 때문에,
공격자가 Requset url만 안다면 사용자 관련 link를 클릭하도록 유도하여 Request를 위조하기 쉽다.
※ 참고
최근에는 쿠키의 CSRF 취약점을 막기 위해 쿠키의 same-site 속성과 JS의 fetch api 속성의 기본값 등으로 Request에 쿠키를 싣지 않음이 설정되어 있다.
⭐ 결론
1. Best Option
- refreshToken을 사용하는 방법
백엔드 API 개발자와 소통이 가능하다면,
refreshToken을 httpOnly 쿠키로 설정하고 url이 새로고침 될 때마다 accessToken을 발급받는다.
발급받은 accessToken은 JS private variable에 저장한다.
이 방법의 장점은,
refreshToken이 CSRF에 의해 사용된다 하더라도 공격자는 accessToken을 알 수 없다.
CSRF는 피해자의 컴퓨터를 제어할 수 있는 것이 아니다.
요청을 위조하여 피해자가 의도하지 않은 서버동작을 일으키는 공격방법이다.
따라서 refreshToken을 통해 받아온 response(accessToken)는 공격자가 확인할 수 없다.
따라서 쿠키를 사용하여 XSS를 막고, refreshToken 방식을 이용하여 CRSF를 막을 수 있다.
▶ 참고
프론트에서 안전하게 로그인 처리하기 (ft. React)
'store your refresh token in the cookie' 부분 참고
2. cookie vs localStorage, 굳이 1개를 선택한다면?
- localStorage를 선호한다.
- cookie의 httpOnly 옵션도 XSS 공격을 완벽히 막을 수 없다. 어차피 XSS 방어는 필수적이므로 cookie의 장점이 매력적이게 보이지 않는다.
- cookie를 사용한다면 백엔드 API에 내가 사용하는 cookie를 위한 설정을 요구해야 한다. 백엔드와 조율이 잘 되는 상태라면 cookie를 사용해도 무방하지만, 불가능한 경우 localStorage가 더 좋다.
- mdn은 저장소로 쿠키를 추천하지 않는다. 대신 ModernStorage(localStorage와 sessionStorage)를 추천한다.
반대로, cookie를 지지하는 입장도 보자.
🙋♂️ cookie를 지지하는 입장
CSRF 공격은 다루기 쉬운 반면 프론트엔드 크기가 크면 클수록
XSS 공격을 막기 위한 작업은 많아지므로 쿠키 사용을 추천한다고 한다.
▶ 의견 참고
저는 cookie에 저장하는 편입니다. (가능하다면 httpOnly)
쿠키는 별도로 헤더에 담지 않아도 요청을 보낼 때 자동으로 담아서 보내지기 때문에
매번 서버로의 요청에 담아야 하는 토큰이라는 성격과 잘 맞고, 코드도 더 간결하게 작성할 수 있게 되는 것 같아요
▶ 참고 : 개인적인 기록을 위해 해당 포스팅을 참고하여 옮긴 내용입니다.
'Java' 카테고리의 다른 글
[Java] JAR vs War 비교 분석 (0) | 2022.08.10 |
---|---|
[React] JWT 토큰과 localStorage 로그인 (0) | 2022.08.08 |
[node.js] JWT 토큰 발급 & JWT 미들웨어 설정 (0) | 2022.08.03 |
[JS] localStorage & sessionStorage 비교 (0) | 2022.07.29 |
[JS] JSON 값 넣는 방법 (0) | 2022.07.29 |