카테고리 없음

[유레카 / 백엔드] TIL - 12 (Web)

coding-quokka101 2025. 12. 2. 14:06

 

📌 들어가며

안녕하세요! 멀티캠퍼스 유레카 3기 백엔드 과정을 수강 중인 개발자 지망생입니다.

오늘은 웹의 핵심 동작 원리에 대해 배웠어요. 그동안 당연하게 사용해온 로그인, API 호출, 쿠키 저장 같은 것들이 사실은 엄청나게 체계적인 시스템 위에서 돌아간다는 걸 깨달았습니다!

"여러분이 웹사이트에서 로그인 버튼을 누르는 순간, 브라우저부터 서버까지 얼마나 많은 일이 일어나는지 아세요? 

저도 처음엔 "뭐 그렇게 복잡해?"라고 생각했는데... 진짜 복잡하고 체계적이더라고요! 😅

특히 Cookie, Session, JWT, OAuth까지 배우면서 실무에서 왜 이런 기술들을 사용하는지 완벽하게 이해하게 됐습니다!


🎯 Today I Learned

✅ 브라우저 동작 방식 (렌더링 과정)
✅ Cookie & Session의 차이와 사용 이유
✅ REST API 설계 원칙
✅ HTTP Request Methods (GET, POST, PUT, DELETE)
✅ HTTP Status Code (200, 404, 500 등)
✅ JWT (JSON Web Token) 인증 방식
✅ OAuth 소셜 로그인
✅ Web Server vs WAS 차이

🌐 1단계: 브라우저는 어떻게 작동할까?

사진출처 : 브라우저 동작 원리 & 구조

 

주소창에 URL 입력하면 무슨 일이?

우리가 https://www.google.com을 입력하고 엔터를 치는 순간부터 엄청난 일들이 일어나요!

1️⃣ DNS 조회
   www.google.com → IP 주소 (예: 172.217.175.46) 변환

2️⃣ TCP 연결
   브라우저와 서버 간 연결 수립 (3-way handshake)

3️⃣ HTTP 요청 전송
   GET / HTTP/1.1
   Host: www.google.com

4️⃣ 서버 응답
   HTML, CSS, JavaScript 파일 전송

5️⃣ 브라우저 렌더링
   받은 파일을 화면에 그림!

 

브라우저 렌더링 과정 (중요!)

HTML 파싱 → DOM 트리 생성
    ↓
CSS 파싱 → CSSOM 트리 생성
    ↓
DOM + CSSOM → 렌더 트리 생성
    ↓
레이아웃 계산 (위치, 크기 결정)
    ↓
페인팅 (실제로 화면에 그림)

왜 중요한가?

개발자가 이걸 알면:

  • ✅ 성능 최적화 가능 (불필요한 리렌더링 방지)
  • ✅ JavaScript로 DOM 조작 시 효율적인 코드 작성
  • ✅ CSS 선택자 최적화

사진출처 : [Web] 브라우저 렌더링 과정


🍪 2단계: Cookie와 Session - 상태를 기억하는 방법

사진 출처: What Are Session Cookies? – A Comprehensive Guide for 2024

HTTP는 Stateless (무상태)다!

문제 상황:

1. 사용자가 로그인 → 서버 인증 ✅
2. 다른 페이지 이동 → 서버가 사용자를 모름 ❌

왜? HTTP는 각 요청을 독립적으로 처리!
이전 요청을 기억하지 않음!

그래서 Cookie와 Session이 필요해요!

Cookie란?

// 쿠키 설정 (클라이언트 측)
document.cookie = "username=김철수; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/";

// 쿠키 읽기
console.log(document.cookie);
// 출력: "username=김철수"

쿠키의 특징:

  • 🍪 클라이언트(브라우저)에 저장
  • 🍪 최대 4KB 크기 제한
  • 🍪 만료 시간 설정 가능
  • 🍪 도메인별로 관리

사용 예시:

  • "오늘 하루 이 창 보지 않기"
  • 쇼핑몰 장바구니 (비로그인 상태)
  • 사용자 선호 설정 (언어, 테마)

Session이란?

// Spring Boot에서 세션 사용
@GetMapping("/login")
public String login(HttpSession session) {
    // 세션에 사용자 정보 저장
    session.setAttribute("userId", "user123");
    session.setAttribute("username", "김철수");
    
    return "로그인 성공!";
}

@GetMapping("/mypage")
public String mypage(HttpSession session) {
    // 세션에서 사용자 정보 가져오기
    String userId = (String) session.getAttribute("userId");
    
    if (userId == null) {
        return "로그인이 필요합니다";
    }
    
    return "환영합니다, " + userId + "님!";
}

세션의 특징:

  • 🔒 서버에 저장 (보안 ⬆️)
  • 🔒 저장 용량 제한 없음
  • 🔒 Session ID만 쿠키로 전달
  • 🔒 서버 재시작 시 세션 사라짐 (일반적으로)

사진출처 : What Are Session Cookies? – A Comprehensive Guide for 2024

Cookie vs Session 완벽 비교

구분 Cookie Session

저장 위치 클라이언트 (브라우저) 서버
보안 낮음 (탈취 위험) 높음
속도 빠름 상대적으로 느림 (서버 조회)
용량 4KB 제한 제한 없음
생명주기 만료 시간 설정 가능 브라우저 종료 시 삭제 (일반적)
사용 예 자동 로그인, 장바구니 로그인 정보, 중요 데이터

🔌 3단계: REST API - 서버와 통신하는 표준

REST API란?

사진출처 : https://raw.githubusercontent.com/Codecademy/articles/0b631b51723fbb3cc652ef5f009082aa71916e63/images/rest_api.svg

 

REST는 네트워크 상에서 클라이언트와 서버 사이의 통신 방식 중 하나로, HTTP의 장점을 최대한 활용하는 아키텍처입니다.

핵심 규칙 2가지:

  1. URI는 자원(Resource)을 표현
  2. 행위(Verb)는 HTTP Method로 표현
❌ 나쁜 예:
GET /getUserInfo?id=1
POST /createUser
GET /deleteUser?id=1

✅ 좋은 예:
GET /users/1          → 사용자 정보 조회
POST /users           → 사용자 생성
DELETE /users/1       → 사용자 삭제

HTTP Request Methods

HTTP 프로토콜은 GET, POST, PUT, DELETE와 같은 메서드를 제공하며, 이는 CRUD 작업에 대응됩니다.

HTTP Method CRUD 설명 예시

GET Read 데이터 조회 GET /users
POST Create 데이터 생성 POST /users
PUT Update 데이터 전체 수정 PUT /users/1
PATCH Update 데이터 일부 수정 PATCH /users/1
DELETE Delete 데이터 삭제 DELETE /users/1
// JavaScript로 REST API 호출하기

// GET - 사용자 목록 조회
fetch('https://api.example.com/users')
    .then(response => response.json())
    .then(data => console.log(data));

// POST - 새 사용자 생성
fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        name: '김철수',
        email: 'kim@example.com'
    })
});

// PUT - 사용자 정보 전체 수정
fetch('https://api.example.com/users/1', {
    method: 'PUT',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        name: '김철수',
        email: 'newemail@example.com',
        age: 25
    })
});

// DELETE - 사용자 삭제
fetch('https://api.example.com/users/1', {
    method: 'DELETE'
});

사진출처 : RESTful API의 설계 원칙과 구현 예시 : 네이버 블로그

 

HTTP Status Code

REST API에서 HTTP 상태 코드는 요청의 성공 여부와 실패 원인을 명확하게 전달합니다.

2xx - 성공

  • 200 OK: 요청 성공
  • 201 Created: 리소스 생성 성공
  • 204 No Content: 성공했지만 반환할 데이터 없음

4xx - 클라이언트 오류

  • 400 Bad Request: 잘못된 요청
  • 401 Unauthorized: 인증 필요
  • 403 Forbidden: 권한 없음
  • 404 Not Found: 리소스 없음

5xx - 서버 오류

  • 500 Internal Server Error: 서버 내부 오류
  • 502 Bad Gateway: 게이트웨이 오류
  • 503 Service Unavailable: 서비스 사용 불가
// Spring Boot에서 상태 코드 설정
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        
        if (user == null) {
            return ResponseEntity.status(404).build();  // 404 Not Found
        }
        
        return ResponseEntity.ok(user);  // 200 OK
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User created = userService.create(user);
        return ResponseEntity.status(201).body(created);  // 201 Created
    }
}

🔐 4단계: JWT - 현대적인 인증 방식

JWT가 왜 필요할까?

기존 세션 방식의 문제:

문제 1: 서버 확장이 어렵다
- 세션을 서버 메모리에 저장
- 서버가 여러 대면 세션 공유가 복잡

문제 2: 서버 부하
- 매 요청마다 세션 저장소 조회 필요

문제 3: 모바일 앱에서 불편
- 쿠키 관리가 복잡함

JWT를 사용하면 서버측에서 유저의 세션을 유지할 필요가 없어 서버 자원을 많이 아낄 수 있습니다.

JWT 구조

JWT는 Header, Payload, Signature 세 부분으로 구성되며, 점(.)으로 구분됩니다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJuYW1lIjoi6rmA7LKg7IiYIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
     [Header]                    [Payload]                [Signature]

1. Header (헤더)

{
  "alg": "HS256",
  "typ": "JWT"
}

2. Payload (페이로드)

{
  "userId": "123",
  "name": "김철수",
  "exp": 1735689600
}

3. Signature (서명)

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)

출처 : JWT Authentication 완전 정복기!

 

JWT 인증 흐름

1️⃣ 로그인
   사용자: ID/PW 전송
   서버: 검증 후 JWT 발급

2️⃣ JWT 저장
   클라이언트: localStorage 또는 Cookie에 저장

3️⃣ API 요청
   클라이언트: Authorization 헤더에 JWT 포함
   예) Authorization: Bearer eyJhbGci...

4️⃣ 서버 검증
   서버: JWT 서명 확인
   서명 일치 → 요청 처리
   서명 불일치 → 401 Unauthorized

실제 코드 예시:

// Node.js + jsonwebtoken 라이브러리
const jwt = require('jsonwebtoken');

// 1. JWT 발급 (로그인 시)
app.post('/login', (req, res) => {
    const { email, password } = req.body;
    
    // 사용자 인증 (예시)
    if (email === 'test@test.com' && password === '1234') {
        // JWT 생성
        const token = jwt.sign(
            { 
                userId: '123',
                email: 'test@test.com'
            },
            'my_secret_key',  // 비밀 키
            { expiresIn: '1h' }  // 1시간 유효
        );
        
        res.json({ token });
    } else {
        res.status(401).json({ message: '인증 실패' });
    }
});

// 2. JWT 검증 (보호된 API)
app.get('/api/protected', (req, res) => {
    const token = req.headers.authorization?.split(' ')[1];  // Bearer 제거
    
    try {
        const decoded = jwt.verify(token, 'my_secret_key');
        res.json({ message: '인증 성공!', user: decoded });
    } catch (error) {
        res.status(401).json({ message: '유효하지 않은 토큰' });
    }
});

사진출처: https://raw.githubusercontent.com/wlswo/wlswo.github.io/aa3500cfea40f3e8adaa3c53d2deda49bbb556c7/assets/images/SideProject/side%234/session3.png

 

JWT의 장단점

장점:

  • ✅ 서버 확장이 쉬움 (Stateless)
  • ✅ 서버 부하 감소 (세션 저장소 불필요)
  • ✅ 모바일 앱에 적합
  • ✅ 서로 다른 도메인 간 인증 가능

단점:

  • ❌ 토큰 길이가 길어 네트워크 부하
  • ❌ 토큰 탈취 시 만료까지 막을 방법 없음
  • ❌ Payload는 암호화되지 않음 (중요 정보 저장 X)

해결 방법:

// Access Token + Refresh Token 패턴
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });  // 짧은 만료
const refreshToken = jwt.sign(payload, secret, { expiresIn: '7d' });  // 긴 만료

// Access Token 만료 시 Refresh Token으로 재발급

🔗 5단계: OAuth - 소셜 로그인

OAuth란?

"구글 계정으로 로그인", "카카오 계정으로 로그인"
→ 이게 바로 OAuth!

OAuth는 사용자가 비밀번호를 제공하지 않고도 다른 웹사이트나 애플리케이션의 자원에 접근할 수 있도록 허용하는 인증 프로토콜입니다.

OAuth 인증 흐름

1️⃣ 사용자가 "구글로 로그인" 클릭

2️⃣ 우리 서비스 → 구글 로그인 페이지로 리다이렉트

3️⃣ 사용자가 구글에 로그인

4️⃣ 구글이 우리 서비스에 Authorization Code 전달

5️⃣ 우리 서비스 → 구글에 Access Token 요청

6️⃣ Access Token으로 사용자 정보 조회

7️⃣ 우리 서비스에서 회원 가입 or 로그인 처리

사진출처: OAuth2.0란?

 

왜 OAuth를 쓸까?

  • ✅ 사용자: 회원가입 절차 생략 (편리함)
  • ✅ 서비스: 비밀번호 저장 부담 감소 (보안)
  • ✅ 모두: 신뢰할 수 있는 업체의 인증 시스템 활용
// Passport.js로 구글 OAuth 구현 (Node.js)
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: "http://localhost:3000/auth/google/callback"
  },
  function(accessToken, refreshToken, profile, cb) {
    // 사용자 정보로 로그인 처리
    User.findOrCreate({ googleId: profile.id }, function (err, user) {
      return cb(err, user);
    });
  }
));

// 라우트
app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

app.get('/auth/google/callback', 
  passport.authenticate('google', { failureRedirect: '/login' }),
  function(req, res) {
    res.redirect('/');  // 로그인 성공!
  }
);

🖥️ 6단계: Web Server vs WAS

차이점이 뭘까?

Web Server (웹 서버)
- 정적 컨텐츠 제공 (HTML, CSS, JS, 이미지)
- 예: Apache, Nginx
- 빠르고 효율적

WAS (Web Application Server)
- 동적 컨텐츠 제공 (비즈니스 로직 처리)
- 예: Tomcat, JBoss
- 데이터베이스 연동, 복잡한 연산

 

실무에서는 같이 사용!

사용자 → Web Server → WAS → Database
          (Nginx)     (Tomcat)

Nginx 역할:
- 정적 파일 직접 제공 (빠름!)
- 로드 밸런싱
- SSL/TLS 처리

Tomcat 역할:
- Java 애플리케이션 실행
- API 요청 처리
- 데이터베이스 쿼리

사진출처 : https://gmlwjd9405.github.io/images/web/web-service-architecture.png

 

왜 분리할까?

  • ✅ 성능: 정적 파일은 Web Server가 빠르게 처리
  • ✅ 보안: Web Server가 외부 요청 1차 필터링
  • ✅ 확장성: WAS만 늘려서 처리 능력 향상

💡 오늘 나는 무엇을 잘했는가 (성취)

웹의 전체 그림을 그릴 수 있게 됨!

처음에는 "로그인하면 서버에서 확인하고 끝"이라고 생각했는데, 실제로는:

브라우저 → DNS 조회 → TCP 연결 → HTTP 요청
    ↓
서버에서 세션 or JWT 검증
    ↓
데이터베이스 조회
    ↓
HTTP 응답 (JSON)
    ↓
브라우저 렌더링

이 모든 과정을 이해하게 됐어요!

실습 코드 작성 성공

수업 시간에 배운 JWT 인증을 Node.js로 직접 구현해봤어요. 처음엔 막막했는데, 토큰 생성하고 검증하는 과정을 직접 해보니 완전히 이해가 됐습니다!

특히 jwt.sign()과 jwt.verify() 함수를 사용하는 게 생각보다 간단해서 놀랐어요.


🔧 어떤 문제를 겪었고, 어떻게 개선할까 (개선점)

문제: JWT vs Session 헷갈림

"그럼 언제 Session 쓰고 언제 JWT를 써야 해?"가 가장 헷갈렸어요.

개선 계획:

Session을 쓸 때:
- 전통적인 웹 애플리케이션
- 서버가 1대 (또는 세션 공유 쉬운 환경)
- 실시간 세션 관리 필요 (즉시 로그아웃 등)

JWT를 쓸 때:
- RESTful API 서버
- 마이크로서비스 아키텍처
- 모바일 앱
- 서버 확장이 필요한 경우

이렇게 정리하니까 명확해졌어요!

문제: OAuth 실습 환경 설정 어려움

구글 OAuth를 실습하려면 구글 클라우드 콘솔에서 Client ID를 발급받아야 하는데, 이 과정이 복잡했어요.

해결 방법:

  • 📝 구글 OAuth 설정 과정을 단계별로 스크린샷으로 정리
  • 📝 다음에 다른 소셜 로그인(카카오, 네이버)도 도전해볼 예정

📚 오늘 배운 것 (학습)

1. HTTP는 Stateless지만 상태를 기억해야 한다

HTTP: "난 너 몰라, 매번 처음 보는 사람이야"
Cookie/Session/JWT: "아니야, 이 사람 아까 본 적 있어!"

웹 개발의 핵심은 어떻게 상태를 관리하느냐였어요!

2. REST API는 '명사 + HTTP Method'

❌ /getUser
❌ /createPost
❌ /deleteComment

✅ GET /users
✅ POST /posts
✅ DELETE /comments/1

URI는 자원만 표현하고, 행위는 HTTP Method로!

3. JWT는 은탄환이 아니다

JWT가 좋다고 해서 무조건 쓰는 게 아니라, 상황에 맞게 선택해야 해요.

소규모 프로젝트: Session도 충분
대규모 + 모바일: JWT 추천
극한의 보안: OAuth + JWT + 추가 보안 조치

4. 브라우저 렌더링 최적화의 중요성

// ❌ 나쁜 예 - DOM 조작 반복
for (let i = 0; i < 1000; i++) {
    document.getElementById('list').innerHTML += `<li>${i}</li>`;
}
// 리플로우가 1000번 발생!

// ✅ 좋은 예 - 한 번에 처리
let html = '';
for (let i = 0; i < 1000; i++) {
    html += `<li>${i}</li>`;
}
document.getElementById('list').innerHTML = html;
// 리플로우가 1번만 발생!

😊 좋았던 점 & 😅 아쉬웠던 점

좋았던 점

👍 실무 중심의 설명

"왜 이 기술이 필요한가?"를 항상 설명해주셔서 이해가 빨랐어요. 특히 Session의 단점 → JWT 등장 흐름이 논리적이었습니다!

👍 체계적인 커리큘럼

브라우저 → Cookie/Session → REST API → JWT → OAuth 순서로 배우니까, 각각이 왜 필요한지 자연스럽게 이해됐어요.

👍 보안 인식 향상

JWT Payload에 중요 정보를 넣으면 안 된다는 것, Cookie에는 HttpOnly 설정을 해야 한다는 것 등 보안 관련 팁을 많이 배웠어요!

아쉬웠던 점

😢 실습 시간 부족

OAuth 같은 경우 이론은 이해했는데, 실제로 여러 소셜 로그인을 구현해보는 시간이 부족했어요. 주말에 개인적으로 실습해봐야겠어요!

😢 HTTPS/SSL 관련 내용

HTTP까지는 배웠는데, 실무에서 필수인 HTTPS와 SSL 인증서는 다루지 못해 아쉬웠어요.

😢 대규모 시스템 사례

Netflix, Facebook 같은 대기업에서는 어떻게 수억 명의 인증을 처리하는지 궁금한데, 아직 잘 모르겠어요.


🎓 나만의 학습 팁

1. Postman으로 API 테스트하기

1. Postman 설치
2. Collection 만들기
3. GET, POST, PUT, DELETE 요청 직접 보내보기
4. Authorization 헤더에 JWT 넣어보기

이론만 보지 말고 직접 API를 쳐보는 게 진짜 공부예요!

2. 크롬 개발자 도구 활용

F12 → Network 탭

여기서 볼 수 있는 것:
- HTTP Method (GET, POST, PUT, DELETE)
- Status Code (200, 404, 500)
- Request Headers (Authorization, Content-Type)
- Response Body (JSON 데이터)
- Cookie 정보

실제 웹사이트의 네트워크 통신을 보면서 배우면 훨씬 이해가 빠릅니다!

3. 브라우저 렌더링 최적화 체크리스트

// 성능 측정 - Performance API
const start = performance.now();

// 어떤 작업...
for (let i = 0; i < 1000000; i++) {
    // ...
}

const end = performance.now();
console.log(`실행 시간: ${end - start}ms`);

코드의 성능을 직접 측정해보면서 최적화하는 습관을 들이세요!

4. 보안 체크리스트

✅ JWT는 localStorage보다 HttpOnly Cookie에 저장 (XSS 공격 방지)
✅ CORS 설정 확인 (허용된 도메인만 API 접근)
✅ JWT Payload에 민감한 정보 넣지 않기
✅ HTTPS 사용 (중간자 공격 방지)
✅ Rate Limiting 적용 (무차별 대입 공격 방지)

5. 나만의 정리 방법

## REST API 설계 규칙

1. 명사 사용 (동사 X)
2. 소문자 사용
3. 언더바(_) 대신 하이픈(-) 사용
4. 마지막에 슬래시(/) 포함하지 않기

예시:
✅ /api/users
✅ /api/blog-posts
❌ /api/getUsers
❌ /api/blog_posts/

이렇게 핵심만 정리해서 Notion에 저장해두면, 나중에 찾아보기 편해요!


🚀 다음 학습 목표

📌 단기 목표 (이번 주)
   ✓ JWT를 실제 Spring Boot 프로젝트에 적용
   ✓ Postman으로 REST API 100개 테스트해보기
   ✓ OAuth 2.0으로 구글/카카오 로그인 구현

📌 중기 목표 (이번 달)
   ✓ HTTPS/SSL 인증서 설정 공부
   ✓ Redis로 세션 관리 실습
   ✓ Nginx + Tomcat 연동 실습
   ✓ CORS 문제 해결 경험 쌓기

📌 장기 목표 (부트캠프 기간)
   ✓ 포트폴리오에 JWT + OAuth 인증 시스템 구현
   ✓ RESTful API 설계 가이드 문서 작성
   ✓ 성능 최적화 (캐싱, CDN) 적용

🎬 마치며

오늘 수업을 듣기 전에는 "웹? 그냥 요청 보내고 응답 받으면 끝 아닌가?"라고 생각했어요.

하지만 하나하나 파고들수록  정교한 시스템이라는 걸 깨달았습니다.

특히 인상 깊었던 부분은:

"보안은 선택이 아니라 필수다"

Cookie, Session, JWT, OAuth... 모든 기술이 결국 **"어떻게 안전하게 사용자를 인증할 것인가?"**에 대한 고민이었어요.

그리고 REST API 설계 원칙을 배우면서, 좋은 API는 직관적이고 일관성 있게 설계되어야 한다는 걸 배웠습니다.

좋은 API = 문서 없이도 이해할 수 있는 API

앞으로는 단순히 "작동하는 코드"가 아니라 **"안전하고 확장 가능하며 이해하기 쉬운 코드"**를 짜는 개발자가 되고 싶어요! 💪

웹의 기초를 탄탄하게 다진 하루였습니다!

 

💬 여러분의 인증 방식은?

Session? JWT? 아니면 둘 다 사용? 실무에서 어떤 방식을 쓰는지 공유해주세요! 🙏