Skip to content

20250207/crontab_save_log 김규영 기술블로그 제출 #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions 2025-02-05-OAuth2-JWT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---
layout: post
title: "스프링 부트에서 구현한 구글 OAuth2 + JWT 로그인 구현, 이대로 안전할까?"
author: "윤석모"
categories: "기술 블로그"
banner:
image: 썸네일로 넣고 싶은 이미지 링크
background: "#000"
height: "100vh"
min_height: "38vh"
heading_style: "font-size: 4.25em; font-weight: bold; text-decoration: underline"
tags: [oauth2, jwt, cookie]
---

현재 프로젝트에서는 스프링 부트(3.3.5)에서 구글 OAuth2 + JWT(Access Token) 방식을 사용해 로그인 기능을 구현했습니다.
JWT(Access Token)을 쿠키에 저장하여 인증을 처리하는 방식인데, 과연 이 방법이 충분히 안전할까요?
Refresh Token 및 블랙리스트 추가 구현이 필요한 이유는 무엇일까요?
이번 글에서는 현재 로그인 구현 방식의 보안 이슈를 살펴보고, 이를 개선하기 위한 방향을 고민해보겠습니다.

## 현재 로그인 구현 방식

### 1️⃣ 로그인 흐름
#### 1. 사용자가 구글 OAuth2로 로그인

#### 2. 서버에서 JWT(Access Token) 발급

#### 3. JWT를 쿠키에 저장하여 인증 유지

#### 4. 클라이언트는 쿠키를 사용해 API 요청 시 인증

### 2️⃣ 서버 URL 정보
프론트엔드 서버: https://www.unsemawang.com/
백엔드 서버: https://dev.unsemawang.com/

### 3️⃣ 쿠키 설정
JWT를 쿠키에 저장하면서 다음과 같이 설정했습니다.
```
.path("/")
.domain(".unsemawang.com") // 서브 도메인 간 쿠키 공유
.sameSite("None") // 다른 도메인에서도 쿠키 사용 가능
.secure(true) // HTTPS에서만 전송
.httpOnly(true) // JavaScript에서 접근 불가

```
### 4️⃣ 서브 도메인 간 쿠키 공유
백엔드와 프론트엔드 서버가 다른 서브 도메인(www.unsemawang.com , dev.unsemawang.com)을 사용하기 때문에,
```.domain(".unsemawang.com") ```
설정을 추가하여 쿠키를 공유할 수 있도록 했습니다.

🔗 [서브 도메인이란?](https://velog.io/@superwalk/%EC%BF%A0%ED%82%A4-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%84%A4%EC%A0%95)

## 🚨보안 이슈 분석

#### 🔹 CSRF 공격에 취약할 수 있음
현재 쿠키 설정에서 .sameSite("None") 옵션을 사용하고 있습니다.
이 설정은 다른 도메인에서도 쿠키를 사용할 수 있도록 허용하는데, 이로 인해 CSRF(Cross-Site Request Forgery) 공격에 취약해질 가능성이 있습니다.

#### ➡ CSRF 공격이란?
공격자가 사용자의 세션을 가로채어 원치 않는 요청을 실행하도록 유도하는 방식입니다.
자세한 설명은 아래 링크를 참고하세요.
🔗 [CSRF 공격과 방어 방법](https://velog.io/@superwalk/CSRF-Cross-Site-Request-Forgery%EB%9E%80)

## 개선 방향

### 🛡️ CSRF 방어 방법

### 1️⃣ CSRF 토큰 사용
#### ✅ POST, PUT, DELETE 요청 시 CSRF 토큰을 추가 인증 수단으로 사용

### 2️⃣ Referer / Origin 검증
#### ✅ 서버에서 Referer 또는 Origin 헤더를 검사하여 CSRF 차단
#### ✅ 요청이 신뢰할 수 있는 도메인 (dev.test.com 또는 www.test.com) 에서 왔는지 확인
#### ✅ CSRF 공격은 보통 Referer를 조작할 수 없기 때문에 효과적

#### 예시 (Spring Security에서 Referer 검증)
```
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/api/**"))
.and()
.headers()
.referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN);
}
```

### 3️⃣ CORS 정책 강화
#### ✅CORS 설정을 통해 신뢰할 수 있는 도메인에서만 요청 허용
#### ✅Access-Control-Allow-Origin을 특정 도메인 (dev.test.com)만 허용
#### ✅Access-Control-Allow-Credentials: true를 사용하여 쿠키 기반 인증 가능하도록 설정

### 4️⃣ 쿠키 보안 강화 (HttpOnly + Secure 적용)
#### ✅ HttpOnly → JavaScript에서 쿠키 접근 불가 → XSS 방어
#### ✅ Secure → HTTPS에서만 쿠키 전송 → MITM 공격 방어

### 5️⃣ Content-Type 제한 (application/json만 허용)
#### ✅ CSRF 공격은 보통 application/x-www-form-urlencoded 또는 multipart/form-data로 이루어짐
#### ✅ Content-Type이 application/json이 아닌 경우 요청을 차단(application/json이라고 해서 공격이 없는 것은 아님)

#### 🔹 Content-Type 검사 예제 (Spring Boot)
```
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.requireCsrfProtectionMatcher(request ->
"POST".equals(request.getMethod()) &&
!"application/json".equals(request.getContentType())
);
}
```
### ✔️ 방어 원리
#### CSRF 공격은 보통 HTML 폼을 이용하여 application/x-www-form-urlencoded로 요청을 보냄
#### 서버에서 application/json이 아닌 요청을 차단하면 CSRF 공격 무력화 가능

#### 🔹 CORS 설정과 allowCredentials(true) 사용 문제
현재 CORS 정책을 강화하기 위해 .allowedOriginPatterns("https://www.unsemawang.com/")을 추가했습니다.
하지만 .allowCredentials(true) 옵션도 함께 사용했기 때문에 보안 이슈가 발생할 수 있습니다.

#### allowCredentials(true)란?
Spring Boot의 CORS 설정에서 allowCredentials(true) 옵션을 사용하면,
브라우저가 인증 정보를 포함한 요청을 보낼 수 있도록 허용합니다.
즉, 쿠키, Authorization 헤더, TLS 클라이언트 인증서 같은 민감한 데이터를 CORS 요청에 포함할 수 있게 됩니다.

#### ➡ 문제점
이 옵션을 잘못 사용하면 악의적인 웹사이트에서 사용자의 인증 정보를 가로채는 CSRF 공격이 가능해집니다.

### CSRF 공격에 대한 방어 적용 필요
=> 해당 내용은 실제 구현시 위에 나열한 여러 방법들 중 더 쉬운 방법을 적용할 생각

### ✅ Refresh Token 및 블랙리스트 적용 필요
현재 Access Token만 사용하고 있는데, 이를 보완하기 위해 Refresh Token을 도입하고 블랙리스트 기능을 추가하는 것이 필요합니다.

#### 🔹 Refresh Token 도입 해야하는 이유
Access Token이 탈취되었을 경우, 토큰 만료 전까지 공격자가 무제한으로 API를 호출할 수 있음
Refresh Token을 사용하면, Access Token이 탈취되더라도 주기적으로 갱신하면서 보안성을 높일 수 있음

#### 🔹 블랙리스트 적용 해야하는이유
로그아웃 시 해당 Access Token을 즉시 무효화할 수 있도록 블랙리스트에 추가
이미 탈취된 토큰이 있다면, 추가적인 피해를 막을 수 있도록 블랙리스트를 활용


## 결론

현재 구현된 구글 OAuth2 + JWT 로그인 방식은 기본적인 인증 기능을 수행할 수 있지만, 보안적으로 보완해야 할 점이 많은걸 깨달았다.
원래 개선하려고 했던 Refresh Token 적용, 블랙리스트 기능 추가 이외에도
CSRF 공격에 대한 방어 강화의 필요성을 느꼈고 빠르게 개선 해야겠다.
Loading
Loading