자원 관리

 

1. 자원을 관리한다는 것은?

  • 자원(Resource):
    • 프로그램이 실행되는 동안 사용하는 CPU, 메모리, 파일 핸들, 네트워크 소켓 등을 말함.
    • 제한된 자원을 효율적으로 사용해야 비용 절감성능 최적화 가능.
  • 자원 관리의 중요성:
    • 자원의 불필요한 낭비를 줄이고, 프로그램의 성능과 안정성을 보장.
    • 자원 누수(Resource Leak)를 방지하여 시스템의 안정성을 유지.

2. 싱글턴 패턴 (Singleton Pattern)

  • 설명:
    • 디자인 패턴의 하나로, 클래스의 단 하나의 인스턴스만 생성되도록 보장.
    • 모든 클라이언트가 이 인스턴스를 공유하여 자원을 절약.
    • 주로 전역 상태 관리, 공통된 작업 수행에 사용.
  • 싱글턴 패턴의 장점:
    • 메모리 효율성: 인스턴스를 하나만 생성하여 메모리 낭비 방지.
    • 글로벌 접근성: 어디서든 같은 인스턴스를 참조 가능.
  • 싱글턴 패턴의 주의점:
    • 상태 관리: 싱글턴 객체가 상태를 가진다면, 다중 쓰레드 환경에서 데이터 무결성을 보장해야 함.
    • 의존성 주입 사용 권장: 테스트와 확장성을 고려해, 직접 싱글턴을 참조하기보다 DI(Dependency Injection)를 사용하는 것이 좋음.
  • 싱글턴 패턴 사용 예시:
    • 데이터베이스 연결 관리: 하나의 연결 객체로 다수의 요청 처리.
    • 로깅: 애플리케이션 전역에서 동일한 로깅 객체 사용.
    • 설정 관리: 환경 설정 값을 하나의 객체로 관리.
  • Kotlin의 싱글턴 구현:
  • object Singleton { fun showMessage() { println("Hello from Singleton!") } } fun main() { Singleton.showMessage() }

3. Companion Object

  • 설명:
    • Kotlin에서 클래스 내부에 포함된 특별한 객체.
    • 클래스의 인스턴스 없이 멤버에 접근 가능.
    • 자바의 static 키워드와 비슷한 역할.
  • 사용 목적:
    • 클래스 레벨에서 공통적으로 사용되는 함수나 변수 관리.
    • 팩토리 메서드 구현 등.
  • 예제:
  • data class UserDto(val username: String, val password: String) { companion object { fun encodePassword(password: String): String { return password.reversed() // 단순한 비밀번호 암호화 } } } fun main() { val encodedPassword = UserDto.encodePassword("12345") println("Encoded Password: $encodedPassword") }

4. 지연 초기화 (Lazy Initialization)

  • 설명:
    • 필요한 시점에 객체를 초기화하여 메모리와 성능을 최적화하는 기법.
    • Kotlin에서는 lazy 키워드를 사용하여 구현.
  • 장점:
    • 성능 개선: 객체가 실제로 필요할 때만 초기화하여 초기 로딩 속도 개선.
    • 메모리 효율성: 불필요한 메모리 사용 방지.
  • 예제:
  • class Image(val imageFile: String) { val thumbnail: String by lazy { println("Generating thumbnail...") "Thumbnail for $imageFile" } } fun main() { val image = Image("photo.jpg") println("Image loaded") println(image.thumbnail) // 이 시점에서 thumbnail 초기화 }

5. 인라인 함수 (Inline Function)

  • 설명:
    • 함수 호출 시, 함수 본문을 호출 지점에 직접 삽입하는 방식.
    • 람다나 고차 함수에서 객체 생성을 피하기 위해 메모리 효율적으로 사용.
  • 장점:
    • 함수 호출 오버헤드 감소.
    • 고차 함수에서 불필요한 객체 생성 제거.
  • 단점:
    • 함수 크기가 클 경우, 인라인 사용 시 컴파일된 바이트코드가 커져 프로그램 크기 증가.
  • 예제:
  • inline fun compute(a: Int, action: () -> Unit): Int { action() return a * 2 } fun main() { val result = compute(10) { println("Action executed!") } println("Result: $result") }

6. 추가 학습이 필요한 주제

  1. 자원 관리 기법:
    • try-with-resources(Java)나 use(Kotlin) 같은 자원 자동 해제 기법.
    • 파일, 네트워크 소켓, 데이터베이스 연결의 효율적인 관리.
  2. 멀티쓰레드 환경에서 자원 관리:
    • 동기화(Synchronization)와 Lock, Semaphore 등의 동시성 제어 기법.
  3. 객체 풀(Object Pool):
    • 자원을 효율적으로 재사용하기 위해 객체를 풀(Pool)로 관리하는 기법.
  4. Dependency Injection (DI):
    • 싱글턴 객체와 함께 사용하면 코드의 확장성과 유지보수성을 높일 수 있음.
  5. 성능 측정 도구:
    • 자원 사용량을 분석하고, 최적화 가능성을 식별하는 도구(Ex: Android Profiler, JConsole).

7. 정리

기술 설명 장점
싱글턴 패턴 하나의 인스턴스만 생성하여 전역적으로 접근 가능. 메모리 절약, 전역 상태 관리 용이.
Companion Object 클래스의 정적 멤버를 관리하는 특별한 객체. 코드 간결화, 정적 함수/변수 관리.
지연 초기화 객체를 필요할 때 초기화하여 메모리와 성능 최적화. 초기 로딩 속도 개선, 불필요한 메모리 사용 방지.
인라인 함수 호출 시 함수 본문을 호출 지점에 삽입. 함수 호출 오버헤드 제거, 고차 함수 효율화.

결론

자원 관리는 프로그램의 효율성안정성을 높이는 핵심 요소입니다. 싱글턴, Companion Object, Lazy Initialization, Inline Function과 같은 기술들은 자원의 효율적 사용뿐 아니라 코드의 가독성과 유지보수성을 높이는 데 기여합니다. 추가 학습을 통해 동시성과 멀티쓰레드 환경에서 자원을 관리하는 방법을 익히면 더욱 견고한 시스템을 구축할 수 있습니다. 

'CS ( Computer Science ) > 운영 체제 (Operating Systems)' 카테고리의 다른 글

[OS] 코루틴  (0) 2025.02.19
[OS] 비동기 프로그래밍  (0) 2025.02.18
[OS] 쓰레드  (0) 2025.02.17
[OS] 프로세스와 쓰레드  (0) 2024.11.24

코루틴 (Coroutine)

1. 코루틴이란?

  • 코루틴은 비동기 프로그래밍을 쉽게 구현할 수 있는 기술.
  • Light-Weight Thread로 불리며, 쓰레드보다 가볍게 동시성을 구현 가능.
  • 주요 목표:
    • 하드웨어 자원(CPU, 메모리) 효율적인 사용.
    • 안정적이고, 직관적인 비동기 코드 작성.
    • 운영체제의 깊은 이해 없이도 협동적 작업 처리 가능.

2. 코루틴의 주요 특징

  1. 비동기 작업 최적화:
    • 대기 시간이 발생하는 작업(예: 네트워크 요청, 파일 입출력)을 효율적으로 처리.
  2. 경량성:
    • 하나의 쓰레드에서 수백만 개의 코루틴 실행 가능.
    • 코루틴은 컨텍스트 스위칭(Context Switching) 없이 동일 쓰레드에서 작업을 교체 실행.
  3. 동시성 프로그래밍:
    • 복잡한 동시성을 직관적이고 간결하게 구현.
  4. Suspendable Functions (일시중단 함수):
    • suspend 키워드를 사용하여 일시적으로 작업을 중단하고, 다른 작업을 수행.
  5. 사용자 정의 스위칭:
    • 작업 교체 시점을 개발자가 직접 지정 가능.

3. 코루틴 빌더

코루틴 실행을 시작하는 빌더를 사용하여 다양한 방식으로 작업 수행.

  1. launch:
    • 결과값을 반환하지 않는 코루틴 빌더.
    • Job 객체를 반환하여 코루틴을 제어 가능.
    val job = GlobalScope.launch {
        delay(1000) // 1초 대기
        println("코루틴 작업 완료!")
    }
    
  2. async:
    • 결과값을 반환하는 코루틴 빌더.
    • 반환값을 Deferred 객체로 받고, await()을 호출해 결과를 반환.
    val deferred = GlobalScope.async {
        delay(1000) // 1초 대기
        "코루틴 작업 완료!"
    }
    println(deferred.await()) // 결과 출력
    
  3. runBlocking:
    • 현재 쓰레드를 차단하여 코루틴 작업이 완료될 때까지 대기.
    • 주로 테스트 코드나 코루틴을 호출하는 비코루틴 환경에서 사용.
    runBlocking {
        launch {
            delay(1000)
            println("비동기 작업 완료!")
        }
        println("runBlocking 종료")
    }
    

4. 코루틴 스코프

  1. GlobalScope:
    • 앱 전체에 걸쳐 살아있는 코루틴을 생성.
    • 앱이 실행되는 동안 코루틴 유지.
    GlobalScope.launch {
        delay(1000)
        println("GlobalScope 코루틴")
    }
    
  2. CoroutineScope:
    • 필요한 시점에 생성하고, 사용 후 정리 가능.
    • 자식 코루틴의 생명 주기를 관리.
    val scope = CoroutineScope(Dispatchers.Default)
    scope.launch {
        delay(1000)
        println("CoroutineScope 코루틴")
    }
    

5. 디스패처 (Dispatcher)

코루틴이 실행될 쓰레드를 결정.

  1. Dispatchers.Main:
    • UI와 상호작용하거나 사용자 이벤트 처리에 적합.
    • Android 개발에서 많이 사용.
  2. Dispatchers.IO:
    • 네트워크 통신, 파일 입출력 등 I/O 작업에 최적화.
  3. Dispatchers.Default:
    • CPU 집약적인 작업(데이터 처리, 복잡한 계산 등)에 적합.
  4. Dispatchers.Unconfined:
    • 제한되지 않은 디스패처. 실행 환경에 따라 동적으로 실행.

6. 쓰레드와 코루틴의 차이

  쓰레드(Thread)  코루틴(Coroutine)
경량성 무겁고, 수십 개의 쓰레드 생성 시 성능 저하. 매우 가벼움. 하나의 쓰레드에서 수백만 개의 코루틴 생성 가능.
메모리 사용 각 쓰레드는 독립적인 스택 메모리를 가짐. 코루틴은 힙 메모리를 공유하며 필요 시만 메모리 사용.
교체 방식 운영체제가 컨텍스트 스위칭을 관리. 개발자가 소스 코드에서 작업 교체 시점을 명시적으로 설정.
사용 용도 주로 동시 작업을 처리하는 저수준 구현. 동시성과 비동기 작업을 처리하는 고수준 프로그래밍.
성능 다수의 쓰레드를 실행하면 메모리와 CPU 오버헤드 증가. 효율적. CPU 자원을 최소화하며 동시성 작업 처리.
오버헤드 쓰레드 생성과 종료, 컨텍스트 스위칭 비용 큼. 오버헤드가 적고, 효율적으로 스케줄링.

7. 예시 코드

1) 간단한 코루틴 실행

fun main() = runBlocking {
    launch {
        delay(1000)
        println("첫 번째 코루틴")
    }
    println("메인 함수")
}

2) 여러 코루틴 실행

fun main() = runBlocking {
    val job1 = async {
        delay(2000)
        "작업 1 완료"
    }
    val job2 = async {
        delay(1000)
        "작업 2 완료"
    }
    println(job1.await())
    println(job2.await())
}

8. 추가 학습이 필요한 주제

  1. 코루틴 컨텍스트와 스코프:
    • CoroutineScope와 SupervisorJob의 역할 및 사용 방법.
  2. 에러 처리:
    • 코루틴 내 예외 처리 방법 (try-catch, supervisorScope).
  3. 코루틴 채널 (Channels):
    • 코루틴 간 데이터를 주고받는 통신 방식.
  4. Flow:
    • Kotlin의 비동기 데이터 스트림 처리 도구.
  5. 테스트:
    • runBlockingTest와 같은 코루틴 기반 테스트 도구 활용.

9. 결론

코루틴은 효율적인 비동기 프로그래밍동시성 처리를 위한 강력한 도구입니다. Google에서 Android 개발에 적극 권장하며, 현대적인 비동기 처리를 위해 필수적인 기술로 자리 잡고 있습니다. 학습을 통해 코루틴의 다양한 사용법과 최적화 기법을 익히는 것이 중요합니다. 😊

 

'CS ( Computer Science ) > 운영 체제 (Operating Systems)' 카테고리의 다른 글

[OS] 자원 관리  (0) 2025.02.20
[OS] 비동기 프로그래밍  (0) 2025.02.18
[OS] 쓰레드  (0) 2025.02.17
[OS] 프로세스와 쓰레드  (0) 2024.11.24

비동기 프로그래밍

 

1. 비동기 프로그래밍이란?

  • 비동기 프로그래밍(Asynchronous Programming):
    • 작업이 완료될 때까지 기다리지 않고, 다른 작업을 수행할 수 있도록 처리하는 방식.
    • **시간이 오래 걸리는 작업(예: 파일 다운로드, 네트워크 요청)**이 진행되는 동안 앱이나 프로그램이 멈추지 않고 다른 작업을 계속 실행.
  • 동기적 프로그래밍과의 차이점:
    • **동기적 프로그래밍(Synchronous Programming)**은 작업을 순차적으로 실행하며, 이전 작업이 완료될 때까지 기다려야 다음 작업을 시작.
    • 반면, 비동기 프로그래밍은 작업의 완료 여부와 관계없이 다른 작업을 병렬적으로 실행.

2. 비동기 프로그래밍의 필요성

  • 효율적인 자원 사용:
    • I/O 작업(네트워크, 파일 시스템 등)에서 작업 완료를 기다리지 않고, 유휴 상태인 CPU를 활용 가능.
  • 사용자 경험 개선:
    • 긴 작업이 진행되는 동안 애플리케이션이 멈추거나 응답하지 않는 문제를 방지.
  • 동시성 지원:
    • 여러 작업이 동시에 실행되도록 설계하여 멀티태스킹 구현 가능.

3. 동기와 비동기의 차이

  동기 프로그래밍 비동기 프로그래밍
작업 방식 작업이 순차적으로 실행됨. 작업이 병렬적으로 실행 가능.
작업 흐름 이전 작업이 끝나야 다음 작업이 실행됨. 작업 완료 여부와 관계없이 다른 작업을 실행 가능.
장점 구현이 단순하고 이해하기 쉬움. 응답성이 좋고, 자원 활용이 효율적.
단점 시간이 오래 걸리는 작업이 전체 흐름을 막을 수 있음. 구현 복잡도가 높아질 수 있음(콜백 지옥, 오류 처리 등).
사용 사례 계산, 순차적 처리 로직. 네트워크 요청, 대규모 데이터 처리, 사용자 인터페이스 응답.

4. 비동기 프로그래밍의 예시

예시 1: 동기 프로그래밍

// 동기 방식: 모든 작업이 순서대로 실행
System.out.println("1. 5GB 영상 다운로드 중...");
Thread.sleep(5000); // 5초 동안 대기
System.out.println("2. 메일 전송 중...");
Thread.sleep(2000); // 2초 동안 대기
System.out.println("3. 알림 전송 완료!");
  • 출력:
    1. 5GB 영상 다운로드 중...
    2. 메일 전송 중...
    3. 알림 전송 완료!
    
  • 동기 방식에서는 앞선 작업이 끝날 때까지 다음 작업이 대기.

예시 2: 비동기 프로그래밍

// 비동기 방식: 각 작업이 병렬적으로 실행
System.out.println("1. 5GB 영상 다운로드 요청!");
CompletableFuture.runAsync(() -> {
    try {
        Thread.sleep(5000); // 5초 동안 대기 (다운로드)
        System.out.println("2. 영상 다운로드 완료!");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
System.out.println("3. 메일 전송 중...");
Thread.sleep(2000); // 2초 동안 대기
System.out.println("4. 알림 전송 완료!");
  • 출력:
    1. 5GB 영상 다운로드 요청!
    3. 메일 전송 중...
    4. 알림 전송 완료!
    2. 영상 다운로드 완료!
    
  • 작업이 병렬로 실행되어 응답성이 향상.

5. 비동기 프로그래밍을 활용한 생활 속 사례

  1. 웹 브라우저:
    • 웹 페이지를 로드하는 동안 이미지나 추가 데이터를 백그라운드에서 다운로드.
  2. 모바일 앱:
    • 사용자가 앱을 탐색하는 동안, 새로운 메시지나 알림을 비동기로 가져옴.
  3. 게임:
    • 게임 플레이 중, 백그라운드에서 리소스 로드 또는 업데이트.
  4. 파일 업로드:
    • 사용자가 다른 작업을 계속할 수 있도록 대용량 파일을 비동기적으로 업로드.

6. 비동기 프로그래밍의 장점과 단점

장점 단점
사용자 경험(UX) 개선: 응답성 향상 및 애플리케이션 정지 방지. 복잡한 구현: 콜백 지옥, 오류 처리 어려움.
리소스 효율성: CPU와 메모리 자원을 효과적으로 사용 가능. 디버깅 및 유지보수 어려움.
대규모 작업 처리: I/O 작업이나 네트워크 요청을 효율적으로 처리 가능. 동기 작업보다 설계 및 코드 작성에 더 많은 시간 필요.

7. 비동기 프로그래밍 구현 방식

  1. 콜백 (Callback):
    • 특정 작업 완료 후 실행할 동작을 미리 정의.
    • 단점: "콜백 지옥"으로 인해 코드 가독성이 떨어질 수 있음.
  2. Promise/Future:
    • 작업 완료 후 반환값을 처리하는 객체.
    • 단점: 여러 Promise를 연결하면 복잡해질 수 있음.
  3. Async/Await:
    • 비동기 작업을 동기 코드처럼 읽기 쉽게 작성.
    • 단점: 예외 처리를 적절히 구현해야 함.

8. 비동기 프로그래밍이 적합한 상황

  • 네트워크 요청:
    • 외부 API 호출, 데이터베이스 쿼리 등.
  • 대용량 작업 처리:
    • 파일 입출력, 대규모 데이터 처리.
  • 사용자 인터페이스 응답성 개선:
    • UI 작업 중 애플리케이션 멈춤 방지.

결론

비동기 프로그래밍은 애플리케이션의 성능과 응답성을 높이는 데 매우 중요한 프로그래밍 기법입니다. 동기적 흐름에서 발생할 수 있는 병목 현상을 해결하고, 사용자가 더 나은 경험을 할 수 있도록 돕습니다. 하지만 구현의 복잡성이 증가할 수 있으므로, **상황에 맞는 패턴(콜백, Promise, Async/Await)**을 사용해 관리해야 합니다. 

'CS ( Computer Science ) > 운영 체제 (Operating Systems)' 카테고리의 다른 글

[OS] 자원 관리  (0) 2025.02.20
[OS] 코루틴  (0) 2025.02.19
[OS] 쓰레드  (0) 2025.02.17
[OS] 프로세스와 쓰레드  (0) 2024.11.24

쓰레드(Thread)

 

1. 쓰레드란?

  • **쓰레드(Thread)**는 프로그램 내에서 작업을 실행하는 최소 실행 단위.
  • 하나의 프로그램(프로세스)은 기본적으로 하나의 메인 쓰레드(fun main(0 메인함수)에서 실행되며, 이를 통해 모든 작업이 처리됨.
  • 추가 쓰레드를 생성하면 여러 작업을 동시에 처리할 수 있어 **동시성(Concurrency)**을 구현할 수 있음.

2. 쓰레드의 기본 개념

  1. 프로세스(Process):
    • 실행 중인 프로그램으로, 운영체제(OS)에서 메모리와 CPU를 할당받아 독립적으로 실행됨.
    • 각 프로세스는 독립된 메모리 공간(코드, 데이터, 힙, 스택)을 가짐.
  2. 쓰레드(Thread):
    • 프로세스 내에서 실행되는 작은 작업 단위.
    • 스택 메모리를 독립적으로 사용하지만, 힙 메모리(공유 자원)는 다른 쓰레드와 공유.
  3. 쓰레드의 메모리 구조:
    • 스택(Stack): 쓰레드마다 독립된 실행 컨텍스트를 저장.
    • 힙(Heap): 모든 쓰레드가 공유하는 메모리 공간.
    • 코드/데이터 영역: 프로그램의 실행 코드와 상수 저장.

3. 쓰레드의 특징

특징 설명
병렬 실행 여러 쓰레드가 동시에 실행 가능하여, 작업 처리 속도와 효율성을 높임.
스택 독립성 각 쓰레드는 독립된 스택 메모리를 가지므로, 실행 컨텍스트를 개별적으로 관리.
공유 자원 관리 쓰레드는 힙 메모리를 공유하므로, 공유 자원 접근 시 동기화(Synchronization)가 필요.
컨텍스트 스위칭 CPU가 여러 쓰레드 사이를 전환하며 실행. 전환 비용(오버헤드)이 발생.

4. 쓰레드의 필요성

  1. 동시성(Concurrency) 구현:
    • 하나의 작업을 여러 단위로 나누어 동시에 처리.
    • 예: UI 처리 중 파일 다운로드.
  2. 대기 시간 최소화:
    • 시간이 오래 걸리는 작업(네트워크 요청, 파일 입출력 등) 동안 다른 작업을 중단 없이 실행.
  3. 프로그램 효율성 향상:
    • 멀티코어 CPU에서 여러 쓰레드를 실행하면 자원을 효율적으로 활용 가능.

5. 쓰레드를 사용하는 주요 사례

  1. 게임:
    • 캐릭터의 움직임, 효과음, 사용자 입력 처리를 동시에 수행.
  2. 멀티미디어 애플리케이션:
    • 비디오 재생 중 오디오 출력과 자막 표시를 병렬로 실행.
  3. 네트워크 서버:
    • 여러 클라이언트 요청을 동시에 처리.
  4. 경쟁 환경:
    • 경마 프로그램처럼 여러 개체가 동시에 움직이고 결과를 경쟁.

6. 쓰레드의 구현

1) Kotlin에서 쓰레드 생성

fun main() {
    thread(start = true) {
        println("Thread 1: 실행 중")
    }

    thread(start = true) {
        println("Thread 2: 실행 중")
    }
}

2) 쓰레드 경쟁 예제

fun main() {
    thread(start = true) {
        for (i in 1..10) {
            println("Thread 1: $i")
            Thread.sleep(500) // 0.5초 대기
        }
    }

    thread(start = true) {
        for (i in 50..60) {
            println("Thread 2: $i")
            Thread.sleep(500) // 0.5초 대기
        }
    }
}

3) 쓰레드 동기화 문제 해결

  • 여러 쓰레드가 공유 자원을 동시에 접근할 경우, 데이터 충돌 문제 발생 가능.
  • 해결 방법: synchronized 키워드로 동기화 처리.
val lock = Any()
var sharedResource = 0

fun main() {
    thread(start = true) {
        synchronized(lock) {
            for (i in 1..5) {
                sharedResource++
                println("Thread 1: $sharedResource")
            }
        }
    }

    thread(start = true) {
        synchronized(lock) {
            for (i in 1..5) {
                sharedResource++
                println("Thread 2: $sharedResource")
            }
        }
    }
}

7. 쓰레드의 장단점

장점 단점
병렬 처리로 작업 처리 속도가 향상. 컨텍스트 스위칭으로 인한 성능 오버헤드 발생.
대기 시간이 긴 작업(I/O 작업)을 처리하면서 다른 작업을 실행 가능. 동기화 문제로 인해 데이터 충돌 및 데드락 발생 가능.
멀티코어 CPU 환경에서 성능 효율성을 극대화. 쓰레드 수가 많아지면 메모리 사용량 증가.

8. 추가 학습이 필요한 주제

  1. 코루틴 (Coroutine):
    • 쓰레드보다 가볍고 효율적인 비동기 프로그래밍 방식.
    • Kotlin의 대표적인 비동기 처리 도구.
  2. 동기화(Synchronization):
    • 공유 자원 접근 시 발생하는 문제를 해결하기 위한 기술.
    • Mutex, Semaphore, Volatile 등.
  3. 컨텍스트 스위칭(Context Switching):
    • 쓰레드 간 전환 시 발생하는 비용과 효율성 최적화 방법.
  4. 멀티쓰레드 디버깅:
    • 복잡한 쓰레드 환경에서의 오류 해결 방법.
  5. Reactive Programming:
    • 비동기 데이터를 처리하는 프로그래밍 패러다임. (Ex: RxJava, Project Reactor)

9. 결론

쓰레드는 동시성과 병렬성을 구현하기 위한 강력한 도구입니다. 하지만 쓰레드를 잘못 설계하면 데이터 충돌, 데드락 등 복잡한 문제가 발생할 수 있습니다. 쓰레드의 한계를 보완하기 위해 코루틴 같은 최신 기술도 함께 학습하면 더욱 효율적인 프로그래밍이 가능합니다. 

'CS ( Computer Science ) > 운영 체제 (Operating Systems)' 카테고리의 다른 글

[OS] 자원 관리  (0) 2025.02.20
[OS] 코루틴  (0) 2025.02.19
[OS] 비동기 프로그래밍  (0) 2025.02.18
[OS] 프로세스와 쓰레드  (0) 2024.11.24

현대 백엔드 로드맵

📌 아래는 "클라이언트 → 프록시 서버 → 웹 서버 → WAS(웹 애플리케이션 서버) → RDS" 구조에서 각 파트의 역할과 특이점을 포함한 표입니다.


  하는 일 주요 역할 특이점
클라이언트 - HTTP 요청 생성 (예: HTML, API 호출)
- 브라우저, 모바일 앱, 또는 다른 서비스가 요청 전송
- 서버의 응답을 사용자에게 표시
- 데이터를 요청하는 주체.
- 사용자 인터페이스 제공.
- 서버로 요청하고 결과를 화면에 출력.
- 브라우저는 쿠키를 저장하고 필요 시 요청 시 함께 전송.
- 요청과 응답은 주로 HTTP/HTTPS 프로토콜을 통해 이루어짐.
프록시 서버 - 클라이언트 요청을 분석 및 필터링
- 로드 밸런싱: 요청을 여러 웹 서버로 분배
- 자주 요청되는 데이터 캐싱
- 보안 강화 (예: SSL 처리)
- 중개 역할: 클라이언트와 웹 서버 사이에서 요청 처리.
- 캐싱: 정적 리소스를 저장하여 빠른 응답.
- 트래픽 관리 및 부하 분산.
- 리버스 프록시로 동작, 클라이언트 IP를 숨김.
- HTTPS 요청을 웹 서버로 전달하기 전에 해독 가능 (SSL Termination).
웹 서버 - HTTP 요청 처리
- 정적 리소스(HTML, CSS, JS 등) 제공
- 동적 요청을 WAS로 전달
- 정적 리소스 제공.
- 요청이 정적인지 동적인지 구분.
- 동적 요청을 WAS로 전달하여 처리.
- 정적 리소스를 캐싱하여 빠르게 제공.
- Servlet 실행 기능 없음.
- 주로 Nginx, Apache가 사용됨.
필터 (WAS 내부) - HTTP 요청/응답 전처리 및 후처리
- JWT 등 인증/인가 처리
- 요청 로깅 및 데이터 변환
- 요청 흐름 제어: 요청을 허용하거나 차단.
- 공통 로직 처리: 여러 요청에서 반복되는 작업 처리.
- 보안: JWT 검증 및 세션 관리.
- Servlet 실행 전후로 동작.
- JWT 기반 인증 처리.
- 요청 데이터 로깅 및 변환.
- 응답 데이터에 헤더 추가 가능.
WAS (웹 애플리케이션 서버) - 클라이언트 요청 처리(예: 비즈니스 로직 실행)
- 데이터베이스와 통신하여 CRUD 작업 수행
- 요청 결과를 JSON, HTML 등으로 생성 및 반환
- 요청 처리: 클라이언트 요청에 따라 데이터를 생성, 수정, 조회, 삭제.
- 비즈니스 로직 구현.- RDS와 통신하여 필요한 데이터 제공.
- Servlet 컨테이너, 세션 포함.
- 쿠키/세션 관리 (예: HttpSession).
- JSP를 Servlet으로 변환하여 실행.
Servlet 컨테이너 - 클라이언트의 HTTP 요청을 처리하고 동적 콘텐츠 생성
- Servlet의 생명주기 관리 (init, service, destroy)
- 클라이언트 요청(예: GET, POST)을 받아 처리.
- 동적 웹 페이지 생성 (JSP 포함).
- WAS의 핵심 구성 요소.
- 스레드 풀을 사용하여 동시 요청 처리.
- 세션 상태 관리 기능 (HttpSession) 포함.
RDS (관계형 데이터베이스) - 데이터를 저장, 관리 및 조회
- SQL 쿼리 처리
- 요청 데이터를 반환하거나 저장
- 영구 데이터 저장.- SQL을 통해 데이터를 읽고 쓰는 작업 수행.
- 관계형 데이터베이스로 데이터 무결성 보장.
- 주로 MySQL, PostgreSQL, Oracle DB 사용.
- 데이터 모델은 테이블 형식으로 설계.
- WAS와 연결하여 데이터 CRUD 수행.

특이점 정리

  • 클라이언트:
    • 브라우저는 쿠키를 저장하고, 서버 요청 시 쿠키를 전송해 상태 관리.
    • HTTP/HTTPS 프로토콜로 요청과 응답을 주고받음.
  • 프록시 서버:
    • HTTPS 요청의 해독(SSL Termination) 역할 가능.
    • 리버스 프록시로 트래픽 부하 분산 및 클라이언트 IP 숨김.
  • 웹 서버:
    • 정적 리소스 캐싱 및 제공.
    • 동적 요청은 WAS로 전달.
  • WAS:
    • Servlet 컨테이너 포함.
    • 쿠키와 세션 관리 (HttpSession)을 통해 상태 유지.
    • 비즈니스 로직 처리 및 데이터베이스와 통신.
    • 랜더링
 

[Net] Rendering

Rendering📌 웹 애플리케이션에서 콘텐츠를 화면에 표시하는 과정을 의미합니다. 이 과정은 주로 웹 브라우저에서 일어나며, HTML, CSS, JavaScript 코드가 결합되어 최종적으로 사용자가 볼 수 있는 페

kyunghun0515.tistory.com

  • Servlet 컨테이너:
    • 동적 요청 처리의 핵심.
    • 세션과 쿠키 관리 기능 포함 (HttpSession 및 Cookie).
    • WAS 내부에서 동작하며 멀티스레드 요청 처리.
 

[Net] Servlet

Servlet📌 Servlet은 자바(Java) 언어로 작성된 서버 측 프로그램으로, 주로 웹 애플리케이션에서 클라이언트의 요청을 처리하고 동적으로 응답을 생성하는 데 사용됩니다. JAVA에서 Sevlet은 HttpServlet

kyunghun0515.tistory.com

 

  • RDS:
    • SQL 기반의 데이터 저장소.
    • 데이터의 영속성 보장 및 CRUD 처리.

쿠키와 세션 관리

  • 쿠키:
    • 클라이언트[ 웹 브라우저 ]에 저장되는 작은 데이터.
    • 클라이언트 상태를 서버에 전달하는 데 사용. (로그인 상태 유지등에 활용)
    • 보안에 취약하여 민감한 정보를 저장하지 않아야한다
    • 사용자 임의 수정이 가능하다..
    • 쿠키도 만료시간이 지정 가능하다. (만료 기간이 설정된 쿠키는 영속적 쿠키라 부른다.)
    • 만료 시간이 설정 안되면, 브라우저 종료시 삭제되는 세션 쿠키가 동작한다.
    • 예: 로그인 토큰, 사용자 설정.
 

[Net] Cookie

Cookie📌 사용자의 웹 브라우저에 저장되는 정보로 사용자의 상태 혹은 세션을 유지하거나 사용자 경험을 개선하기 위해 사용된다. 사용자 정보나 세션 데이터를 클라이언트(브라우저)에 저장하

kyunghun0515.tistory.com

 

  • 세션:
    • 서버에 저장되는 클라이언트 상태 정보.
    • 클라이언트를 고유하게 식별하기 위해 쿠키나 URL 파라미터로 세션 ID를 사용.
    • 서버에서 중요한 정보를 보관하며 로그인을 유지하는 형태로 쓰인다. Session ID를 탈취당해도 별 정보가 없어 상대적으로 안전하다.
    • 만료 시간을 설정해서 탈취 문제를 최소화 가능하다.
      • HttpSession은 최근 Session을 요청한 시간을 기준으로 만료 시간을 유지한다.
    • 브라우저가 아닌 서버에서 관리하는 만큼, 브라우저가 꺼져도 유지된다.
    • 예: 로그인 상태 유지, 장바구니 정보.

필터

  1. 동작 위치:
    • WAS 내부에서 Servlet 실행 전후에 동작.
    • 클라이언트 요청과 WAS의 비즈니스 로직 사이에서 요청/응답을 전처리/후처리.
  2. 역할:
    • JWT 인증/인가: Authorization 헤더의 JWT를 검증하여 요청 허용 여부 결정.
    • 요청 로깅: 요청 URL, 헤더, 본문 등을 로깅하여 분석 및 디버깅.
    • 응답 로깅: 클라이언트로 전송되는 응답 데이터를 로깅.
    • 공통 로직 처리: 요청 데이터 변환, 헤더 추가, 응답 데이터 가공.
  3. 특이점:
    • 필터는 모든 요청에 대해 공통적으로 동작할 수 있도록 설계되므로, 반복적인 로직을 중앙에서 처리 가능.
    • Spring Security와 연동하여 인증/인가 로직을 필터로 구현 가능.
 

[Net] Filter

공통 관심 사항(cross-cutting concerns)📌 애플리케이션 전반에 걸쳐 여러 모듈이나 레이어에서 공통적으로 필요하지만, 특정 비즈니스 로직과 직접적으로 관련이 없는 기능을 의미합니다. 이러한

kyunghun0515.tistory.com

 

 

현대 데이터 통신 형태

"클라이언트 → 프록시 서버 → 웹 서버 → WAS(웹 애플리케이션 서버)"에서 클라이언트->서버에서 데이터 요청, 전송시에 사용되는 기술의 이야기

 

아래는 RESTful API, JWT(토큰), HTTP 메서드, HTTP 헤더에 관한 설명과 이들 간의 연관성을 정리한 표

 

  개념 설명 주요 역할 연관성
RESTful API - 자원을 URI로 식별하고, HTTP 메서드로 자원을 조작하는 아키텍처 스타일.
- Stateless 방식으로 동작.
- 클라이언트, 서버 간 데이터 교환 표준.
- CRUD 작업을 HTTP 메서드로 정의.
- 요청과 응답을 HTTP 프로토콜로 전달.
- HTTP 메서드와 헤더를 사용해 자원 조작.
- 헤더를 통해 JWT 인증 및 데이터 형식을 관리.
- Stateless 특성을 JWT와 결합.
JWT (JSON Web Token) - Stateless 인증 방식으로 클라이언트의 인증 정보를 서버가 아닌 클라이언트에 저장.
- Header, Payload, Signature로 구성.
- 클라이언트 인증 및 권한 부여.
- 서버가 상태를 저장하지 않아 확장성 높음.
- RESTful API의 Stateless 특성을 지원.
- HTTP 헤더(Authorization)에 포함되어 전달.- RESTful API 요청에서 인증 및 권한 부여에 사용.
- 데이터 보호를 위해 HTTP Secure(HTTPS)와 함께 사용 권장.
HTTP 메서드 - HTTP 프로토콜에서 자원을 조작하는 메서드 (GET, POST, PUT, DELETE, PATCH 등). - CRUD 작업 정의:
- GET: 자원 조회.
- POST: 자원 생성.
- PUT: 자원 수정.
- DELETE: 자원 삭제.
- RESTful API의 자원 조작 방식을 정의.
- 헤더와 결합해 데이터 형식 지정 (예: Content-Type).
- 인증된 요청에서만 동작하도록 JWT 검증과 함께 사용.
HTTP 헤더 - HTTP 요청 및 응답의 메타데이터를 제공.
- 데이터 형식, 인증 정보, 캐싱 정책 등을 포함.
- 인증 정보 전달 (예: Authorization: Bearer <JWT>).
- 데이터 형식 정의 (Content-Type, Accept).
- RESTful API와 JWT 인증에 사용.
- Authorization 헤더로 토큰 전달.
- 요청과 응답 데이터의 형식을 제어하여 클라이언트-서버 간 호환성 보장.

연관성 및 흐름 요약

RESTful API:

  • HTTP 메서드로 자원 조작을 정의.
  • HTTP 헤더를 통해 인증(JWT) 및 데이터 형식(Content-Type)을 관리.
  • Stateless 특성 유지.

 Token

  • 인증/인가 과정에서 사용되며 사용자 또는 시스템의 신원과 권한을 증명하고 요청의 유효성을 검증하는 데 사용되는 디지털 문자열
  • Session과는 다르게 Client가 데이터(Token)를 저장하고 있다.
    • Stateless를 기반으로 하여 확장성이 뛰어나다.
  • Mobile과 같이 Cookie를 사용할 수 없는 경우에도 사용할 수 있다.
  • Payload는 암호화되지 않는다.
  • 만료 시간으로 Token 탈취를 대비한다.
 

[Net] Token & JWT

Token📌 Web Application이나 API에서 인증(Authentication)과 인가(Authorization) 과정에서 사용되며 사용자 또는 시스템의 신원과 권한을 증명하고 요청의 유효성을 검증하는 데 사용되는 디지털 문자열이

kyunghun0515.tistory.com

 

JWT

  • 인증에 필요한 정보들을 암호화시킨 JSON 형태의 Token
  • Signature를 통해 Token을 안전하게 관리한다.
  • JWT의 목적은 정보 보호가 아닌, 위조 방지에 있다.
  • HTTP 헤더(Authorization)로 전달.
  • RESTful API 요청에서 클라이언트 인증과 권한 부여에 사용.
  • Stateless 특성을 유지하여 서버 상태 관리의 부담을 줄임.

HTTP 메서드:

  • RESTful API의 자원 조작 방식을 정의.
  • 요청 시 헤더(Content-Type, Authorization)와 결합하여 요청 처리.

HTTP 헤더:

  • HTTP 요청/응답의 메타데이터 제공.
  • JWT를 Authorization 헤더에 포함해 인증 정보를 전달.
  • 데이터 형식(Content-Type, Accept)을 설정해 클라이언트-서버 간 데이터 교환 정의.

예시: RESTful API 요청과 흐름

  1. 클라이언트가 RESTful API 요청:
  2. POST /api/products HTTP/1.1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json
  3. JWT 인증:
    • 서버는 Authorization 헤더에서 JWT를 추출.
    • JWT를 검증하여 클라이언트의 권한 확인.
  4. HTTP 메서드:
    • 요청이 POST 메서드임을 확인.
    • 자원 생성 로직 실행.
  5. 응답:
  6. HTTP/1.1 201 Created Content-Type: application/json { "id": 123, "name": "New Product", "price": 50.0 }

결론

  • RESTful API는 HTTP 메서드와 헤더를 통해 자원을 조작하며, Stateless 특성을 유지.
  • JWT는 인증과 권한 부여를 처리하며, HTTP 헤더(Authorization)를 통해 전달.
  • HTTP 메서드와 헤더는 RESTful API와 JWT가 효율적으로 동작하도록 결합되어 사용
    이 구조를 통해 클라이언트-서버 간의 효율적이고 안전한 데이터 교환이 가능

 

추가 설명

  1. 네트워크 통신은 HTTP로 이루어진다.
  2. HTTP는 무상태 프로토콜이며 비연결성 특징을 가지고 있다.
  3. HTTP Message 구조
    1. HTTP Method
    2. 상태코드
    3. HTTP Header
  4. HTTP API는 Restful 하게 설계해야 한다. 최소 성숙도레벨 2를 지켜야 한다.
  5. Servlet은 Java에서 Request, Response를 쉽게 다루게 해주는 객체이다.
  6. Servlet Container는 Servlet 객체를 싱글톤으로 관리한다.
  7. WAS는 다중 요청 처리를 위해 Multi Thread를 지원한다.
  8. SSR 방식은 서버에서 동적인 페이지를 완성하여 응답한것을 브라우저에서 화면을 출력한다.

CSR은 HTTP API 통신으로 얻은 결과를 통해 브라우저에서 동적으로 화면을 출력한다.

 

[1] 네트워크 기본 개념

  • Packet: 데이터는 패킷이라는 작은 단위로 쪼개져 전송됩니다. 패킷은 헤더, 페이로드, 트레일러로 구성되며, 패킷을 구성하는 각 요소는 데이터가 정확하게 전송되도록 돕습니다.
    • 배달 예시: 패킷은 배달되는 소포에 해당합니다. 소포는 수신자의 이름과 주소(헤더), 내용물(페이로드), 배송 확인 스티커(트레일러)가 있습니다.
  • HTTP: HyperText Transfer Protocol 에서 데이터를 주고받는 프로토콜입니다. 클라이언트와 서버 간의 요청과 응답 방식으로 동작합니다.
    • 배달 예시: HTTP는 배달 요청서 배송 알림에 해당합니다. 클라이언트가 요청서를 보내면 서버는 해당 요청에 맞는 배송 알림을 보냅니다.
  • DNS (Domain Name System): DNS는 도메인 이름을 IP 주소로 변환하는 시스템입니다.
    • 배달 예시: DNS는 배달에 필요한 주소 안내서와 같습니다. 클라이언트가 도메인 이름을 입력하면, DNS는 그것을 실제 주소로 바꿔주는 역할을 합니다.
  • URI (Uniform Resource Identifier): URI는 웹 자원의 주소를 식별하는 방법입니다.
    • 배달 예시: URI는 배달 주소와 같습니다. 웹 자원을 정확하게 식별하고 위치를 알려줍니다.
  • Port: 네트워크 통신에서 특정 애플리케이션을 식별하는 숫자입니다.
    • 배달 예시: 포트는 집의 방 번호와 같습니다. 소포가 도착한 주소에서 어떤 방으로 가야 할지 알려줍니다.
  • Cache: 캐시는 자주 사용하는 데이터를 임시 저장하여 빠르게 접근할 수 있도록 돕습니다.
    • 배달 예시: 캐시는 자주 사용하는 물건을 미리 준비해두고, 필요할 때 바로 꺼내서 배송하는 것과 같습니다.

[2] 웹 서버와 WAS (Web Application Server)

  • Web Server: 웹 서버는 클라이언트의 요청을 받아 **정적 파일(HTML, CSS, JS)**을 응답하는 서버입니다.
    • 배달 예시: 웹 서버는 택배 회사입니다. 택배 회사는 소포를 받은 후, 내용물이 정해져 있는 소포를 정확하게 배달합니다.
  • WAS (Web Application Server): WAS는 동적인 웹 애플리케이션을 처리하는 서버로, 웹 서버보다 더 복잡한 비즈니스 로직을 처리합니다.
    • 배달 예시: WAS는 주문을 받고 복잡한 가공을 거쳐 배송하는 업체입니다. 클라이언트가 주문한 후, 상품을 가공하여 최종 결과물을 배송합니다.

[3] HTTP와 관련된 개념들

  • HTTP Method: HTTP 요청의 유형에는 GET, POST, PUT, DELETE 등이 있습니다.
    • 배달 예시: HTTP 메서드는 배달 요청 유형입니다. 예를 들어, GET은 단순히 "배달해주세요", POST는 "새로운 주문을 넣어주세요"와 같습니다.
  • HTTP Header: HTTP 헤더는 요청이나 응답에서 추가적인 정보를 담고 있는 부분입니다.
    • 배달 예시: 헤더는 배달 시 필요한 추가 정보입니다. 예를 들어, 요청자가 주문한 물건에 대한 배달 주소, 연락처 같은 정보입니다.
  • Cookie: 쿠키는 클라이언트 측에 저장된 세션 정보입니다.
    • 배달 예시: 쿠키는 배달 중간에 발생하는 특별한 메모입니다. 배달 중에 클라이언트가 이전 정보를 기억하여 다시 요청할 수 있게 돕습니다.
  • Cache: 캐시는 자주 요청되는 데이터를 로컬에 저장하여 응답 속도를 빠르게 만듭니다.
    • 배달 예시: 캐시는 자주 받는 물건을 미리 준비해 놓고 필요할 때 빠르게 배송하는 것과 같습니다.
  • Redirection (리다이렉션): 리다이렉션은 사용자가 요청한 자원이 다른 위치로 이동했을 때, 해당 자원을 새로운 위치로 안내하는 과정입니다.
    • 배달 예시: 리다이렉션은 주소지가 변경된 경우 새 주소로 다시 안내하는 것입니다.

[4] 서버와 클라이언트의 렌더링 방식

  • CSR (Client-Side Rendering): CSR은 클라이언트에서 HTML을 동적으로 생성하여 렌더링하는 방식입니다.
    • 배달 예시: CSR은 클라이언트가 주문을 받고 상품을 준비하여 직접 배달하는 것과 같습니다.
  • SSR (Server-Side Rendering): SSR은 서버에서 HTML을 미리 생성하여 클라이언트에게 전달하는 방식입니다.
    • 배달 예시: SSR은 서버가 모든 물건을 미리 포장하여 클라이언트에게 빠르게 배송하는 방식입니다.

[5] WAS 생명주기 모델

  • 생명주기: WAS의 생명주기는 애플리케이션의 시작, 실행, 종료 순으로 진행됩니다.
    • 배달 예시: WAS의 생명주기는 배달 준비 과정에 해당합니다. 주문 받기 → 배달 준비 → 배달 완료의 과정입니다.

[6] Thread와 관련된 개념들

  • Thread: 쓰레드는 프로그램 내에서 실행 단위를 말하며, 여러 쓰레드가 동시에 작업을 처리할 수 있습니다.
    • 배달 예시: 쓰레드는 여러 명의 배달 기사가 동시에 여러 주소로 배달하는 것에 해당합니다.
  • Servlet: 서블릿은 웹 애플리케이션 서버에서 요청을 처리하는 Java 기반의 기술입니다.
    • 배달 예시: 서블릿은 배달 기사가 주문을 받고, 그에 맞는 작업을 처리하여 고객에게 전달하는 역할입니다.

[7] 다른 주요 개념들

  • MSA (Microservices Architecture): MSA는 마이크로서비스 아키텍처로, 큰 애플리케이션을 작은 서비스들로 분할하여 관리하는 방법입니다.
    • 배달 예시: MSA는 여러 개의 물류 창고에서 각각의 물건을 처리하고, 모든 창고가 협력하여 배달을 완성하는 것과 같습니다.
  • Scale Up, Scale Out: Scale Up은 하드웨어 성능을 강화하는 방식이고, Scale Out은 서버를 추가하는 방식입니다.
    • 배달 예시: Scale Up은 더 큰 트럭을 구입하는 것이고, Scale Out은 배달 기사를 더 추가하는 것과 같습니다.

 

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] Filter  (1) 2025.01.01
[Net] Session & Cookie의 관계  (0) 2024.12.31
[Net] Token & JWT  (0) 2024.12.29
[Net] Cookie  (1) 2024.12.28
[Net] MVC 패턴  (0) 2024.12.13

공통 관심 사항(cross-cutting concerns)

📌 애플리케이션 전반에 걸쳐 여러 모듈이나 레이어에서 공통적으로 필요하지만, 특정 비즈니스 로직과 직접적으로 관련이 없는 기능을 의미합니다.

  • 이러한 기능은 여러 곳에서 반복적으로 사용되며, 코드 중복을 초래하거나 모듈 간의 결합도를 높이는 원인이 될 수 있습니다.
같은 말로 횡단 관심사 라고 하며 여러 위치에서 공통적으로 사용되는 부가 기능이고 Filter가 나오게된 이유는 공통 관심사(Cross Cutting Concern)의 처리 때문이다.

 

요구사항 : 로그인 한 유저만 특정 API를 사용할 수 있어야 한다.
  • 해결방법
    • 언제나 핵심은 수정에 있다!
    1. 화면에서 로그인 하지 않으면 API를 사용하지 못하도록 막는다.
      • 유저가 HTTP 요청을 마음대로 조작할 수 있다.
    2. Controller에서 로그인 여부를 체크하는 Logic을 작성한다.
      • 실제로는 인증이 필요한 모든 컨트롤러에 공통으로 로그인 여부를 체크해야 한다.
      • 로그인 로직이 변경될 때 마다 로그인 여부를 체크하는 Logic 또한 변경될 가능성이 높다.
@RestController
@RequestMapping("/post")
public class PostController {

    @PostMapping
    public PostResponseDto create(PostCreateRequestDto request) {
        // 로그인 여부 확인 로직

        // 생성 로직
    }

    @PutMapping("/{postId}")
    public void update(
            @PathVariable Long postId,
            PostUpdateRequestDto request
    ) {
        // 로그인 여부 확인 로직
        // 수정 로직
    }

    @DeleteMapping("/{postId}")
    public void delete(@PathVariable Long postId) {
        // 로그인 여부 확인 로직
        // 삭제 로직
    }

}
  • 위와같이 여러가지 로직에서 공통으로 관심이 있는 부분을 공통 관심사 라고 한다.
로그인 여부 확인 로직 -> 공통 관심사 (인증 : 로그인)
  • 공통 관심사는 로그인뿐만 아니라 더 큰범위를 의미한다.
  1. Spring AOP를 활용할 수 있다.
  2. Web과 관련된 공통 관심사는 Servlet FilterSpring Intercepter를 사용한다.
    • HttpServletRequest 객체를 제공하기 때문에 HTTP 정보나 URL 정보에 접근하기 쉽다.
    ex) HTTP Header Cookie → 인증
  3. ex ) 특정 URL의 요청은 인증을 할것이다. → URL 정보 필요

 

 

Spring AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)

📌 로깅, 보안, 트랜잭션 관리 등과 같은 횡단 관심사를 개별적인 Aspect(관점)로 모듈화할 수 있도록 합니다.

  • 이를 통해 핵심 비즈니스 로직과 부가적인 로직을 분리하여 코드의 가독성과 유지보수성을 높일 수 있습니다.
  • 반복되는 공통 작업(예: 로깅, 보안, 트랜잭션 관리)을 한 곳에 모아서 관리할 수 있게 도와준다. 예를 들어 모든 메서드 실행 전에 로그를 냄기는 경우, 각 메서드에 System.out.println()을 넣는 대신, AOP로 한번만 설정하면 알아서 모든 메서드에 적용된다.

 

Spring AOP의 주요 개념

  1. Aspect(관점)
    • 횡단 관심사를 정의한 모듈입니다. 예: 로그 기록, 트랜잭션 관리.
  2. Join Point(조인 포인트)
    • 애플리케이션 실행 과정에서 Aspect를 적용할 수 있는 지점입니다. 예: 메서드 호출, 예외 발생.
  3. Advice(어드바이스)
    • Join Point에서 실행되는 작업(Aspect의 구체적인 동작)입니다. Advice는 다음과 같이 구분됩니다:
      • Before: 메서드 실행 전에 실행.
      • After: 메서드 실행 후에 실행.
      • Around: 메서드 실행 전후에 실행.
      • After Returning: 메서드가 정상적으로 반환된 후 실행.
      • After Throwing: 메서드 실행 중 예외가 발생한 후 실행.
  4. Pointcut(포인트컷)
    • Advice가 적용될 Join Point를 정의하는 표현식입니다.
  5. Weaving(위빙)
    • Aspect를 애플리케이션의 대상 객체에 적용하는 과정입니다. Spring AOP에서는 런타임 위빙이 일반적으로 사용됩니다.

 

Spring AOP 특징

  • Spring AOP는 프록시 기반으로 동작합니다.
  • 런타임 기반 AOP를 지원하며, Java 동적 프록시나 CGLIB를 사용합니다.
  • 메서드 호출 수준의 AOP를 제공합니다. (클래스 내부의 필드 변경 등에는 적용되지 않음)

 

Spring AOP 사용 방법

1. 의존성 추가 Spring AOP를 사용하려면 Maven 또는 Gradle에 관련 의존성을 추가합니다:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>버전</version>
</dependency>

 

2. Aspect 정의 아래는 간단한 로그 Aspect 예제입니다:

@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("메서드 실행 전: " + joinPoint.getSignature().getName());
    }
    
    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("메서드 실행 후: " + joinPoint.getSignature().getName());
    }
}

 

3. Pointcut 표현식

  • execution(* 패키지명.클래스명.메서드명(..)): 특정 메서드에 적용.
  • within(패키지명..*): 특정 패키지 내 모든 클래스에 적용.
  • @annotation(애너테이션): 특정 애너테이션이 적용된 메서드에만 적용.

4. AOP 활성화 AOP 기능을 활성화하려면 @EnableAspectJAutoProxy 애너테이션을 사용합니다:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

 

 

Proxy

📌 대리자라는 의미로, 컴퓨터 네트워킹에서 주로 클라이언트와 서버 간의 중간에 위치하여 데이터를 중계하는 서버 또는 소프트웨어를 지칭합니다.

 

1. 프록시의 주요 개념

  • 중간자 역할: 프록시는 클라이언트가 서버에 직접 요청을 보내지 않고, 프록시를 통해 요청과 응답이 오가게 만듭니다.
  • 주소 은폐: 클라이언트의 실제 IP 주소를 감추고, 프록시 서버의 IP를 사용하여 요청을 처리합니다.
  • 트래픽 제어: 요청을 필터링하거나 캐싱하는 등 트래픽을 관리합니다.

2. 프록시의 유형

1) 포워드 프록시 (Forward Proxy)

  • 동작: 클라이언트가 프록시 서버를 통해 인터넷에 요청을 보냅니다.
  • 사용 사례:
    • IP 숨김: 사용자의 IP 주소를 감추고 다른 주소로 대체.
    • 콘텐츠 우회: 지역 제한된 콘텐츠(예: 특정 국가에서 차단된 웹사이트) 접근.
    • 캐싱: 자주 요청되는 데이터를 저장하여 빠른 응답 제공.

2) 리버스 프록시 (Reverse Proxy)

  • 동작: 서버 앞단에 위치하며, 클라이언트 요청을 받아 적절한 서버에 전달.
  • 사용 사례:
    • 로드 밸런싱: 여러 서버로 트래픽을 분산하여 서버 과부하 방지.
    • 보안: 서버의 실제 위치(IP)를 숨기고 공격으로부터 보호.
    • SSL 종단: HTTPS 암호화를 처리하여 서버 부담 감소.

3) 오픈 프록시 (Open Proxy)

  • 동작: 전 세계 누구나 사용할 수 있는 공개 프록시 서버.
  • 특징: 익명성을 제공하지만 보안 문제가 있을 수 있음.
  • 주의사항: 악의적인 활동에 이용될 가능성이 높음.

4) 투명 프록시 (Transparent Proxy)

  • 동작: 사용자에게 보이지 않는 방식으로 트래픽을 중계.
  • 사용 사례:
    • 네트워크 관리자에 의해 웹 필터링이나 모니터링.
    • 기업 및 공공기관에서 사용.

5) SOCKS 프록시

  • 특징: TCP와 UDP 트래픽을 모두 처리 가능.
  • 사용 사례:
    • P2P 파일 공유(예: 토렌트).
    • 게임에서 지연 감소 및 지역 제한 우회.

6) VPN과의 차이

  • VPN은 네트워크 트래픽 전체를 암호화하는 반면, 프록시는 특정 트래픽만 처리.
  • 프록시는 보안보다는 익명성과 요청 중계를 중심으로 함.

3. 프록시 사용 사례

  1. 보안 및 익명성 제공
    • 사용자의 실제 IP를 숨기고, 온라인 활동을 익명으로 수행.
  2. 콘텐츠 필터링 및 접근 제한
    • 기업이나 학교에서 불법적이거나 부적절한 콘텐츠 접근 차단.
  3. 지역 제한 콘텐츠 우회
    • 예: 특정 국가에서 차단된 Netflix 콘텐츠를 시청.
  4. 트래픽 최적화
    • 캐싱을 통해 대역폭을 줄이고, 요청 속도를 높임.
  5. 분산 처리
    • 리버스 프록시를 사용하여 대규모 트래픽을 여러 서버로 나누어 처리.
  6. 데이터 로깅 및 모니터링
    • 네트워크 트래픽 분석 및 기록.
  7. 테스트 및 디버깅
    • 웹 개발에서 네트워크 요청과 응답을 확인하거나 조작.

4. 프록시 사용 시 고려사항

  1. 성능:
    • 추가적인 네트워크 홉으로 인해 지연이 발생할 수 있음.
  2. 보안:
    • 신뢰할 수 없는 프록시 서버 사용 시 개인 정보가 유출될 위험.
  3. 법적 문제:
    • 지역 제한 콘텐츠 우회 등은 해당 국가의 법률을 위반할 수 있음.
  4. 설정 복잡성:
    • 특정 애플리케이션이나 네트워크 환경에서는 복잡한 설정이 필요할 수 있음.

 

아래는 **프록시(Proxy)**와 유사 기술(VPN, NAT, 방화벽 등)을 비교한 표입니다. 각 기술의 특징, 주 용도, 장단점을 기준으로 작성했습니다.

기술 주요 특징 주 용도 장점 단점
프록시 (Proxy) 클라이언트와 서버 간의 중계 역할. 특정 요청과 응답만 처리. 익명성 제공, 콘텐츠 필터링, 캐싱, 로드 밸런싱 익명성 제공, 특정 요청만 중계 가능, 네트워크 효율성 증대 암호화 부족, 전체 트래픽 보호 불가, 설정 복잡도
VPN 클라이언트와 서버 간의 모든 네트워크 트래픽을 암호화된 터널을 통해 전송. 전체 트래픽 보호, 지역 제한 우회, 공공 네트워크 보안 강력한 암호화, 전체 네트워크 보호, 위치 은폐 느린 속도(암호화로 인한 지연), 서버 설정 복잡도
NAT 내부 네트워크의 사설 IP를 공인 IP로 변환하여 인터넷 접속을 가능하게 함. 내부 네트워크 주소 은폐, 인터넷 접속 공유 네트워크 자원 효율적 사용, 기본적인 보안 제공 특정 연결 설정 어려움(포트 포워딩 필요), 정교한 보안 기능 부족
방화벽 (Firewall) 네트워크 트래픽을 모니터링하고, 사전 정의된 규칙에 따라 허용 또는 차단. 네트워크 보안, 공격 방지, 접근 제어 높은 보안성, 트래픽 모니터링 가능 설정 복잡도, 성능 저하 가능
CDN 클라이언트 요청을 가장 가까운 서버로 라우팅하여 콘텐츠를 빠르게 제공. 콘텐츠 전송 속도 향상, 대규모 트래픽 처리 빠른 속도, 지연 시간 감소, 서버 부하 감소 실시간 처리 제한, 동적 콘텐츠 캐싱 어려움
로드 밸런서 여러 서버에 네트워크 트래픽을 분산하여 서버의 부하를 줄임. 서버 성능 최적화, 고가용성 제공 서버 부하 감소, 고가용성 제공 초기 설정 복잡, 추가 하드웨어/소프트웨어 필요
TOR (Onion Routing) 익명 네트워크로 트래픽을 여러 노드에 암호화해 전송하여 사용자의 위치와 활동을 감춤. 높은 익명성 제공, 검열 우회 익명성 보장, 검열 우회 가능 느린 속도, 네트워크 신뢰성 부족, 악성 노드 가능성
SOCKS 프록시 TCP 및 UDP 트래픽 모두 지원하며, 특정 애플리케이션 트래픽만 중계. P2P 파일 공유, 게임 지연 감소, 특정 트래픽 익명화 유연성 높음, 애플리케이션별 설정 가능 암호화 부족, 전체 네트워크 보호 불가

 

 

Servlet Filter

📌 클라이언트의 요청(request) 및 응답(response)을 처리하거나 조작하기 위해 사용되는 컴포넌트( 소프트웨어 개발에서 재사용 가능하고 독립적인 기능 단위 )

  • Servlet Filter는 보안, 로깅, 인코딩, 인증/인가 등 다양한 작업을 처리하기 위해 사용된다.

 

 

Servlet Filter 특징

  1. 공통 관심사 로직 처리
    • 공통된 로직을 중앙 집중적으로 구현하여 재사용성이 높고 유지보수가 쉽다.
    • 모든 요청이 하나의 입구를 통해 처리되어 일관성을 유지한다.
  2. HTTP 요청 및 응답 필터링
  3. Filter Chain
    • 여러 개의 필터가 순차적으로 적용될 수 있다.
    • filterChain.doFilter(request, response); 다음 필터로 제어를 전달한다.
  4. doFilter()
    • 실제 필터링 작업을 수행하는 주요 메소드로 필터가 처리할 작업을 정의한다.
    • 다음 필터로 제어를 넘길지 여부를 결정한다.

 

Servlet Filter 적용

  • Filter를 적용하면 Servlet이 호출되기 이전에 Filter를 항상 거치게된다.
  • 공통 관심사를 필터에만 적용하면 모든 요청 or 응답에 적용된다 .
  • Filter는 특정 URL Pattern에 적용할 수 있다.
  • Spring을 사용하는 경우 Servlet은 **Dispatcher Servlet**이다.

 

Servlet Filter와 Servlet의 차이점

항목Servlet FilterServlet

주요 목적 요청/응답 전후의 처리 클라이언트 요청을 처리하고 응답 생성
체인 가능성 여러 필터를 체인 방식으로 연결 가능 독립적으로 동작
적용 대상 서블릿, JSP, 정적 리소스 등 모든 요청 특정 URL에 대한 요청만 처리
작업 위치 요청/응답 전후 요청 처리 내부

 

Servlet Filter와 Listener 비교

항목Servlet FilterServlet Listener

주요 역할 요청/응답 흐름을 가로채고 처리 특정 이벤트(애플리케이션, 세션 등) 처리
적용 대상 클라이언트 요청/응답 흐름 컨텍스트, 세션, 요청 수명 주기
예제 로깅, 인증, 캐싱 세션 생성/소멸, 애플리케이션 초기화

 

 

 

Filter Interface

📌 Java Servlet에서 HTTP 요청과 응답을 중간에서 처리, 이를 기반으로 다양한 처리 작업을 수행하는 데 사용되는 Interface이다.

  • 클라이언트 → 요청 → 필터 → 서블릿/JSP → 응답 → 필터 → 클라이언트

 

Filter Interface의 사용 사례

  1. 인증(Authentication) 및 권한 부여(Authorization):
    • 특정 페이지에 접근하려는 클라이언트를 필터에서 확인하고, 인증되지 않은 사용자라면 로그인 페이지로 리디렉션.
  2. 로깅(Logging):
    • 요청의 정보를 기록(예: 요청 URL, 클라이언트 IP, 요청 시간 등).
  3. 데이터 유효성 검사:
    • 클라이언트의 요청 데이터를 확인하고, 잘못된 값이 있으면 필터 단계에서 차단.
  4. 응답 데이터 변환:
    • 서버의 응답 데이터를 필터에서 압축하거나 특정 포맷(예: JSON)으로 변환.
  5. 보안 처리:
    • HTTP 요청의 헤더를 검사해 보안 위협(예: XSS, CSRF)을 탐지 및 차단.

 

jakarta.servlet.Filter

  • Filter Interface를 Implements하여 구현하고 Bean으로 등록하여 사용한다.
    • Servlet Container가 Filter를 Singleton 객체로 생성 및 관리한다.

 

주요 메서드

  1. init()
    • Filter를 초기화하는 메서드이다.
    • Servlet Container가 생성될 때 호출된다.
    • default ****method이기 때문에 implements 후 구현하지 않아도 된다.
  2. doFilter()
    • Client에서 요청이 올 때 마다 doFilter() 메서드가 호출된다.
      • doFilter() 내부에 필터 로직(공통 관심사 로직)을 구현하면 된다.
    • WAS에서 doFilter() 를 호출해주고 하나의 필터의 doFilter()가 통과된다면
    • Filter Chain에 따라서 순서대로 doFilter() 를 호출한다.
    • 더이상 doFilter() 를 호출할 Filter가 없으면 Servlet이 호출된다.
  3. destroy()
    • 필터를 종료하는 메서드이다.
    • Servlet Container가 종료될 때 호출된다.
    • default method이기 때문에 implements 후 구현하지 않아도 된다.

 

 

Servlet Filter 구현

  • Filter 구현체
    • 요청 URL을 Log로 출력하는 Filter
@Slf4j
public class CustomFilter implements Filter {
    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {

        // Filter에서 수행할 Logic
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        log.info("request URI={}", requestURI);
        // chain 이 없으면 Servlet을 바로 호출
        chain.doFilter(request, response);
    }
}

doFilter() 는 더 이상 호출할 Filter가 없다면 Servlet을 호출한다.

ServletRequest 는 기능이 별로 없어서 대부분 기능이 많은 HttpServletRequest 를 다운 캐스팅 하여 사용한다.

 

 

Filter 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
		
		@Bean
		public FilterRegistrationBean customFilter() {
				FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
				// Filter 등록
				filterRegistrationBean.setFilter(new CustomFilter());
				// Filter 순서 설정
				filterRegistrationBean.setOrder(1);
				// 전체 URL에 Filter 적용
				filterRegistrationBean.addUrlPatterns("/*");
				
				return filterRegistrationBean;
		}
}
  • setFilter()
    • 등록할 필터를 파라미터로 전달하면 된다.
  • setOrder()
    • Filter는 Chain 형태로 동작한다.
    • 즉, 실행될 Filter들의 순서가 필요하다.
    • 파라미터로 전달될 숫자에 따라 우선순위가 정해진다.
    • 숫자가 낮을수록 우선순위가 높다.
  • addUrlPatterns()
    • 필터를 적용할 URL 패턴을 지정한다.
    • 여러개 URL 패턴을 한번에 지정할 수 있다.
    • 규칙은 Servlet URL Pattern과 같다.
  • filterRegistrationBean.addUrlPatterns("/*")
    • 모든 Request는 Custom Filter를 항상 지나간다.
Spring이 제공하는 URL Pattern은 Servlet과 다르게 더욱 세세하게 설정할 수 있다.

 

 

 

Servlet Filter 정리

  1. Filter를 사용하려면 Filter Interface를 Implements 하여 구현한다.
  2. 구현한 Filter를 Bean으로 등록 한다.
  3. HTTP 요청이 오면 doFilter() 메서드가 호출된다.
    • ServletRequest는 기능이 별로 없어서 HttpServletRequest로 다운 캐스팅 해야한다.
  4. chain.doFilter(request, response)
    • (순서를 설정해둔)다음 필터가 있으면 Filter를 호출한다.
    • 다음 실행할 필터가 없으면 Servlet을 호출한다.
    • 해당 메서드를 호출하지 않으면 다음 단계로 진행되지 않는다.
      • 다음 필터나 Servlet을 호출하지 않는다.
  5. Filter를 등록하는 방법은 여러가지가 있다.
    • SpringBoot의 경우 FilterRegistrationBean 을 사용한다.

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] 네트워크 요약  (1) 2025.01.22
[Net] Session & Cookie의 관계  (0) 2024.12.31
[Net] Token & JWT  (0) 2024.12.29
[Net] Cookie  (1) 2024.12.28
[Net] MVC 패턴  (0) 2024.12.13

세션과 쿠키는 항상 함께 다니는 이유는, 세션이 동작하는 데 쿠키가 필요한 경우가 많기 때문입니다.


1. 세션과 쿠키의 관계

세션과 쿠키는 역할이 다름

  • 세션은 서버에서 클라이언트를 관리하기 위해 사용하는 데이터입니다.
  • 쿠키는 클라이언트(브라우저)에 데이터를 저장하기 위해 사용하는 기술입니다.

왜 세션에 쿠키가 필요할까?

  • 세션은 클라이언트를 식별하기 위해 세션 ID를 발급합니다.
  • 세션 ID를 클라이언트와 서버 간에 주고받아야 세션 상태를 유지할 수 있습니다.
  • 쿠키는 이 세션 ID를 클라이언트 측에 저장하고, 이후 요청마다 서버로 전달하는 데 사용됩니다.

2. 세션과 쿠키가 항상 같이 다니는 이유

  1. 세션 ID 전달
    • 서버는 클라이언트를 식별하기 위해 세션 ID를 발급하고, 이를 클라이언트에 저장해야 합니다.
    • 세션 ID를 저장하고 서버로 다시 전달하는 가장 일반적인 방법이 쿠키입니다.
    • :
      HTTP/1.1 200 OK
      Set-Cookie: JSESSIONID=abcd1234; Path=/; HttpOnly
      
  2. 자동 관리
    • 쿠키는 브라우저가 자동으로 관리하므로, 클라이언트가 따로 세션 ID를 전송하지 않아도 브라우저가 요청마다 서버로 쿠키를 전송합니다.
    • :
      GET /profile HTTP/1.1
      Host: example.com
      Cookie: JSESSIONID=abcd1234
      
  3. 보안 및 편의성
    • 세션 ID를 URL로 전달하는 방법(예: URL Rewriting)은 보안 문제와 관리상의 불편함이 있습니다.
    • 쿠키를 사용하면 세션 ID가 URL에 노출되지 않아 보안성이 더 높습니다.

3. 세션 사용 시 쿠키를 따로 저장하지 않는 경우

쿠키 없이 세션을 유지하는 방법

  • 쿠키를 사용하지 않고도 세션을 유지할 수 있지만, 이는 덜 일반적이며, 다음과 같은 방식으로 구현됩니다.
  1. URL Rewriting:
    • 세션 ID를 URL에 포함하여 클라이언트와 서버 간에 전달.
    • :
      GET /profile;jsessionid=abcd1234 HTTP/1.1
      
    • 단점:
      • URL이 길어지고 관리가 어려움.
      • 세션 ID가 링크를 통해 외부로 유출될 위험이 있음.
  2. Hidden Field (HTML 폼):
    • 세션 ID를 숨겨진 HTML 입력 필드에 저장하여 폼 데이터와 함께 전송.
    • :
      <form action="/profile" method="POST">
          <input type="hidden" name="jsessionid" value="abcd1234">
          <button type="submit">Submit</button>
      </form>
      
    • 단점:
      • GET 요청에는 사용할 수 없음.
      • 페이지마다 세션 ID를 포함해야 하므로 구현이 복잡.

4. 세션과 쿠키의 공통 허용 여부

웹 브라우저 설정에서 세션과 쿠키가 항상 함께 나오는 이유

  • 쿠키는 세션 ID를 저장하고 전송하는 데 자주 사용되므로, 쿠키가 차단되면 세션도 제대로 동작하지 않을 가능성이 큽니다.
  • 쿠키를 허용하지 않으면 세션 유지가 어려워질 수 있으므로, 브라우저에서 "세션과 쿠키"를 함께 설정 항목으로 묶는 경우가 많습니다.

5. 세션과 쿠키의 차이

  세션(Session) 쿠키(Cookie)
저장 위치 서버 클라이언트(브라우저)
용도 사용자 상태 관리 (예: 로그인) 사용자 데이터 저장 (예: 선호 테마, 언어)
보안성 데이터는 서버에 저장되므로 비교적 안전 클라이언트에 저장되므로 조작/탈취 가능성 있음
데이터 전송 세션 ID만 전송 쿠키 전체 데이터가 요청마다 서버로 전송
유효 기간 서버에서 설정 가능 (일반적으로 세션 유지 동안) 설정된 만료 시간에 따라 유지
상호작용 쿠키를 통해 세션 ID를 전달하는 데 사용 세션 없이도 독립적으로 사용 가능

6. 요약

  • 세션은 서버에서 사용자 상태를 관리하기 위한 데이터이고, 쿠키는 세션 ID를 저장하고 클라이언트와 서버 간에 전송하는 역할을 합니다.
  • 쿠키를 사용하면 세션 ID를 쉽게 관리하고 전송할 수 있으므로, 세션과 쿠키가 자주 함께 사용됩니다.
  • 세션은 반드시 쿠키에 의존하지는 않지만, 쿠키를 사용하지 않으면 구현이 복잡하고 관리가 어려워질 수 있습니다.
    따라서 세션과 쿠키는 **"같이 다니는 것처럼 보인다"**고 느껴질 수 있습니다.

 

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] 네트워크 요약  (1) 2025.01.22
[Net] Filter  (1) 2025.01.01
[Net] Token & JWT  (0) 2024.12.29
[Net] Cookie  (1) 2024.12.28
[Net] MVC 패턴  (0) 2024.12.13

Token

📌 Web Application이나 API에서 인증(Authentication)과 인가(Authorization) 과정에서 사용되며 사용자 또는 시스템의 신원과 권한을 증명하고 요청의 유효성을 검증하는 데 사용되는 디지털 문자열이다.

  • 인증(Authentication), 권한 부여(Authorization), 또는 데이터 교환을 위해 클라이언트와 서버 간에 사용되는 작고 안전한 데이터 객체입니다.
  • 로그인 인증이나 사용자 권한 관리를 할 때 토큰을 발급하고, 이를 통해 사용자를 식별하거나 인증 상태를 유지할 수 있습니다.
  • 토큰은 전체 데이터를 포함하지 않는다. 박스가 아니라 키라고 생각해야한다. 말 그대로 다른 클라에 요청할 시 사용가능한 인물인지에 대한 정보만 포함된다. 패킷이 아니다.

  • Token 생성 시 사용자의 고유한 정보를 포함한다.
  • 데이터베이스에 접근하지 않고 Token의 유효성만 검증한다.
  • Token의 단점
    1. Cookie/Session 방식보다 Token 자체의 데이터 용량이 많다.
      • 요청이 많아지면 그만큼 트래픽이 증가한다.
    2. Payload(전송되는 데이터)는 암호화되지 않아서 중요한 데이터를 담을 수 없다.
    3. Token을 탈취당하면 대처하기 어려워 만료 시간(30분)을 설정한다.

 

0. Token을 사용하는 이유

  1. Token은 서버가 아닌 클라이언트에 저장되어 서버의 부담을 덜 수 있다.
  2. Cookie는 웹 브라우저에만 존재하여 모바일 앱 등의 다양한 클라이언트에서 인증을 처리할 수 없다.
  3. Token 방식은 Stateless를 기반으로 하여 확장성이 뛰어나다.
  4. 인증된 사용자임을 확인하기 위한 고유한 서명을 포함하여 위조된 요청인지 확인할 수 있다.

 

1. 토큰의 기본 개념

토큰의 역할

  1. 인증(Authentication):
    • 사용자가 로그인하면 서버는 인증 결과를 기반으로 토큰을 발급합니다.
    • 클라이언트는 이후 요청마다 이 토큰을 사용하여 인증된 사용자임을 증명합니다.
  2. 권한 부여(Authorization):
    • 사용자가 어떤 리소스에 접근할 수 있는 권한이 있는지 확인합니다.
    • 예: "관리자" 권한을 가진 사용자만 특정 API에 접근 가능.
  3. 상태 정보 저장(State):
    • 토큰에 사용자 관련 정보를 포함하여 서버가 상태를 유지하지 않아도 인증 상태를 확인할 수 있습니다.

2. 토큰의 종류

a. 세션 토큰

  • 서버가 상태를 관리하며, 클라이언트는 세션 ID를 사용해 서버에 요청.
  • 예: session_id=abcd1234 쿠키로 저장.

b. JSON Web Token (JWT)

  • 클라이언트 측에서 상태를 관리할 수 있는 자체 포함(Self-Contained) 토큰.
  • 서명(Signature)을 통해 데이터의 변조를 방지.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsIm5hbWUiOiJKb2huIERvZSJ9.s5t0sT5kdcfXb3wNkm2mOiVjHXe

c. OAuth 토큰

  • OAuth2.0 표준에서 사용하는 토큰으로, **액세스 토큰(Access Token)**과 **리프레시 토큰(Refresh Token)**으로 구성.

d. CSRF 토큰

  • CSRF(Cross-Site Request Forgery) 공격을 방지하기 위해 요청마다 고유한 토큰을 생성.

3. JSON Web Token (JWT)의 구조

JWT는 세 가지 부분으로 구성된 문자열이며, 점(.)으로 구분됩니다.

Header.Payload.Signature

Header (헤더):

  • 토큰의 메타정보를 포함.
  • 예: 알고리즘과 토큰 타입.
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload (페이로드):

  • 사용자 정보와 클레임(Claim)을 포함.
  • 예: 사용자 ID, 권한 정보.
{
  "userId": 1,
  "role": "admin"
}

 

Signature (서명):

  • 토큰의 무결성을 검증하기 위한 서명.
  • 비밀키(Secret Key)로 생성.

4. 토큰 기반 인증 흐름

a. 로그인 요청

  • 사용자가 서버에 로그인 요청을 보냅니다.
POST /login HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "username": "john_doe",
  "password": "secure_password"
}

b. 토큰 발급

  • 서버는 사용자를 인증한 후, JWT 또는 세션 토큰을 발급합니다.
HTTP/1.1 200 OK
Content-Type: application/json

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

c. 요청마다 토큰 사용

  • 클라이언트는 이후 요청마다 토큰을 Authorization 헤더에 포함하여 전송합니다.
GET /dashboard HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

d. 서버에서 토큰 검증

  • 서버는 토큰의 서명을 검증하고, 페이로드에 포함된 사용자 정보를 기반으로 요청을 처리합니다.

 

5. 토큰의 장단점

장점

  1. 확장성:
    • 상태를 서버에 저장하지 않으므로, 분산 서버 환경에서도 쉽게 사용 가능.
  2. 자체 포함(Self-Contained):
    • JWT는 페이로드에 필요한 정보를 포함하므로, 서버가 별도의 데이터베이스를 조회할 필요가 줄어듭니다.
  3. 유연성:
    • 토큰은 쿠키, HTTP 헤더, 또는 URL 파라미터로 전달할 수 있습니다.

단점

  1. 보안 취약성:
    • 토큰이 탈취되면 만료 전까지 악용될 수 있습니다.
    • 해결: HTTPS 사용, 짧은 유효 기간, 리프레시 토큰.
  2. 데이터 크기:
    • JWT는 페이로드에 데이터를 포함하므로, 크기가 커질 수 있습니다.
  3. 만료 처리:
    • JWT는 발급 후 변경할 수 없으므로, 토큰이 만료되거나 폐기된 경우 클라이언트에 새로운 토큰을 발급해야 합니다.

6. 토큰과 세션의 차이

  세션 기반 인증 토큰 기반 인증 (JWT)
저장 위치 서버 (세션 ID는 클라이언트에 저장) 클라이언트 (서버에 상태 저장 없음)
확장성 서버가 상태를 관리하므로 확장에 제약 상태를 저장하지 않으므로 확장성 우수
보안성 서버에서 세션 만료나 폐기가 가능 탈취 시 만료 전까지 악용 가능
데이터 크기 세션 ID만 저장 페이로드 포함, 크기가 상대적으로 큼
사용 사례 로그인 상태 유지 (웹 애플리케이션) API 인증, 분산 시스템

7. 토큰 사용 시 보안 고려 사항

  1. HTTPS 사용:
    • 토큰이 네트워크에서 탈취되지 않도록 HTTPS를 통해 전송.
  2. 짧은 유효 기간 설정:
    • 액세스 토큰은 유효 기간을 짧게 설정하고, 만료 시 리프레시 토큰으로 재발급.
  3. 서명 검증:
    • 서버는 토큰의 서명을 반드시 검증하여 변조 여부를 확인.
  4. 탈취 방지:
    • HttpOnly, Secure 속성을 사용해 쿠키로 저장하거나, 브라우저 로컬 저장소에 저장.

8. 요약

  • **토큰(Token)**은 클라이언트와 서버 간 인증 및 권한 부여를 위한 작은 데이터 객체입니다.
  • JWT는 가장 널리 사용되는 토큰 방식으로, 페이로드에 사용자 정보를 포함하여 자체적으로 인증을 수행.
  • 토큰은 분산 시스템과 API 기반 애플리케이션에서 유용하지만, 보안을 강화하기 위해 짧은 유효 기간과 HTTPS 사용 등 추가 조치를 해야 합니다.

 

JWT(JSON Web Token)

📌 인증에 필요한 정보들을 암호화시킨 JSON 형태의 Token을 의미한다. JSON 데이터 포맷을 사용하여 정보를 효율적으로 저장하고 암호화로 서버의 보안성을 높였다.

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

JWT 구조

Header

  • 토큰의 타입과 해싱 알고리즘을 정의한다.
  • 예시
{
	"alg": "HS256",
	"typ": "JWT"
}

 

Payload

  • 실제로 인증과 관련된 데이터(Claims)를 담고 있다.
  • Claims의 종류
    • Registered Claims : 미리 정의된 Claims
      • iss(issuer) : 발행자
      • exp(expiration time) : 만료시간
      • sub(subject) : 제목
      • iat(issued At) : 발행 시간
      • jti(JWT ID) : 토큰의 고유 식별자
    • Public Claims : 사용자가 정의할 수 있는 클레임, 공개용 정보 전달 목적
    • Private Claims : 사용자 지정 클레임, 당사자들 간에 정보를 공유하기 위한 목적
  • 예시
{
  "sub": "1234567890",
  "name": "Sparta",
  "exp": 1682563600
}

 

Signature

  • Header와 Payload를 서버의 Secret Key로 서명하여 암호화 한다.
  • 암호화는 Header에서 정의한 알고리즘(alg)을 활용한다.
  • 서명을 통해 서버는 Token이 변조되지 않았음을 확인할 수 있다.
  • 예시
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

 

base64UrlEncode는 값을 URL에서 사용할 수 있도록 +, /를 각각 -, _로 표기한다.
Header와 Payload는 Encoding된 값이기 때문에 복호화 혹은 값을 수정할 수 있지만 Signature는 서버에서 관리하는 값이기 때문에 Secret Key가 유출되지 않는 이상 복호화 할 수 없다.

 

 

JWT 인증

📌 JWT는 Base64로 인코딩되어 쉽게 복호화 할 수 있다. Payload가 그대로 노출되기 때문에 비밀번호나 민감한 정보를 저장하지 않는다.

  1. 클라이언트의 로그인 요청
  2. 로그인에 성공했다면 Header, Payload에 Secret Key를 사용하여 Signature를 만든다.
    • 이후 Base64로 Encoding 한다.
    • 일반적으로 Cookie에 담아 클라이언트에게 JWT를 발급한다.
  3. 발급받은 JWT를 저장 후 서버에 요청할 때 Authorization Header에 JWT를 담아 보낸다.
  4. 서버에서 JWT의 유효성 검사를 통해 통과한다면 인증에 성공하여 요청을 처리해준다.
    • JWT 만료, 위변조 여부를 검사한다.

 

JWT의 유효성 검사

  1. A의 JWT를 B가 탈취
  2. B가 탈취한 JWT를 임의로 수정
  3. B가 수정한 JWT로 Server에 요청
  4. 서버는 Signature를 사용하여 유효성 검사(Signature 불일치)
    • Header, Payload를 서버의 Secret Key값을 이용해 Signature를 다시 만들어 비교한다.
    • 임의로 조작된 데이터를 판별할 수 있다.
JSON Web Token의 목적은 정보 보호가 아닌, 위조 방지에 있다.

 

JWT 장점

  1. Signature로 서버의 보안성이 증가한다.
  2. Token 자체가 필요한 정보(유저 및 검증 정보)들을 모두 가지고 있다.
  3. 서버는 인증 정보와 관련된 별도의 저장소를 사용하지 않는다.
  4. 서버의 수평 확장성(Scale Out)이 높아진다.
  5. Cookie가 없는 다른 환경에서도 인증/인가를 적용할 수 있다.
  6. DB를 조회하지 않아도 된다.
Mobile의 경우 App을 자주 닫거나 백그라운드로 전환하여 Session 방식을 사용하지 않는다.

 

 

JWT 단점

  1. Payload는 암호화 된 것이 아니라 민감한 정보를 다루지 못한다.
  2. Token의 길이가 길어서 트래픽이 증가하면 네트워크에 부하가 증가한다.
  3. 클라이언트 측에서 Token을 관리하기 때문에 탈취당하면 대처하기 어렵다.

 

Access Token, Refresh Token

📌 Token은 클라이언트에서 관리하여 탈취당할 위험성이 높기 때문에 만료 시간 설정이 필요하다. 이 때 발생하는 단점을 극복하기 위해 Access Token과 Refresh Token을 사용한다.

  • Token의 유형
    1. Access Token
      • 사용자 인증 후 서버가 발급하는 유저 정보가 담긴 토큰이다.
      • 유효 기간 동안 API나 리소스에 접근할 때 사용한다.
    2. Refresh Token
      • Access Token은 보안을 위해 짧은 수명을 가진다.
      • Access Token이 만료된 경우 재발급 받기위해 사용한다.
      • 주로 데이터베이스에 유저 정보와 같이 저장한다.
  1. 클라이언트의 로그인 요청
  2. 로그인에 성공했다면 Header, Payload에 Secret Key를 사용하여 Signature를 만든다.
  3. 발급받은 JWT를 저장 후 서버에 요청할 때 Authorization Header에 JWT(Access Token)를 담아 보낸다.
  4. 서버에서 JWT의 유효성 검사를 통해 통과한다면 인증에 성공하여 요청을 처리해준다.
  5. Access Token이 만료 되었다면 Refresh Token 으로 토큰 재발급을 요청한다.
  6. 서버로부터 Access Token을 재발급 받는다.
JWT를 Access Token만을 사용하여 인증한다면 탈취되어 보안에 취약할 수 있다. 유효 시간을 부여하여 문제를 해결하지만 유효 시간이 짧다면 로그인을 자주 해야하기 때문에 Refresh Token을 적용한다.
 
참고하면 좋은 영상

 

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] Filter  (1) 2025.01.01
[Net] Session & Cookie의 관계  (0) 2024.12.31
[Net] Cookie  (1) 2024.12.28
[Net] MVC 패턴  (0) 2024.12.13
[Net] API 설계  (0) 2024.12.12

Cookie

📌 사용자의 웹 브라우저에 저장되는 정보로 사용자의 상태 혹은 세션을 유지하거나 사용자 경험을 개선하기 위해 사용된다. 사용자 정보나 세션 데이터를 클라이언트(브라우저)에 저장하는 기술

  • Cookie는 주로 사용자 세션 관리(로그인, 장바구니, 접속시간)나 광고 트래킹(사용자 행동) 등의 목적으로 사용된다.

  1. HTTP는 Stateless, Connectionless 특성을 가지고 있다.
  2. Client가 재요청시 Server는 이전 요청에 대한 정보를 기억하지 못한다.
  3. 로그인과 같이 상태를 유지해야 하는 경우가 발생한다.
  4. Request에 사용자 정보를 포함하면 해결이 된다.
    • 로그인 후에는 사용자 정보와 관련된 값이 저장되어 있어야한다.
  5. 브라우저를 완전히 종료한 뒤 다시 열어도 사용자 정보가 유지되어야 한다.
서버에 전송하지 않고 브라우저에 단순히 데이터를 저장하고 싶다면 Web Storage(localStorage, sessionStorage)를 사용하면 된다. 하지만 보안에 취약하기 때문에 주민번호와 같은 민감정보를 저장하면 안된다.

Web Storage는 클라이언트의 저장소로, 쿠키와 비교했을 때 서버로 데이터를 자동전송하지 않으며, 클라이언트에서만 데이터를 유지한다는 특징을 가지고 있다.

 

 

  쿠키 (Cookie) Web Storage (localStorage/sessionStorage)
저장 위치 클라이언트(브라우저), HTTP 요청 시 서버로 전송 클라이언트(브라우저), 서버로 자동 전송되지 않음
데이터 전송 HTTP 요청 시 서버로 자동 전송 서버로 전송되지 않음
저장 용량 약 4KB 약 5MB
유효 기간 설정된 만료 시간까지 유지 localStorage는 영구, sessionStorage는 세션 종료 시 삭제
범위 도메인/경로 단위로 설정 가능 localStorage는 모든 탭에서 공유, sessionStorage는 현재 탭에 한정
보안 HttpOnly와 Secure로 보안 강화 가능 자바스크립트를 통해 접근 가능 (XSS 공격에 취약)
주 용도 사용자 상태 유지 (예: 로그인, 인증) 사용자 설정, UI 상태, 임시 데이터 저장
데이터 접근 방식 HTTP 헤더 또는 자바스크립트 API로 접근 자바스크립트 API로 접근
지원 브라우저 모든 브라우저에서 지원 최신 브라우저에서 지원
삭제 방식 쿠키 만료 시간 또는 브라우저 설정에서 삭제 localStorage.clear(), sessionStorage.clear()
  • Cookie 찾아보기
    • 브라우저 개발자도구(F12) → Application → Cookies

 

로그인 성공시 응답

Set-Cookie

  • 로그인시 전달된 ID, Password로 User 테이블 조회하여 일치여부 확인
  • 일치한다면 Set-Cookie를 활용해 Cookie에 사용할 값 저장
    • Cookie는 보안에 취약하다.

 

로그인 이후 요청

요청 헤더 Cookie : 사용자 정보

  • 로그인 이후에는 모든 요청마다 Request Header에 항상 Cookie 값을 담아서 요청한다.
    • 클라이언트 → 서버 방향의 요청
    • 네트워크 트래픽이 추가적으로 발생된다.
    • 최소한의 정보만 사용해야한다.
  • Cookie에 담겨있는 값으로 인증/인가 를 진행한다.

 

Cookie Header

📌 클라이언트(브라우저)가 서버에 HTTP 요청을 보낼 때, 클라이언트에 저장된 쿠키를 함께 전송하는 HTTP 요청 헤더입니다.

  • 서버에서는 HTTP 응답 헤더에 Set-Cookie 속성을 사용해 생성하고 설정할 수 있다
  • Cookie는 서버에서 생성되어 클라이언트에 전달, 저장된다.

 

Cookie Header

  1. Set-Cookie
    • Server에서 Client로 Cookie 전달(Response Header)
  2. Cookie
    • Client가 Cookie를 저장하고 HTTP 요청시 Server로 전달(Request Header)

Response 알아보기

set-cookie: 
sessionId=abcd; 
expires=Sat, 11-Dec-2024 00:00:00 GMT;
path=/; 
domain=spartacodingclub.kr;
Secure
  • Cookie의 생명주기
    1. 세션 Cookie
      • 만료 날짜를 생략하면 브라우저 완전 종료시 까지만 유지된다.(Default)
        • expires, max-age 가 생략된 경우
      • 브라우저를 완전 종료 후 다시 페이지를 방문했을 때 다시 로그인을 해야한다.
    2. 영속 Cookie
      • 만료 날짜를 입력하면 해당 날짜까지 유지한다.
        • expires=Sat, 11-Dec-2024 00:00:00 GMT;
          • 해당 만료일이 도래하면 쿠키가 삭제된다.
        • max-age=3600 (second, 3600초는 한시간. 60 * 60)
          • 0이 되거나 음수를 지정하면 쿠키가 삭제된다.
  • Cookie의 도메인
    • 쿠키가 아무 사이트에서나 생기고 동작하면 안된다!
      • 필요없는 값 전송, 트래픽 문제 등이 발생한다.
    • domain=spartacodingclub.kr
      • domain=spartacodingclub.kr를 지정하여 쿠키를 저장한다.
      • dev.spartacodingclub.kr와 같은 서브 도메인에서도 쿠키에 접근한다.
    • domain을 생략하면 현재 문서 기준 도메인만 적용한다.
  • Cookie의 경로
    • 1차적으로 도메인으로 필터링 후 Path가 적용된다.
    • 일반적으로 path=/ 루트(전체)로 지정한다.
    • 위 경로를 포함한 하위 경로 페이지만 쿠키에 접근한다.
      • path=/api 지정
        • path=/api/example 가능
        • path=/example 불가능
  • Cookie 보안
    1. Secure
      • 기본적으로 Cookie는 http, https 구분하지 않고 전송한다.
      • Secure를 적용하면 https인 경우에만 전송한다. s = Secure
    2. HttpOnly
      • XSS(Cross-site Scripting) 공격을 방지한다.
        • 악성 스크립트를 웹 페이지에 삽입하여 다른 사용자의 브라우저에서 실행되도록 하는 공격
      • 자바스크립트에서 Cookie 접근을 못하도록 막는다.
      • HTTP 요청시 사용한다.
    3. SameSite
      • 비교적 최신 기능이라 브라우저 지원여부를 확인 해야한다.
      • CSRF(Cross-Site Request Forgery) 공격을 방지한다.
        • 사용자가 의도하지 않은 상태에서 특정 요청을 서버에 전송하게 하여 사용자 계정에서 원치 않는 행동을 하게 만든다.
      • 요청 도메인과 쿠키에 설정된 도메인이 같은 경우만 쿠키 전송

 

 

Cookie로 로그인 상태 유지하기

 

  • 한번 로그인에 성공하면 HTTP Response에 쿠키를 담아서 브라우저에 전달한다.
  • 브라우저는 요청마다 Cookie를 함께 전송한다.
  • 보안상의 문제로 name=원욱이 아닌 userId=1과 같은 index 정보를 저장한다.
    • 이것 또한 보안문제가 있다.
  • 요구사항에 맞추어 세션 Cookie를 사용할지 영속 Cookie를 사용할지 결정한다.

  • 코드예시
    • 예시를 위해 ViewTemplate(Thymeleaf)을 사용하는 경우를 가정(SSR)
더보기

User 클래스

@Getter
public class User {
    // 식별자
    private Long id;
    // 이름
    private String name;
    // 나이
    private Integer age;
    // 로그인 ID
    private String userName;
    // 비밀번호
    private String password;

    public User(Long id, String name, Integer age, String userName, String password) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.userName = userName;
        this.password = password;
    }
}
  • User 클래스 설계

로그인 요청 DTO

// 필드 전체를 매개변수로 가진 생성자가 있어야 @ModelAttribute가 동작한다.
@Getter
@AllArgsConstructor
public class LoginRequestDto {
	// 사용자가 입력한 아이디
	@NotBlank	
	private final String userName;
	// 사용자가 입력한 비밀번호
	@NotNull
	private final String password;
}
  • 일반적으로 DTO는 클라이언트의 요청혹은 서버의 응답이기 때문에 변경되면 안된다.
    • final을 사용하여 불변 객체로 관리한다.
    • Java17 버전에 나온 record를 사용할 수 있다.

로그인 응답 DTO

@Getter
public class LoginResponseDto {
    private final Long id;
    // 이외 응답에 필요한 데이터들을 필드로 구성하면 된다.
    // 필요한 생성자
    public LoginResponseDto(Long id) {
        this.id = id;
    }
}

 

유저 조회 응답 DTO

@Getter
public class UserResponseDto {
		// 유저 식별자
    private final Long id;
    // 유저 이름
    private final String name;

    public UserResponseDto(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

 

 

HomeController

@Controller
@RequiredArgsConstructor
public class HomeController {

    private final UserService userService;

    @GetMapping("/home")
    public String home(
            // @CookieValue(required = true) 로 필수값(default) 설정
            // required = false 이면 필수값 아님.
            @CookieValue(name = "userId", required = false) Long userId, // String->Long 자동 타입컨버팅
            Model model
    ) {

        // 쿠키에 값이 없으면 로그인 페이지로 이동 -> 로그인 X
        if(userId == null) {
            return "login";
        }

        // 실제 DB에 데이터 조회 후 결과가 없으면 로그인 페이지로 이동 -> 일치하는 회원정보 X
        UserResponseDto loginUser = userService.findById(userId);

        if(loginUser == null) {
            return "login";
        }

        // 정상적으로 로그인 된 사람이라면 View에서 사용할 데이터를 model 객체에 데이터 임시 저장
        model.addAttribute("loginUser", loginUser);
        // home 화면으로 이동
        return "home";
    }
}

View에서는 model 객체에 담겨있는 loginUser 를 활용하여 변수로 사용할 수 있다.

 

UserController

@Controller
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/login")
    public String login(
            @Valid @ModelAttribute LoginRequestDto request,
            HttpServletResponse response // 쿠키값 세팅에 필요
    ) {
        // 로그인 유저 조회
        LoginResponseDto responseDto = userService.login(request.getUserName(), request.getPassword());

        if (responseDto.getId() == null) {
            // 로그인 실패 예외처리
            return "login";
        }

        // 로그인 성공 처리
        // 쿠키 생성, Value는 문자열로 변환하여야 한다.
        Cookie cookie = new Cookie("userId", String.valueOf(responseDto.getId()));

        // 쿠키에 값 세팅 (expire 시간을 주지 않으면 세션쿠키가 됨, 브라우저 종료시 로그아웃)
        // Response Set-Cookie: userId=1 형태로 전달된다.
        response.addCookie(cookie);
        
        // home 페이지로 리다이렉트
        return "redirect:/home";
    }

    @PostMapping("/logout")
    public String logout(
            HttpServletResponse response
    ) {
        Cookie cookie = new Cookie("userId", null);
        // 0초로 쿠키를 세팅하여 사라지게 만듬
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        // home 페이지로 리다이렉트
        return "redirect:/home";
    }

}
  1. 로그인 기능
    • 로그인에 성공하면 Cookie를 생성하고 HttpServletResponse 객체에 담는다.
      • Cookie 이름(Key)은 userId , 값(Value)은 회원 index 값을 담아둔다.
      • Set-Cookie: userId=1
    • 만료 시간을 지정하지 않으면 세션 쿠키로 만들어진다.
      • 브라우저 종료 전까지 userId 가 모든 요청 헤더의 Cookie에 담겨서 전달된다.
  2. 로그아웃 기능
    • 새로운 Cookie를 userId = null로 생성한다.
    • setMaxAge(0) 설정으로 만료시킨다.
    • 응답에 만료된 쿠키를 담아 보낸다.

UserService

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    public LoginResponseDto login(String userName, String password) {
        // 입력받은 userName, password와 일치하는 Database 조회
        Long index = userRepository.findIdByUserNameAndPassword(userName, password);

        return new LoginResponseDto(index);
    }

    public UserResponseDto findById(Long id) {

        return userRepository.findById(id);
    }
}

 

UserRepository

@Repository
public class UserRepository {

    private static final User USER1 = new User(1L, "wonuk", 100, "wonuk", "1234");
    private static final User USER2 = new User(2L, "wonuk2", 200, "wonuk2", "2345");
    private static final List<User> USERS = Arrays.asList(USER1, USER2);

    public Long findIdByUserNameAndPassword(String userName, String password) {
        return USERS.stream()
                .filter(user -> user.getUserName().equals(userName) && user.getPassword().equals(password))
                .map(User::getId)
                .findFirst()
                .orElse(null);
    }

    public UserResponseDto findById(Long id) {
        return USERS.stream()
                .filter(user -> Objects.equals(user.getId(), id))
                .map(user -> new UserResponseDto(user.getId(), user.getName()))
                .findFirst()
                .orElse(null);
    }
}

실제 Database와 연동하지 않고, 상수로 미리 만든 User를 사용한다.

 

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<h2>Login</h2>
<form th:action="@{/login}" method="post">
    <div>
        <label for="userName">Username:</label>
        <input type="text" id="userName" name="userName" required>
    </div>
    <div>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required>
    </div>
    <button type="submit">Login</button>
</form>

</body>
</html>

 

home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
<h1>Welcome Home!</h1>

<p th:text="'안녕하세요, ' + ${loginUser.name} + '님!'">Hello, User!</p>

<!-- 로그아웃 버튼 -->
<form th:action="@{/logout}" method="post" style="margin-top: 10px;">
    <button type="submit">Logout</button>
</form>

</body>
</html>

 

 

브라우저 테스트

http://localhost:8080/home

  • HomeController 호출
  • Cookie에 값이 없는 상태로 login 페이지가 반환된다.

DB에 저장된 userName과 password로 로그인

 

  • 로그인이 실패하면 login 페이지로 이동한다.
  • 로그인이 성공하면 home 페이지로 리다이렉트
  • 리 다이렉트되어 GET + /home 호출
  • Cookie에 저장된 user 식별자 값으로 DB 조회
  • 조회된 User를 Model에 추가
  • home 페이지에서 Model을 참조하여 화면 출력

 

 

Cookie 문제점

📌 Cookie는 보안에 취약하여 userId=1 형태의 방식으로 로그인을 구현하지 않는다.

  • = 쿠키에 민감한 정보를 직접 저장하거나 이를 기반으로 인증 로직을 구현하지 않는다
  • userId=1은 데이터를 숨기지 않고 직접 대놓고 저장하는 방식

 

Cookie 문제점

쿠키 값은 임의로 변경할 수 있다.

  • Client가 임의로 쿠키의 값을 변경하면 서버는 다른 유저로 인식한다.
  • userId = 임의로 수정

  • 브라우저 개발자도구(F12) → Application → Cookies → 값 수정 가능
  • 실제로는 암호화되어 저장되어있는 Value들을 볼 수 있다!

Cookie에 저장된 Data는 탈취되기 쉽다.

  • userId = 주민번호, userId = 인덱스 값
  • 쿠키는 네트워크 전송 구간에서 탈취될 확률이 매우 높다.
    • HTTPS를 사용하는 이유 중 하나에 속한다.
    • 민감한 정보를 저장하면 안된다.
  • 한번 탈취된 정보는 변경이 없다면 반영구적으로 사용할 수 있다.

 

보안 대처방법

  1. 쿠키에 중요한값을 저장하지 않는다.
  2. 사용자 별로 일반 유저나 해커들이 알아보지 못하는 값을 노출한다.
    • 일반적으로 암호화된 Token을 쿠키에 저장한다.
    • 서버에서 암호화된 Token과 사용자를 매핑해서 인식한다.
    • Token은 서버에서 관리한다.
  3. 토큰은 해커가 임의의 값을 넣어도 동작하지 않도록 만들어야 한다.
  4. 해커가 토큰을 탈취해도 사용할 수 없도록 토큰 만료시간을 짧게 설정한다.
  5. 탈취가 의심되는 경우 해당 토큰을 강제로 만료시키면 된다.
    • 접속기기 혹은 IP가 다른 경우 등

 

 

 

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] Session & Cookie의 관계  (0) 2024.12.31
[Net] Token & JWT  (0) 2024.12.29
[Net] MVC 패턴  (0) 2024.12.13
[Net] API 설계  (0) 2024.12.12
[Net] Rendering  (1) 2024.12.05

Template Engine

📌 동적인 웹 페이지를 생성하기 위해 사용되는 도구이며 템플릿을 기반으로 정적인 부분과 동적인 데이터를 결합하여 HTML, XML 등 의 문서를 생성하는 역할을 수행한다.

  •  우리가 흔히 말하는 UI(User Interface)를 만들며, SSR(Server Side Rendering)에 사용된다.
  • 정적 출력은 [안내 메시지]와 같은 모든 사용자에게 동일하게 보이는 데이터를 말하고 동적 데이터는 [사용자 이름]과 같은 사용자, 상황마다 다른 결과를 표현하는 데이터다.
  • 아래와 같이 서버에서 보내는 데이터를 html쪽에서 정의하는 방식으로 접근이 편리하게 만들어준다.
    <!-- 정적 출력 -->
    <h1>안녕하세요, 방문해주셔서 감사합니다!</h1>

    <!-- 동적 출력 -->
    <p>안녕하세요, <span th:text="${name}">이름</span>님!</p>


	[실제 출력]

    <!-- 정적 출력 -->
    <h1>안녕하세요, 방문해주셔서 감사합니다!</h1>

    <!-- 동적 출력 -->
    <p>안녕하세요, <span>철수</span>님!</p>

  • 템플릿 엔진이 나온 이유
    • 자바 코드로 HTML을 만들어 내는 것이 아닌 HTML 문서에 동적으로 변경해야 하는 부분만 자바 코드를 넣을 수 있다면 더 편리하다.
  • 대표적인 템플릿 엔진
    1. Thymeleaf
      • Spring과 통합이 잘 되어있다.
      • 다양한 기능을 포함하고 있다.
    2. JSP(Java Server Pages)
      • 예전엔 많이 사용했으나, 현재 안 쓰는 추세
    3. FreeMarker
    4. Velocity
    5. Mustache

 

MVC 패턴 개요

📌 Servlet이나 JSP만으로 비지니스 로직과 View Rendering 까지 모두 처리하면 너무 많은 역할을 하게 되고 유지보수가 굉장히 어려워져서(책임이 너무 많음) 고안된 패턴이다. Web Application은 일반적으로 MVC(Model View Controller) 패턴을 사용한다.

  • Servlet 문제점
    • 화면을 그리는 View 영역과 비지니스 로직이 Servlet 하나에 모두 섞여있다.
    • 책임을 너무 많이 가지고 있다.

Servlet 동작 순서

  1. 사용자가 Client(브라우저)를 통해 서버에 HTTP Request 즉, API 요청을 한다.
  2. 요청을 받은 Servlet 컨테이너는 HttpServletRequest, HttpServletResponse객체를 생성한다.
  3. 설정된 정보(URL, HTTP Method)를 통해 어떠한 Servlet에 대한 요청인지 찾는다.
  4. 해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method에 따라 doGet() 혹은doPost() 등의 메서드를 호출한다.
  5. 서버에서 응답을 생성한 뒤 HttpServletResponse객체에 응답을 담아 Client(브라우저)에 반환한다.
  6. 응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse객체를 소멸한다.
  • JSP
    • Servlet 코드에서 HTML을 만드는 부분인 View가 분리되었다.
    • 하지만 여전히 문제가 있는데, Servlet만 사용할 경우 View를 위한 코드와 비지니스 로직을 처리하는 코드가 Servlet에 모두 존재하며 유지보수가 어려워진다.
    • JSP를 사용하여 View를 분리하였지만 비지니스 로직의 일부가 JSP파일안에 존재한다. 여전히 책임이 많아 유지보수가 어렵다.

 

MVC 패턴

📌 하나의 Servlet이나 JSP로 처리하던 것들을 Model, View, Controller 영역으로 나눈것이다.

 

  • 핵심 내용
    • View가 분리된 이유의 핵심은 변경이다.
    • 기획이 변하지 않는 이상 비지니스 로직과 View의 수정 원인은 별개로 발생한다.
      • 화면 구성에 수정이 발생하면 View만 변경
      • 요구사항에 수정이 발생하는 경우 비지니스 로직 변경
    • 즉, 서로 연관이 없는 코드끼리 함께 존재할 필요가 없다. 완전히 분리하자

 

MVC 패턴 구조

  • Controller
    • 예시 코드에서 Servlet에 해당하는 영역이다.
    1. HTTP Request를 전달받아 파라미터를 검증한다.
    2. 비지니스 로직을 실행한다.
      • 비지니스 로직을 Controller에 포함하게되면 Controller가 너무 많은 역할을 담당하게 되어 일반적으로 Service Layer를 별도로 만들어서 처리한다.
      • Database와 상호작용 하는 Layer를 따로 구분하여 Repository Layer를 추가로 구성한다.
      • 위와 관련된 자세한 내용인 Layered Architecture는 다음 강의에서 알아보자.
      • Controller도 비지니스 로직을 포함할 수 있지만 일반적으로 Service Layer를 호출하는 역할을 담당한다.
    3. View에 전달할 결과를 조회하여 Model 객체에 임시로 저장한다.
  • Model
    1. View에 출력할 Data를 저장하는 객체이다.
    2. View는 비지니스 로직이나 Data 접근을 몰라도 되고 View Rendering에만 집중하면 된다.(책임 분리)
  • View
    • 예시 코드에서 JSP에 해당하는 영역이다.
    1. Model 객체에 담겨져 있는 Data를 사용하여 화면을 Rendering 한다.

 

 

MVC 패턴의 문제점

📌 MVC 패턴을 적용 후 View의 역할은 필요한 데이터를 Model 에서 참조하여 화면을 그리는 역할만 수행하면 된다. 하지만 Controller에 해당하는 부분은 여전히 문제를 가지고 있다.

  • 문제점
    1. dispatcher.forward(request, response) View로 이동하는 forward( 서버가 하나의 요청을 다른 리소스(예: JSP, 서블릿)로 전달 )가 항상 중복 호출된다.
    2. String path= “/WEB-INF/views/new-form.jsp” View의 path를 입력(중복 작업)한다.
      1. jsp 파일의 경로 혹은 이름이 바뀌면 해당 코드가 변경되어야 한다.
      2. JSP 이외의 확장자를 사용하려면 전체가 변경되어야 한다.
    3. 위의 내용을 설명하면, 클라에서 서버에 계속해서 같은 내용을 반복 요청한다는 것, 그런데 그걸 받는 컨트롤러가 하나에만 배치되면 되는데 다 각각 배치된다는 것.. (카페 알바에게 아메리카노 3잔시켰는데 다른 주문도 많은데 알바 3명이서 1개씩 만들고 있다...)
    4. HttpServletResponse 객체를 사용하는 경우가 적다. (JSP에서 모두 해결하기 때문)
      1. HttpServletRequest와 HttpServletResponse는 Test 코드를 작성하기도 매우 힘들다.
    5. 공통 기능이 추가될수록 Controller에서 처리해야 하는 부분들이 많아진다.
  • 공통 기능 처리
    • 모든 컨트롤러에서 공통으로 적용되는 기능을 뜻한다.
    ex) Log 출력, 인증, 인가 등

  • 공통 기능을 Method로 분리하여 각각의 컨트롤러에서 사용하면 되는것 아닌가요? 라고 생각이 가능하지만, 공통 기능으로 만들어놓은 Method 또한 항상 중복적으로 호출이 필요합니다. 또한, 사람인 개발자가 작업하다보면 Method를 호출하는 일을 깜빡 할수도 있고 Method가 많아지면 많아질수록 Controller의 책임이 점점 커진다. 이를 위해 아래의 대안이 나왔다.

 

프론트 컨트롤러 패턴

📌 Servlet(Controller)이 호출되기 전에 공통 기능을 하나의 Servlet에서 처리해주는 패턴이다. 프론트 컨트롤러(Servlet) 하나에 모든 클라이언트측 요청이 들어온다.

  • 프론트 컨트롤러의 역할
    1. 모든 요청을 하나의 프론트 컨트롤러가 받는다.
    2. 공통 기능을 처리한다.
    3. 요청을 처리할 수 있는 Controller를 찾아서 호출한다.(Controller Mapping)
    4. 프론트 컨트롤러를 제외한 나머지 컨트롤러는 Servlet을 사용하지 않아도 된다.
      • 일반 Controller들은 HttpServlet을 상속( HTTP 프로토콜 기반의 요청과 응답을 처리하기 위해 제공되는 클래스 )받거나, @WebServlet(URL 매핑 여기서는 url과 servlet과의 매핑을 말한다.)을 사용하지 않아도 된다. = 프론트 컨트롤러가 처리함
      • @WebServlet의 설명이 이해가 안될 수도 있는데, url은 말 그대로 주소 식별자다. url에 보내는 요청을 보고 어느 servlet에서 처리를 할지 정하고 해당 servlet에 배치(매핑)하는 과정은 완전별개의 문제이다.
  • 프론트 컨트롤러 의문점
    • 프론트 컨트롤러를 사용하면 모든 컨트롤러에서 같은 형태의 응답을 해야하는가?

    • 위 그림처럼 공통 처리 로직에 모든 컨트롤러가 연결되기 위해서는 모든 컨트롤러가 return 하는 결과의 형태가 동일해야 한다.
    • 하지만, Controller 마다 로직이나 응답해야하는 결과는 당연히 다를테고 응답을 동일하게 맞추려고 한다면 해당 애플리케이션은 확장성, 유지보수성을 잃는다.
    • 공통 로직에서 응답별로 퍼즐을 다시 하나하나 처리할 수 있으나 공통 부분의 책임이 너무 커지게된다. 또한, 컨트롤러에서 반환되는 결과가 달라지면 공통처리 부분의 변경또한 불가피하다.
    • 그래서 아래의 대안이 또 나왔다.

 

어댑터 패턴

📌 어댑터 패턴호환되지 않는 인터페이스를 가진 클래스들이 함께 동작할 수 있도록 중간에서 변환을 도와주는 디자인 패턴입니다. 주로, 이미 존재하는 코드와 새로운 코드 사이의 호환성 문제를 해결하는 데 사용됩니다.

  • 컨트롤러들은 동일한 인터페이스를 구현하도록 하고 해당 인터페이스와 공통 로직 사이에 어댑터를 두어 유연하게 만든다. 서로 다른 인터페이스를 갖는 두 클래스를 연결해주는 패턴이다.
  • 이해가 잘 안된다면 아래의 접은글을 참고
더보기
 

비유: 요리사, 레시피, 그리고 주방

  1. 요리사(각 컨트롤러)
    • 요리사는 특정 요리(기능)를 책임지고 있습니다.
    • 하지만 요리사는 "자신만의 레시피(방법)"만 알고, 다른 요리사들이 어떤 레시피를 사용하는지 알지 못할 수도 있습니다.
  2. 레시피(인터페이스 또는 요청 처리)
    • 레시피는 요리사가 따라야 하는 공통된 규칙(인터페이스)입니다.
    • 예를 들어, 레시피에 따라 어떤 순서로 재료를 조합할지 정의됩니다.
    • 모든 요리사가 이 레시피를 준수해야 주방이 동작을 관리하기 쉬워집니다.
  3. 주방(공통 로직 또는 프론트 컨트롤러)
    • 주방은 요리사들이 만든 다양한 레시피를 필요한 시점에 호출하고, 손님(사용자)에게 제공할 음식을 최종적으로 내놓습니다.
    • 주방은 모든 요리를 직접 만들지 않고, 요리사들에게 필요한 일을 분담시킵니다.

어댑터 패턴의 맥락에서

주방에서 레시피(인터페이스)를 공유하여 요리사들이 협업 가능하게 만드는 구조는 어댑터 패턴과 비슷합니다.

  • 주방(공통 처리 로직)과 요리사(개별 로직)가 서로 다른 방식을 사용하고 있다면, 어댑터(중간 번역기)를 통해 조율할 수 있습니다.

프론트 컨트롤러를 요리사와 주방에 비유하면

  1. 요리사 (각 컨트롤러)
    • @Controller를 사용한 일반 컨트롤러.
    • HTTP 요청이 들어오면, 요리사(컨트롤러)는 특정 요리를 준비하는 역할을 수행.
    • 클라이언트 요청에 따라 요리사는 자신의 비즈니스 로직에만 집중.
  2. 레시피 (인터페이스)
    • 요리사가 동작할 때 따라야 하는 규칙.
    • 예를 들어, Spring MVC에서는 @RequestMapping 또는 @GetMapping 등의 애너테이션이 이런 역할을 함.
  3. 주방 (프론트 컨트롤러)
    • Spring의 DispatcherServlet 같은 역할.
    • 모든 요청이 주방으로 들어오고, 주방은 적절한 요리사(컨트롤러)를 찾아 요청을 전달.
    • 공통적인 작업(예: 인증, 로깅, 예외 처리 등)을 주방에서 처리한 뒤 요리사에게 넘김.

  1. 컨트롤러(Handler)는 비지니스 로직을 처리하고 알맞은 결과를 반환한다.
  2. 어댑터는 공통 로직과 컨트롤러(Handler)가 자연스럽게 연결되도록 한다.
  3. 프론트 컨트롤러는 공통으로 처리되는 로직을 수행한다.
  • 어댑터 패턴 장점
    • 프론트 컨트롤러, 어댑터, 핸들러 모두 각자의 역할만 수행한다. (책임 분리)
    • 새로운 컨트롤러(Handler)가 추가되어도 컨트롤러와 어댑터만 추가한다면 공통 로직의 변경이 발생하지 않는다.

 

요약

  1. Servlet 사용
    • 비지니스 로직을 처리하는 코드와 화면을 그리는 View 코드가 함께 존재하는 문제
  2. JSP 사용
    • View 에 해당하는 코드를 분리하였지만, 여전히 비지니스 로직을 JSP에 포함하는 문제
  3. MVC 패턴 사용
    • 공통 로직을 처리하는것에 코드가 중복되는 문제
  4. 프론트 컨트롤러 패턴 사용
    • 공통 로직을 하나의 입구에서 처리하기 위해서 프론트 컨트롤러 패턴 적용
    • 각각의 핸들러 호출 후 응답을 프론트 컨트롤러에 맞게 변형시켜야 하는 문제
  5. Spring MVC 사용
    • 프론트 컨트롤러 패턴, 어댑터 패턴이 모두 적용된 현재
    • 우리가 사용하는 Spring을 이용한 Web Application 개발 방식에 사용됨
    • Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용되어 있다.

 

'CS ( Computer Science ) > 네트워크 (Networking)' 카테고리의 다른 글

[Net] Token & JWT  (0) 2024.12.29
[Net] Cookie  (1) 2024.12.28
[Net] API 설계  (0) 2024.12.12
[Net] Rendering  (1) 2024.12.05
[Net] Servlet  (1) 2024.12.04

+ Recent posts