일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 항해99
- 99클럽
- 빈 조회 2개 이상
- 프로그래머스
- 구글 OAuth login
- 커스텀 헤더
- spring batch 5.0
- 개발자 취업
- 단기개발자코스
- 디자인 패턴
- Python
- 전략패턴 #StrategyPattern #디자인패턴
- 인프콘 2024
- 디자인패턴
- 개발자부트캠프추천
- jwt
- 코딩테스트 준비
- DesignPattern
- 파이썬
- jwttoken
- 프로그래머스 이중우선순위큐
- 취업리부트코스
- 빈 충돌
- @FeignClient
- TiL
- infcon 2024
- Spring multimodule
- KPT회고
- JavaScript
- 1주일회고
- Today
- Total
m1ndy5's coding blog
JWT Login과 Cookie (feat. Redirect) 본문
프론트없이 JWT 토큰을 사용한 로그인을 구현하던 중에 겪었던 문제들과 고민들에 대해서 정리해보려고 한다!
일단 기술 스택으로는 Spring boot 3.2, Spring Security 6.2, Spring Gateway 4.1을 사용하였다.
현재 진행하고 있는 프로젝트는 멀티모듈 구조이기 때문에 로그인 후 토큰 발급(인증)은 User Module에서 진행하고, 각 요청의 토큰 검증(인가)은 Gateway Module에서 진행하게 설계했다.
가장 먼저 문제를 겪었던 부분은 로그인이 성공한 후에 SimpleUrlAuthenticationSuccessHandler의 onAuthenticationSuccess 메서드에서 토큰을 발급하고 메인페이지로 리다이렉트해주는 부분이었다.
구현하고자 했던 로직은 로그인 성공 -> onAuthenticationSuccess 메서드가 실행 -> JWT 토큰을 Header에 달아서 메인페이지로 리다이렉트 였다.
하지만...... 헤더에 JWT 토큰이 달릴 생각을 안하는 것이 아니겠는가,,,,,,,
첫번째 문제. 리다이렉트시 Custom Header 추가 불가능
정말 많은 방법을 시도해봤지만 결국 결론은
리다이렉트시에는 Custom Header를 추가할 수 없다는 것 이었다.
이유는 다음과 같다.
1. HTTP 리다이렉트 응답은 클라이언트에게 새로운 위치(새 URL)로 요청을 재시도하라는 지시를 내리는 것이기 때문에 그저 다른 URL로 이동하도록 하는 것일 뿐, 실제로 서버가 클라이언트에게 새로운 요청을 구성하는 것이 아니다.
2. 브라우저는 리다이렉트 응답을 받으면 내부적으로 새로운 요청을 생성하여 지정된 URL로 이동하고 이 새로운 요청은 원래 요청과 별개의 것이기 때문에 원래 요청의 헤더를 복사하지 않는다.
3. 리다이렉트 응답에서 커스텀 헤더를 추가할 수 있게 되면, 악의적인 사용자가 악용할 수 있기 때문에 허용하지 않는다.
오케이~~ 그렇다면 리다이렉트 말고 포워드를 사용하야겠다!!
리다이렉트 VS 포워드
리다이렉트는 클라이언트가 새로운 URL로 새로운 요청을 보내기 때문에 주소창이 변경되고 히스토리에 남는다.
ex) 로그인 후 메인 페이지 이동, 페이지가 이동되었음을 알릴 때 등
포워드는 서버 내부에서 요청을 다른 자원으로 전달하기 때문에 주소창이 변경되지 않고, 히스토리에 남지 않는다.
ex) URL 변경이 없기 때문에 서블릿에서 JSP 페이지로 포워드하여 뷰를 렌더링할 때 등
두번째 문제. 포워드시 Gateway 인가 필터를 거치지 않고, URL도 변경되지 않음
원래 의도가
로그인 요청 -> User Module 로그인 성공 -> Gateway 인가(토큰 검증) 필터 거치기 -> User Module의 메인페이지
였지만 포워드는 gateway의 필터를 거치지 않았고 또 다른 문제는 메인화면이 떴지만 여전히 URL이 /login 이었다.
어떻게든 헤더에 토큰을 달 수 없음을 깨달은 나는... (혹시 있다면 알려주세요!!)
토큰을 어디다 저장해야할 지 고민했다.
JWT 토큰 저장 위치
1. 로컬 스토리지(Local Storage)
로컬 스토리지에 저장했을 때의 장점은 JavaScript에서 로컬 스토리지에 저장된 JWT에 쉽게 접근할 수 있고 브라우저가 닫히고 다시 열려도 남아있을 수 있다.
단점으로는 XSS(교차 사이트 스크립팅) 공격에 취약해 자바스크립트를 통해 로컬 스토리지에 접근하면 토큰 탈취가 가능하고 요청 시 자동으로 서버에 전송되지 않기 때문에 수동으로 헤더에 넣어줘야한다.
2. 쿠키(Cookies)
쿠키에 저장했을 때의 장점은 HttpOnly 속성을 설정하면 자바스크립트에서 쿠키에 접근할 수 없어 XSS 공격으로부터 보호할 수 있고 Secure나 SameSite 속성을 설정하여 보안을 강화할 수 있다.
단점으로는 용량이 4KB고 설정과 관리가 로컬 스토리지보다 복잡하다는 것이다.
토큰은 어짜피 유효기간이 짧고 탈취가 되면안되기 때문에 쿠키에 저장하기로 했다!
세번째 문제. HttpOnly, SameSite, Path가 대체 뭔대?!
쿠키에 토큰을 설정하는 과정도 결코 쉽진않았다...
물론 코드적으로는 간단했지만 앞서 언급했던 보안을 위한 설정들을 해줘야 했고 지식이 별로 없던 나로써는 '이게 뭐지'만 외치고 있었다,,
개발자 도구를 열어서 Application -> Cookies 에 들어가보면 이런 컬럼들을 볼 수 있다.
하나하나 알아가 보자!
Name과 Value는 쿠키 이름과 해당 값이다.
Domain : 쿠키가 전송될 도메인이다. 만약 Domain=example.com 으로 설정하면 example.com과 sub.example.com 에서 쿠키를 사용할 수 있다. 기본값은 쿠키를 설정한 도메인이다.
Path : 쿠키가 전송될 URL 경로이다. Path=/app 으로 설정하면 /app, /app/subpage 와 같은 경로에서만 쿠키를 사용할 수 있다.
HttpOnly : 쿠키가 클라이언트 측 스크립트에서 접근할 수 없게 한다. XSS(교차 사이트 스크립팅) 공격으로부터 쿠키를 보호할 수 있다. HttpOnly 설정 시 document.cookie 를 통해 쿠키에 접근할 수 없다. 기본값은 없는 상태이다.
**XSS 공격 : 웹 애플리케이션 취약점을 이용해 악성 스크립트를 삽입하는 공격
Secure : 쿠키가 HTTPS를 통해서만 전송되도록 한다. 네트워크 공격(ex. 중간자 공격)으로부터 쿠키를 보호할 수 있다. 기본값은 없는 상태이다.
**중간자 공격(MITM) : 두 당사자 간의 통신을 은밀히 가로채고 이를 통해 정보를 도청하거나 조작하는 공격
SameSite : 크로스 사이트 요청에서 쿠키가 전송되는 방식을 제어한다. CSRF(크로스 사이트 요청 위조) 공격을 방지한다. 3가지의 옵션이 있는데 Strict는 같은 사이트 내의 요청에서만, Lax는 같은 사이트와 일부 크로스 사이트 요청(GET 메서드와 안전한 메서드)에서만, None은 모든 크로스 사이트 요청에서 전송된다. None인 경우 Secure 속성을 함께 사용해야 한다.
예를 들어서 SameSite=Strict로 설정하면 사용자가 다른 사이트에서 링크를 클릭해도 쿠키가 전송되지 않는다.
쿠키 설정 방식에 따라 다르며 명시적으로 설정하지 않으면 Lax가 기본값인 경우가 많다.
**크로스 사이트 요청 : 한 웹사이트에서 발생한 요청이 다른 웹사이트로 전송되는 상황.
**출처(Origin) : 두 웹사이트가 서로 동일 출처(same origin)로 간주되려면 프로토콜, 도메인, 포트 이 세가지 요소가 모두 일치해야한다. 한 웹페이지가 다른 웹페이지의 문서나 스크립트에 접근할 수 있는지를 결정하는 기준이다.
ex) http <-> https, example.com <-> sub.example.com, 80 <-> 8080
**CSRF 공격 : 사용자가 의도하지 않은 요청을 사용자의 인증된 세션을 이용해 서버에 보내도록 하는 공격. 공격자는 사용자가 특정 웹사이트에 로그인된 상태임을 악용하여 권한을 도용해 원하지 않는 행동을 수행한다.
이전에는 API 형식으로만 개발했기 때문에 그냥 토큰을 응답으로 돌려주는 형식이어서 자세히 공부하지 않았는데 이번에 개발을 하면서 다양한 상황을 겪고 한층 성장하게 된 것 같아서 좋다^_^
혹시 틀린 부분이 있으면 피드백 주세요~~~
'백엔드 with java > spring' 카테고리의 다른 글
ALL ABOUT REFRESH TOKEN (0) | 2024.05.20 |
---|---|
About Setter (feat. Builder) (0) | 2024.01.17 |
Entity 대신 DTO를 반환해야하는 이유 (0) | 2024.01.17 |
UnsatisfiedDependencyException (feat.영한님 강의) (2) | 2023.12.05 |
Spring Security UserDetails, UserDetailsService (1) | 2023.10.11 |