일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 1차캐시
- queryDSL
- Filter
- jpa에러
- 최종 프로젝트
- 빈생성안됨
- REST API 규칙
- json
- github
- uncheck Exception
- 스프링 부트 기능
- Error creating bean with name
- JPA
- Spring Spring boot 차이
- 인텔리제이
- ERD 작성
- JoinColumn
- 스프링 부트 공식 문서
- git
- @IdClass
- jpa회원가입
- 스프링부트오류
- jwt메서드
- REST란
- json gson 차이
- Unsatisfied dependency
- Q 클래스
- spring서버
- JPA주의사항
- 복합키
- Today
- Total
Everyday Dev System
JWT 토큰은 어디에 저장해야 할까? 본문
서버에서 발급한 Access Token 및 Refresh Token을 어디에 저장해야 할까?
현재 상황
현재 제 프로젝트에서 JWT 토큰을 활용합니다.
Access Token이 탈취됐을 때 대비를 위해 Refresh Token 개념을 도입하여, 두 토큰 모두 쿠키에 저장했습니다.
쿠키에 담아 JS 파일에서 토큰 여부를 확인하기 위해 setHttpOnly()를 설정하지 않았습니다.
문제점
클라이언트 측에서 둘다 갖고 있으면 탈취 당할 가능성이 있지 않을까?
고민
먼저, JWT 인증은 서버가 상태를 갖지 않아 클라이언트 측에서 토큰을 저장하고 있어야 합니다.
클라이언트가 사용할 수 있는 저장소는 쿠키, 세션 스토리지, 로컬 스토리지가 있습니다.
이 중 어떤 저장소에 저장할지 선택하기 위해 각 저장소별 차이점을 찾아보았습니다.
저장소 별 차이점으로 고려해야 할 점
첫번째는, "얼마나 오래 보관할 것인가?" 입니다.
- 세션 스토리지에 저장된 데이턴느 브라우저를 닫으면 제거됩니다. 자동 로그인 등 새 브라우저를 열었을 때 유지하기 어렵습니다.
- 로컬 스토리지는 영구 저장소이다. 따로 지워주지 않으면 브라우저에 계속 남아있다. 중요한 정보를 넣어두면 위험합니다.
- 쿠키는 만료일을 저장하게 되어 있습니다.
두번째로, 가장 중요하게 고려할 사항은 "보안 위험 대비" 입니다.
토큰은 사용자 여부를 판단할 수 있는 도구입니다. 공격자가 토큰을 탈취하여 정당한 사용자인 척 할 수도 있습니다. 이런 위험을 최소화할 수 있는 저장소를 선택하여야 합니다. 이 부분은 의견이 분분한 지점입니다.
- 로컬 스토리지는 JavaScript 코드를 통해 접근할 수 있어 XSS 공격에 취약합니다. 쿠키도 JS를 통해 접근 가능 하지만, 쿠키를 설정할 때 httpOnly 플래그를 설정하여 예방할 수 있습니다.
- 또한, 쿠키가 모든 웹 요청에 함께 전송된다는 점이 취약할 수 있으나 쿠키를 만들 때 Secure flag를 설정하면 보안되지 않은 연결에서는 전송되지 않습니다.
- 쿠키는 여러 옵션을 추가로 설정하여 로컬 스토리지의 취약점을 보완할 수 있습니다. 그렇지만 쿠키 역시 CSRF라는 공격에 취약합니다. 다만, XSS 공격이 범위가 더 넓고 방어가 힘들기에 쿠키에 저장하는 것은 권장하는 의견도 많다고 니다.
해결방안
Access Token을 로컬 스토리지 또는 세션 스토리지에 저장하고,
Refresh Token은 쿠키에 저장하고 보안 옵션(HTTP Only)을 활성화 하였습니다.
1) HTTP Only Cookies
클라이언트에서 자바스크립트로 쿠키를 조회할 수 있는데 해당 옵션을 활성화 하면 브라우저에서 쿠키에 접근할 수 없으므로 XSS와 같은 공격으로부터 안전합니다.
cookie.setHttpOnly(true);
2) Secure Cookie
웹브라우저와 웹서버가 HTTPS로 통신하는 경우에만 웹브라우저가 쿠키를 서버로 전송하는 옵션.
네트워크 통신 시에 송수신되는 패킷을 가로채는 스니핑 공격을 예방할 수 있습니다.
cookie.setSecure(true);
setHttpOnly()를 설정하면 js 파일에서 아래와 같은 코드를 사용할 수가 없습니다.
그러므로, Authorization에 해당하는 Access token은 HTTP only를 설정하지 않고 냅두었습니다.
$(document).ready(function () {
pageSetting();
});
/* 페이지 첫 로드 메서드 */
function pageSetting() { // main page 세팅
const token = Cookies.get('Authorization');
if(!token) {
// 토큰 없을 경우, 로그인하지 않은 유저에게 보여줘야 할 내용을 처리하실 수 있습니다.
}
// 만료되지 않은 경우, 로그인한 유저에게 보여줘야 할 내용을 처리하실 수 있습니다.
}
대신, Refresh Token에만 SetHttpOnly() 설정을 추가하여 보안적으로 안전하도록 설정했습니다.
public void addJwtToCookieAccessToken(String token, HttpServletResponse res) {
token = URLEncoder.encode(token, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
res.addCookie(cookie);
}
// JWT Cookie 에 refresh token 저장
public void addJwtToCookieRefreshToken(String refreshToken, HttpServletResponse res) {
refreshToken = URLEncoder.encode(refreshToken, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행
Cookie cookie = new Cookie(REFRESH_HEADER, refreshToken); // Name-Value
cookie.setPath("/");
cookie.setSecure(true);
cookie.setHttpOnly(true);
res.addCookie(cookie);
}
여기서 AccessToken에도 SetHttpOnly() 설정을 추가하여 보안을 높혔을 때,
클라이언트 측에서 인증 여부를 판단할 수 있는 로직을 구현하는 방법을 고민해봐야 할 것 같습니다.
참조:
https://velog.io/@znftm97/JWT-Session-Cookie-%EB%B9%84%EA%B5%90-sphsi9yh
'내배캠 주요 학습 > TIL : Today I Learned' 카테고리의 다른 글
왜 Redis 를 활용 하였나? (0) | 2023.09.14 |
---|---|
Redis 캐시 (0) | 2023.09.05 |
Access Token 만료시 재요청 없이 재발급 (0) | 2023.08.25 |
ajax 통신 시 success , fail, done, fail 차이 (0) | 2023.08.23 |
Cookie.get() 이 안될 때 살펴봐야 할 사항 (0) | 2023.08.23 |