티스토리 뷰

728x90
반응형


### 1️⃣ **기본 동작 방식** (예제 코드 기준)
```java
return ResponseEntity.ok(Map.of(
    "token", token,
    "email", userDetails.getUsername()
));
```
- **현재 구현**: 토큰을 응답 **Body**에 포함하여 전송
- **클라이언트의 책임**: 
  - 로그인 응답에서 토큰을 추출 → **로컬 저장소** (localStorage, sessionStorage, Cookie 등)에 저장
  - **매 요청 시** `Authorization: Bearer <token>` 헤더를 **수동 설정**

---

### 2️⃣ **자동 헤더 설정이 필요한가요?**
- **서버 측**: 토큰 생성만 담당
- **클라이언트 측**: 토큰 저장 및 헤더 설정을 **직접 구현**해야 함
  - **예시 (React에서의 구현)**:
    ```javascript
    // 로그인 성공 시
    const { token } = await loginAPI(email, password);
    localStorage.setItem('jwtToken', token);

    // API 호출 시
    axios.get('/api/data', {
      headers: {
        Authorization: `Bearer ${localStorage.getItem('jwtToken')}`
      }
    });
    ```

---

### 3️⃣ **대안: 쿠키를 통한 자동 전송**
토큰을 **HTTP-Only Cookie**로 설정하면 브라우저가 **자동으로 헤더에 포함**시킵니다.

```java
// 서버 측 코드 수정 (JWT를 쿠키로 전송)
ResponseCookie cookie = ResponseCookie.from("jwt", token)
    .httpOnly(true)
    .secure(true)
    .path("/")
    .maxAge(3600)
    .build();

return ResponseEntity.ok()
    .header(HttpHeaders.SET_COOKIE, cookie.toString())
    .body(Map.of("email", userDetails.getUsername()));
```

- **장점**:
  - XSS 공격에 대한 추가 보호 (HTTP-Only)
  - 클라이언트가 헤더를 수동 설정할 필요 없음
- **단점**:
  - CSRF 공격에 취약 → `SameSite=Strict` 설정으로 보완 필요

---

### 4️⃣ **보안 고려 사항**
1. **HTTPS 필수 사용**: 토큰 전송 시 암호화
2. **토큰 유효 시간**: 짧은 만료 시간 + Refresh Token 조합 추천
3. **저장 위치**:
   - **안전한 방법**: HTTP-Only Cookie > localStorage
   - **위험한 방법**: 일반 Cookie, sessionStorage

---

### 5️⃣ **실무에서의 선택 가이드**
| 상황 | 추천 방식 |
|------|-----------|
| **SPA (React, Vue)** | localStorage + 수동 헤더 설정 |
| **SSR (Thymeleaf, JSP)** | HTTP-Only Cookie + CSRF 토큰 |
| **모바일 앱** | Secure Storage + 헤더 설정 |

---


### 1️⃣ **SPA (React, Vue) → `localStorage` + 수동 헤더 설정**
#### 📍 **주요 특징**
- **클라이언트 측 라우팅**: API 호출이 빈번하고 직접 헤더 제어 필요
- **XSS 취약성**: 브라우저 환경에서 자바스크립트 코드 노출 위험

#### 💡 **추천 이유**
1. **편의성**:  
   - JWT를 `localStorage`에 저장 → 페이지 새로고침 시에도 토큰 유지
   - `axios` 인터셉터 등으로 `Authorization: Bearer <토큰>` 헤더 자동 주입 가능
   ```javascript
   // Axios 인터셉터 예시
   axios.interceptors.request.use(config => {
     config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
     return config;
   });
   ```

2. **SPA 아키텍처 적합성**:  
   - 서버 측 렌더링 없이 클라이언트에서 모든 라우팅 처리
   - CSR 환경에서 쿠키보다 헤더 설정이 직관적

#### ⚠️ **주의 사항**
- **XSS 공격**에 취약 → 토큰 탈취 가능성
- **Refresh Token 사용 금지** → 반드시 짧은 만료 시간(예: 1시간) 적용

---

### 2️⃣ **SSR (Thymeleaf, JSP) → `HTTP-Only Cookie` + CSRF 토큰**
#### 📍 **주요 특징**
- **서버 측 렌더링**: HTML을 서버에서 생성해 전달
- **전통적인 웹 보안 모델**: 브라우저의 쿠키 동작 방식에 최적화

#### 💡 **추천 이유**
1. **보안 강화**:  
   - `HTTP-Only Cookie` → 자바스크립트로 쿠키 접근 불가 (XSS 방지)
   - `Secure`, `SameSite=Strict` 옵션 추가 → 보안 레이어 강화

2. **CSRF 대응**:  
   - CSRF 토큰을 메타 태그나 응답 헤더에 포함
   - 모든 폼 제출 시 서버에서 CSRF 토큰 검증
   ```html
   <!-- Thymeleaf CSRF 토큰 자동 주입 -->
   <input type="hidden" name="_csrf" th:value="${_csrf.token}">
   ```

3. **세션 관리 용이**:  
   - 서버에서 직접 쿠키 관리 (예: 로그아웃 시 쿠키 만료 설정)

---

### 3️⃣ **모바일 앱 → `Secure Storage` + 헤더 설정**
#### 📍 **주요 특징**
- **네이티브 환경**: OS 레벨의 보안 저장소 활용 가능
- **앱 샌드박싱**: 다른 앱과 저장소 공유 불가

#### 💡 **추천 이유**
1. **안전한 저장**:  
   - Android: `EncryptedSharedPreferences`, iOS: **Keychain** 사용
   - 하드웨어 백업 키 저장 → 루팅/탈옥 기기에서도 보호

2. **헤더 제어 최적화**:  
   - 모바일 SDK에서 네트워크 요청 인터셉트 → 자동 헤더 주입
   ```kotlin
   // Android Retrofit 예시
   val okHttpClient = OkHttpClient.Builder()
       .addInterceptor { chain ->
           val request = chain.request().newBuilder()
               .addHeader("Authorization", "Bearer ${secureStorage.getToken()}")
               .build()
           chain.proceed(request)
       }
       .build()
   ```

3. **쿠키 불필요성**:  
   - 앱 ↔ API 서버 통신 시 쿠키보다 헤더 방식이 직관적
   - 크로스 도메인 이슈 없음

---

### 🔐 **종합 보안 전략**
| 환경       | 주요 위협       | 방어 메커니즘                   |
|------------|-----------------|---------------------------------|
| SPA        | XSS             | CSP 설정, 리프레시 토큰 회전    |
| SSR        | CSRF            | SameSite 쿠키, CSRF 토큰        |
| 모바일 앱  | 기기 탈취       | 생체 인증, 토큰 암호화 저장     |


728x90
반응형