Backend/SpringBoot

[Spring Security] 쿠키 및 세션

Dean83 2025. 12. 12. 14:43

쿠키 및 세션은 편리하나, 보안위험이 매우 크다. 보안 관련해선 여기를 보면 된다. (https://dean83.tistory.com/380)

기본적으로 한계가 있기 때문에 주로 JWT 같은 토큰 기반으로 인증을 한다. 

쿠키

  • 서버가 사용자의 브라우저에 저장을 요청하는 작은 텍스트 데이터 조각.
  • 탈취 될 수 있으므로, 보안 속성 설정이 필수임.
    • Secure 설정을 통해 Https 프로토콜 에서만 쿠키를 전송하도록 강제해야 한다.
    • HttpOnly 설정을 통해 자바스크립트에서 쿠키 접근을 원천 차단 할 수 있다.
    • CSRF 공격 방어를 위해 Strict 옵션을 이용하거나, x-csrf-token 값을 이용할 수 있다.

 

  • 주 목적은 서버가 클라이언트를 실별 하기 위한 수단임
    • HTTP 통신은 상태를 저장하지 않으므로 사용
  • 구조 : key=value 형식의 문자열 쌍으로 구성
    • 예 : theme=dark
  • Set-Cookie 명령을 통해 저장
    • Response header 에 서버가 프론트엔드로 리턴할때 담아서 리턴
    • 생명주기 및 전송범위를 지정 할 수 있음.
      • Expires(만료날짜), Max-Age(받은 후 부터의 지속시간) 속성이 있는 경우 해당 기간동안 브라우저가 종료되어도 보관
        • 해당 값이 없으면 브라우저 종료시 자동으로 삭제됨.
      • Domain : 쿠키가 전송될 서버 도메인 지정 (지정을 안하면 현재 도메인)
      • Path : 서버내의 URL 경로 지정. (지정 안하면 /)

아래는 Springboot 에서 추가하는 법에 대한 예 이다.

import org.springframework.http.ResponseCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
public class CookieController {

    @GetMapping("/set-cookie")
    public ResponseEntity<String> setCookie() {

        ResponseCookie cookie = ResponseCookie.from("userToken", "abc123")
                .domain("example.com")       // 쿠키가 전송될 도메인
                .path("/")                   // 쿠키가 유효한 path
                .maxAge(60 * 60 * 24)        // 1 day (expires 대신 maxAge 사용 권장)
                .httpOnly(true)              // JS 접근 불가
                .secure(true)                // HTTPS에서만 전송
                .sameSite("Strict")          // SameSite 옵션
                .build();

        return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, cookie.toString())
                .body("cookie set");
    }
}

 

세션

세션의 경우, 쿠키와 다르게 상태를 서버에 저장한다. 따라서 민감한 정보나 보안이 필요한 정보를 세션으로 저장한다.

메모리, DB, 세션스토어(Redis 등 외부 저장소) 같은 곳에 저장을 할 수 있다. SessionID를 통해 사용자 인증을 할 수 있다.

임시 데이터로 사용되며, 주로 세션스토어를 통해 Redis나 Memcached에 저장하여 사용(다중 서버간 공유를 위해)한다. 인증이 필요한 접근인 경우 매번 SessionID를 저장한 곳에서 가져와 비교해야 한다.

 

* Redis나 Memcached등 외부저장소를 이용하기 위해서는 Spring Session 을 이용하여 설정 하면 된다.

 

세션ID는 각 브라우저 별로 동일한 유저가 로그인 했다 하더라도 각각 발급을 한다. 

즉, 다음의 과정을 거친다. 

 

로그인 -> SessionID 저장 -> 쿠키에 해당 SessionID 전송 -> 다음 요청시 전달 받은 SessionID 유효성 검사 -> 다음 응답

-> 로그아웃 요청시, 세션 저장소에서 해당 SessionID 삭제 -> 서버가 클라이언트 응답을 통해 쿠키를 즉시 만료하여 삭제 시킴

(Set-Cookie: sessionid=; Max-Age=0;)

 

세션을 발급하는 예는 다음과 같다. 

@PostMapping("/login")
public ResponseEntity<String> login(
        @RequestParam String username,
        HttpServletRequest request
) {
    // 세션 생성 (없으면 생성)
    HttpSession session = request.getSession(true);

    // 세션에 값 저장
    session.setAttribute("username", username);
    session.setAttribute("loginTime", System.currentTimeMillis());

    // 세션 ID
    String sessionId = session.getId();

    return ResponseEntity.ok("세션 발급 완료! sessionId = " + sessionId);
}

 

세션을 확인하는 예는 다음과 같다.

@GetMapping("/me")
public ResponseEntity<String> me(HttpServletRequest request) {

    // 기존 세션 가져오기 (없으면 null)
    HttpSession session = request.getSession(false);

    if (session == null) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body("세션이 없습니다. 로그인하세요.");
    }

    String username = (String) session.getAttribute("username");

    if (username == null) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body("세션 정보가 유효하지 않습니다.");
    }

    return ResponseEntity.ok("로그인한 사용자: " + username);
}

 

로그아웃하여 세션아이디를 삭제하는 예는 다음과 같다.

@PostMapping("/logout")
public ResponseEntity<String> logout(HttpServletRequest request) {
    HttpSession session = request.getSession(false);

    if (session != null) {
        session.invalidate();   // 세션 삭제
    }

    return ResponseEntity.ok("로그아웃 완료!");
}

 

 

쿠키와 연계하여, 기본적으로 JSESSIONID 쿠키를 알아서 발급해 주나, 커스터마이징을 할 수 있다. 
아래는 커스터마이징의 예 이다.

import jakarta.servlet.SessionCookieConfig;
import jakarta.servlet.ServletContext;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SessionCookieCustomizer implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

        SessionCookieConfig config = servletContext.getSessionCookieConfig();

        config.setName("JSESSIONID");       // 기본값
        config.setHttpOnly(true);           // 자바스크립트 접근 금지
        config.setSecure(true);             // https에서만 전송
        config.setPath("/");                // 경로
        config.setDomain("example.com");    // 필요하면 설정
        config.setMaxAge(60 * 60 * 24);     // 1일
    }
}