개요
지난 글에 이어 이번에는 OAuth2 로그인 방식에서 JWT를 Access Token / Refresh Token 구조로 사용할 때,
Access Token을 헤더에 담는 방법을 다루어보고자 합니다.
문제 상황
Access Token과 Refresh Token으로 인증을 관리하게 되면 다음과 같은 구조로 설계하게 됩니다.
- Access Token
- HTTP Header에 담는다.
- Authorization : Bearer {AccessToken}
- Refresh Token
- HttpOnly 쿠키에 담는다.
하지만 OAuth2 소셜 로그인에 성공하면, 리다이렉트가 수행되어 Access Token을 헤더에 담아 전달할 수 없습니다.
그렇다면 Access Token을 어떻게 헤더로 가져올 수 있을까요?
우선 Refresh Token과 Access Token을 각각 쿠키와 헤더에 구분하여 담는 이유를 알아보겠습니다.
Access Token을 헤더에 담는 이유
RFC 7235 표준에 따르면, HTTP는 인증 정보를 서버에 전달할 때 Authorization 헤더를 사용하는 방식을 권장하고 있습니다.
이는 OAuth2 등 대부분의 웹 인증 프로토콜에서도 동일하게 적용되는 방식입니다.
Refresh Token을 쿠키에 담아 처리하는 이유
Access Token은 상대적으로 짧은 유효기간을 갖고, Refresh Token은 상대적으로 긴 유효기간을 갖습니다.
Access Token은 탈취당하더라도 짧은 유효기간을 갖고 있기에 피해를 최소화할 수 있습니다.
하지만 Refresh Token이 탈취되면, 유효기간이 길고 Access Token을 재발급받을 수 있습니다.
JWT의 탈취되는 주요 경로는 주요 경로는 XSS를 통한 localStorage 접근인데, HttpOnly 쿠키에 담으면 XSS 공격으로부터 보호할 수 있습니다.
물론 HttpOnly 쿠키에 담더라도 해킹 위험은 존재합니다. 쿠키 방식은 CSRF 공격에 취약합니다.
이를 방지하기 위해서는 Refresh Token Rotate, 블랙리스트 등을 사용하게 됩니다.
Access Token 헤더 요청 흐름도

OAuth2를 사용해 구글 로그인에 성공했다고 가정하겠습니다.
로그인 요청에 성공하면, 서버에서는 JWT 토큰을 쿠키에 담아 전달하게 됩니다.
여기서 쿠키에 담긴 액세스 토큰을 헤더로 전달하려면, 추가적인 작업이 필요합니다.
예를 들어, 서버에 POST /jwt-header 요청을 처리하는 컨트롤러가 존재한다고 가정하겠습니다.
로그인에 성공하면 클라이언트에서 해당 엔드포인트로 요청을 보내도록 구현하고, 서버에서는 쿠키에 담긴 Access Token을 만료 및 새로운 토큰을 발급하여 헤더에 담는 방식으로 구현할 수 있습니다.
해당 요청이 성공하면, Access Token은 헤더에 Refresh Token은 쿠키에 담긴 구조로 변경됩니다.
서버에서는 어떻게 동작할까?

- 서버의 OAuth2LoginAuthenticationProvider는 외부 인증 서버로부터 유저 정보를 획득합니다.
- 유저 정보 획득에 성공하면, 작성한 SuccessHandler를 통해 Refresh/Access 토큰을 생성하고 쿠키에 담습니다. 이때 로그인 성공 시 리다이렉트 할 URL을 전달합니다.
- 클라이언트에서는 전달받은 URL로 리다이렉트합니다.
- 해당 URL로 이동하면, 토큰을 헤더로 전달받기 위한 요청(POST /jwt-header)을 호출합니다.
- 작성한 JwtHeaderController에서는 해당 요청을 받아, 쿠키에서 Access Token을 찾아 만료시키고, 새로운 Access Token을 발급하여 헤더로 전달합니다.
- 이후 요청에서는 헤더에 담긴 Access Token을 사용하여 요청할 수 있습니다.
결론
이번 구현을 통해 기술에는 항상 트레이드오프가 존재한다는 사실을 다시 체감할 수 있었습니다.
토큰을 어디에 저장하느냐에 따라 서로 다른 보안 위협이 발생하고, 이를 완전히 제거하는 것은 사실상 불가능하다고 생각합니다.
다만 각 방식의 특성과 취약점을 이해하면 더 안전한 방향으로 설계할 수 있을 것 같습니다.
인증 기능 개발을 마무리한 뒤 복잡했던 흐름이 헷갈려 정리를 시작했지만, 전체 구조를 시각적으로 다시 되짚어보면서 OAuth2 로그인과 JWT 관리 방식에 대한 이해를 훨씬 명확히 할 수 있었습니다.
참고
'Teck Stack > Spring' 카테고리의 다른 글
| [Auth] OAuth2 구글 로그인 + JWT 방식의 인증 플로우 (0) | 2025.12.08 |
|---|---|
| [Spring] @Scheduled 대신 Quartz를 사용한 스케줄링 구현 (0) | 2025.10.14 |
| [Spring] 외부 API 요청 시 상태 코드별 재시도 전략 (2) | 2025.08.09 |
| [Spring] MultipartFile + DTO 요청 받기 (feat : 415 에러 해결) (0) | 2025.06.30 |
