[2/5] TIL - 쿠키, 세션, JWT

2025. 2. 5. 22:37개발 회고/TIL

쿠키란?

'사용자의 웹 브라우저에' 저장되는 정보로 사용자의 상태 혹은 세션을 유지하거나 사용자 경험을 개선하기 위해 사용된다.

 

쿠키를 사용하는 이유

  1. HTTP는 Stateless, Connectionless 특성을 가지고 있다.
  2. Client가 재요청시 Server는 이전 요청에 대한 정보를 기억하지 못한다.
  3. 로그인과 같이 상태를 유지해야 하는 경우가 발생한다.
  4. Request에 사용자 정보를 포함하면 해결이 된다.
    • 로그인 후에는 사용자 정보와 관련된 값이 저장되어 있어야한다.
  5. 브라우저를 완전히 종료한 뒤 다시 열어도 사용자 정보가 유지되어야 한다.

 

로그인 시

로그인시 전달된 ID, Password로 User 테이블 조회하여 일치여부 확인

일치한다면 Set-Cookie를 활용해 Cookie에 사용할 값 저장

  • Cookie는 보안에 취약하다.

 

로그인 성공 후

로그인 이후에는 모든 요청마다 Request Header에 항상 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를 사용할지 결정한다.

 

요구사항 설정

요구사항1. 로그인한 회원이면 home 페이지로 이동한다.

요구사항2. home 페이지를 보려면 로그인이 필수이다.

요구사항3. 로그인하지 않은 회원이면 login 페이지로 이동한다.

요구사항4. 브라우저 종료 시 로그아웃 된다.(Session Cookie)

 

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;
    }
}

 

로그인 요청 dto

// 필드 전체를 매개변수로 가진 생성자가 있어야 @ModelAttribute가 동작한다.
@Getter
@AllArgsConstructor
public class LoginRequestDto {
	// 사용자가 입력한 아이디
	@NotBlank	
	private final String userName;
	// 사용자가 입력한 비밀번호
	@NotNull
	private final String password;
}

 

로그인 응단 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 어노테이션으로 userId 키값을 Long userId에 맵핑
            @CookieValue(name = "userId", required = false) Long userId, // String->Long 자동 타입컨버팅
            Model model
    ) {

        // 쿠키에 값이 없으면 로그인 페이지로 이동 -> 로그인 X
        // /home에 접근하려면 쿠키에 값이 있어야 함
        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";
    }
}

 

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는 문자열로 변환하여야 한다.
        // id 값을 문자열로 바꾸어서 쿠키에 userId 키값의 데이터로 저장
        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);
    }
}

 

쿠키의 문제점

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

  1. 쿠키 값은 임의로 변경할 수 있다.
    • Client가 임의로 쿠키의 값을 변경하면 서버는 다른 유저로 인식한다.
    • userId = 임의로 수정
    • 브라우저 개발자도구(F12) → Application → Cookies → 값 수정 가능
    • 실제로는 암호화되어 저장되어있는 Value들을 볼 수 있다!
  2. Cookie에 저장된 Data는 탈취되기 쉽다.
    • userId = 주민번호, userId = 인덱스 값
    • 쿠키는 네트워크 전송 구간에서 탈취될 확률이 매우 높다.
      • HTTPS를 사용하는 이유 중 하나에 속한다.
      • 민감한 정보를 저장하면 안된다.
    • 한번 탈취된 정보는 변경이 없다면 반영구적으로 사용할 수 있다.

 

보안 대처방법

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

 

세션이란?

Cookie를 사용한 방식은 여러가지 보안 문제가 있다. 결국 보안 문제를 해결하려면 중요한 정보는 모두 서버에서 저장해야한다. Client와 서버는 예측이 불가능한 임의의 값으로 연결해야 한다.

'서버에서 중요한 정보를 보관하며' 로그인 연결을 유지하는 방법을 Session 이라고 한다. 앞서 배운 Cookie는 중요한 정보를 Client측에서 보관하고 있는것이다.

 

  1. 로그인에 성공하면 Server에서 임의로 만든 Session ID를 생성한다.
    • Session ID는 예측이 불가능해야 한다.
    • UUID와 같은 값을 활용한다.
  2. 생성된 Session ID와 조회한 User 인스턴스를 서버의 Session 저장소에 저장한다.
    • 서버에 유저와 관련된 중요한 정보를 저장한다.

 

세션 동작 순서

  1.  로그인
    • 상태유지를 위해 Cookie를 사용한다.
      • 서버는 클라이언트에 Set-Cookie: SessionId=임의생성값 을 전달한다.
      • 클라이언트는 Cookie 저장소에 전달받은 SessionId 값을 저장한다.
    • Sessions을 사용하면 유저와 관련된 정보는 클라이언트에 없다.
  2. 로그인 이후 요청
    • 클라이언트는 모든 요청에 Cookie 의 SessionId를 전달한다.
    • 서버에서는 Cookie를 통해 전달된 SessionId로 Session 저장소를 조회한다.
    • 로그인 시 저장하였던 Session 정보를 서버에서 사용한다.

 

Servlet의 HttpSession

Servlet이 공식적으로 지원하는 Session인 HttpSession은 Session 구현에 필요한 다양한 기능들을 지원한다.

 

상수를 클래스로 관리

// 추상클래스 -> O
public abstract class Const {
	public static final String LOGIN_USER = "loginUser";
}

// 인터페이스 -> O
public interface Const {
	String LOGIN_USER = "loginUser";
}

// 클래스 -> X
public class Const { // new Const();
	public static final String LOGIN_USER = "loginUser";
}

// Const.LOGIN_USER; O
// new Const(); X
  • 상수로 활용할 class는 인스턴스를 생성(new)하지 않는다.
  • abstract 추상클래스 혹은 interface 로 만들어 상수값만 사용하도록 만들어두면 된다.

 

Servlet을 통해 HttpSession 을 생성하게되면 SessionIdJSESSIONID로 생성되고 JSESSIONID의 Value는 예측 불가능한 랜덤값으로 생성된다.

 

SessionUserController

@Controller
@RequiredArgsConstructor
public class SessionUserController {

    private final UserService userService;

    @PostMapping("/session-login")
    public String login(
            @Valid @ModelAttribute LoginRequestDto dto,
            HttpServletRequest request
    ) {

        LoginResponseDto responseDto = userService.login(dto.getUserName(), dto.getPassword());
        Long userId = responseDto.getId();

        // 실패시 예외처리
        if (userId == null) {
            return "session-login";
        }

        // 로그인 성공시 로직
        // Session의 Default Value는 true이다.
        // Session이 request에 존재하면 기존의 Session을 반환하고,
        // Session이 request에 없을 경우에 새로 Session을 생성한다.
        HttpSession session = request.getSession();

        // 회원 정보 조회
        UserResponseDto loginUser = userService.findById(userId);

        // Session에 로그인 회원 정보를 저장한다.
        session.setAttribute(Const.LOGIN_USER, loginUser);

        // 로그인 성공시 리다이렉트
        return "redirect:/session-home";
    }

    @PostMapping("/session-logout")
    public String logout(HttpServletRequest request) {
        // 로그인하지 않으면 HttpSession이 null로 반환된다.
        HttpSession session = request.getSession(false);
        // 세션이 존재하면 -> 로그인이 된 경우
        if(session != null) {
            session.invalidate(); // 해당 세션(데이터)을 삭제한다.
        }

        return "redirect:/session-home";
    }
}

 

SessionHomeController

@Controller
@RequiredArgsConstructor
public class SessionHomeController {

    private final UserService userService;

    @GetMapping("/session-home")
    public String home(
            HttpServletRequest request,
            Model model
    ) {

        // default인 true로 설정되면 로그인하지 않은 사람들도 값은 비어있지만 세션이 만들어진다.
        // session을 생성할 의도가 없다.
        HttpSession session = request.getSession(false);

        // session이 없으면 로그인 페이지로 이동
        if(session == null) {
            return "session-login";
        }

        // session에 저장된 유저정보 조회
        // 반환타입이 Object여서 Type Casting이 필요하다.
        UserResponseDto loginUser = (UserResponseDto) session.getAttribute(Const.LOGIN_USER);

        // Session에 유저 정보가 없으면 login 페이지 이동
        if (loginUser == null) {
            return "session-login";
        }

        // Session이 정상적으로 조회되면 로그인된것으로 간주
        model.addAttribute("loginUser", loginUser);
        // home 화면으로 이동
        return "session-home";

    }
}

 

 

Spring의 Session

Spring에서는 Session을 쉽게 다루도록 @SessionAttribute라는 어노테이션이 제공된다.

 

@SessionAttribute

  • request.getSession(true); 와는 다르게 Session을 새로 생성하는 기능은 없다.
  • 이미 로그인이 완료된 사용자를 찾는 경우 즉, Session이 있는 경우에 사용한다.
@Controller
@Requiredargsconstructor
public class SessionHomeController {

		private UserService userService;
	
		@GetMapping("/v2/session-home")
    public String homeV2(
            // Session이 필수값은 아니다. 로그인 했거나 안했거나 판별해야하니 required false
            @SessionAttribute(name = Const.LOGIN_USER, required = false) UserResponseDto loginUser,
            Model model
    ) {

        // session에 loginUser가 없으면 Login 페이지로 이동
        if (loginUser == null) {
            return "session-login";
        }

        // Session이 정상적으로 조회되면 로그인된것으로 간주
        model.addAttribute("loginUser", loginUser);

        // home 화면으로 이동
        return "session-home";
    }
}

 

처음 로그인을 시도하는 경우

  • 완전히 처음 로그인을 시도하는 경우 URL 뒤에 JSSESSIONID=값이 함께 전달된다.
  • 해당 값은 굳이 필요가 없다. 내부적으로 Set-Cookie로 SessionID와 값을 넣어주기 때문
  • Cookie를 지원하지 않으면 URL을 통해 Session을 유지하는 방법에 사용된다.
    • 하지만, 사용하려면 모든 요청 URL에 jsessionId값이 전달 되어야한다.

 

세션을 사용할 때 세션이 생성되면 서버 측 세션 스토리지(메모리)에 세션아이디와 그에 해당하는 인스턴스를 저장해 놓는다는걸 까먹고 아니 헤더에는 쿠키에 JSESSIONID 밖에 없는데 도대체 어떻게 UserResponseDto 타입으로 객체를 바인딩 하는거지??? 하고 이해를 못했다. 

 

📌 JSESSIONID만 보이는데, 세션에서 "loginUser"를 어떻게 찾을까?

✅ 핵심 개념:

  • 브라우저가 서버에 요청을 보낼 때, Cookie: JSESSIONID=xxxxxx 형태로 세션 ID를 보냅니다.
  • 서버는 JSESSIONID 값을 이용해 해당 세션을 찾아서 관리합니다.
  • 세션 내부에 loginUser가 저장되어 있다면, 이를 찾아 컨트롤러에서 사용할 수 있습니다.

 

💡 JSESSIONID의 역할

1️⃣ 클라이언트(브라우저)가 서버에 요청

  • 클라이언트는 처음 서버에 요청을 보낼 때 세션이 없습니다.
  • 서버는 새로운 세션을 생성하고, JSESSIONID(예: 09E81FD49BCF589CBD42C876840EDF39)을 쿠키에 저장하여 클라이언트에 보냅니다.
  • 이후 클라이언트는 모든 요청에서 해당 JSESSIONID를 포함하여 요청을 보냅니다.

2️⃣ 서버가 JSESSIONID를 이용해 세션을 찾음

  • 서버는 요청이 올 때마다 JSESSIONID를 보고 해당하는 세션 객체를 찾습니다.
  • 세션 객체 내부에는 session.setAttribute("loginUser", userDto); 같은 코드로 저장된 값들이 들어 있습니다.
  • 즉, 세션 ID만으로도 서버는 세션 내부의 loginUser 값을 가져올 수 있습니다.

 

🛠 동작 과정 상세 설명

1️⃣ 로그인 시 세션에 "loginUser" 저장

@PostMapping("/login")
public String login(@RequestParam String username, HttpServletRequest request) {
    User user = userService.findByUsername(username);
    
    if (user != null) {
        UserResponseDto userResponseDto = new UserResponseDto(user);

        // 세션 가져오기
        HttpSession session = request.getSession();

        // "loginUser"라는 키로 UserResponseDto 객체 저장
        session.setAttribute(Const.LOGIN_USER, userResponseDto);
        
        return "redirect:/v2/session-home";
    }

    return "session-login";
}

 

session.setAttribute("loginUser", userResponseDto);
→ 서버의 메모리 내부에 있는 세션 객체에 "loginUser" 키로 UserResponseDto 객체가 저장됨

✔ 서버는 응답을 보낼 때, Set-Cookie: JSESSIONID=09E81FD49BCF589CBD42C876840EDF39
→ 이 값이 클라이언트의 쿠키에 저장됨

 

2️⃣ 클라이언트가 요청을 보낼 때, JSESSIONID 전송

GET /v2/session-home HTTP/1.1
Host: example.com
Cookie: JSESSIONID=09E81FD49BCF589CBD42C876840EDF39

브라우저가 JSESSIONID를 포함한 요청을 보냄
서버는 해당 ID의 세션 객체를 찾아서 사용

 

3️⃣ 서버가 세션에서 "loginUser" 값을 찾아 바인딩

@GetMapping("/v2/session-home")
public String homeV2(
    @SessionAttribute(name = Const.LOGIN_USER, required = false) UserResponseDto loginUser,
    Model model
) {
    if (loginUser == null) {
        return "session-login";  // 세션에 값이 없으면 로그인 페이지로 이동
    }

    model.addAttribute("loginUser", loginUser);
    return "session-home";  // 홈 화면 이동
}

Spring이 @SessionAttribute("loginUser")를 보고, 세션에서 해당 객체를 찾아 loginUser에 자동 주입
✔ 내부적으로 session.getAttribute("loginUser")를 호출한 것과 동일

 

✅ 결론

📌 JSESSIONID만 보이는 이유?
👉 브라우저는 JSESSIONID만 저장하고, 실제 데이터(loginUser)는 서버 세션 메모리에 저장됨

📌 세션에서 "loginUser"를 찾는 원리?
👉 클라이언트가 JSESSIONID를 서버로 전송하면, 서버는 이를 기반으로 해당하는 세션 객체를 찾아 내부 데이터를 반환

📌 @SessionAttribute("loginUser")의 역할?
👉 session.getAttribute("loginUser")를 자동으로 수행하여 컨트롤러에서 매개변수로 주입

📌 세션 데이터 저장 방식?
👉 기본적으로 서버 메모리에 저장, spring-session을 설정하면 Redis 등에 저장 가능

 

 

Session 정보

HttpSession은 Session을 간편하게 사용할 수 있도록 다양한 기능을 지원한다.

@Slf4j
@RestController
public class SessionController {

    @GetMapping("/session")
    public String session(HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        if (session == null) {
            return "세션이 없습니다.";
        }

        // session 정보 조회
        log.info("session.getId()={}", session.getId());
        log.info("session.getMaxInactiveInterval()={}", session.getMaxInactiveInterval());
        log.info("session.getCreationTime()={}", session.getCreationTime());
        log.info("session.getLastAccessedTime()={}", session.getLastAccessedTime());
        log.info("session.isNew()={}", session.isNew());

        return "세션 조회 성공!";
    }
    
}

 

 

session 정보

  1. session.getId();
    • jsessionId 값을 조회할 수 있다.
  2. session.getMaxInactiveInterval();
    • 세션의 유효시간
    • second 단위 default는 30분(1800초)이다.
  3. session.getCreationTime();
    • 세션 생성시간
    ex) Sat Dec 9 15:40:23 KST 2024
  4. session.getLastAccessedTime();
    • 해당 세션에 마지막으로 접근한 시간
    ex) Sat Dec 9 15:40:23 KST 2024
  5. session.isNew();
    • 새로 생성된 세션인지 여부

 

Session TimeOut

Session은 logout 기능을 사용하여 session.invalidate(); 가 되어야 삭제되지만 대부분의 사용자들은 로그아웃을 굳이 하지않고, 브라우저를 종료한다.

 

  • Session의 문제점
    • HTTP는 Connectionless 특성을 가지고 있어서 서버가 브라우저 종료 여부를 판별하지 못한다.
    • 서버에서 Session을 언제 삭제해야 하는지 판단하기 힘들다.
    • JSESSIONID의 값을 탈취 당한 경우 해당 값으로 악의적인 요청을 할 수 있다.
    • 세션은 서버 메모리에 생성되고 자원은 한정적이기 때문에 꼭 필요한 경우만 생성해야 한다.
    ex) 로그인한 유저가 100만명 이라면..?
  • Session 생명주기
    • 기본적으로 30분을 기준으로 세션을 삭제한다.
    • 실제 로그인 후 30분 이상의 시간동안 사용중인 사용자의 세션 또한 삭제된다.
    • 다시 로그인 해야하는 경우가 발생한다.
  • HttpSession 사용
    • 세션 생성시점 30분이 아닌 서버에 최근 Session을 요청한 시간을 기준으로 30분을 유지한다.
    • HttpSession은 기본적으로 해당 방식으로 세션의 생명주기를 관리한다.
    • Session 정보에서 LastAccessedTime 을 기준으로 30분이 지나면 WAS가 내부적으로 세션을 삭제한다.

 

Session의 한계

Session은 서버의 메모리를 사용하여 확장성이 제한되어 있다.

 

  • 서버가 DB 혹은 메모리에 저장된 세션 정보를 매번 조회하여 오버헤드가 발생한다.
  • 서버가 상태를 유지해야 하므로 사용자 수가 많아질수록 부담이 커진다.
  • Cookie는 웹 브라우저에만 존재하여 모바일 앱 등의 다양한 클라이언트에서 인증을 처리할 수 없다.
  • Scale Out(수평적 확장)에서 서버간 세션 공유가 어렵다.

오버헤드(Overhead)란? 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간, 메모리 등을 의미한다.

 

 

쿠키 세션 총정리

  • 인증과 인가
    1. 인증(Authentication)
      • 사용자가 누구인지 확인하는 과정
      ex) 로그인
    2. 인가(Authorization)
      • 사용자가 어떤 권한을 가지고 있는지 결정하는 과정
      • 반드시 인증이 선행되어야 한다.
      ex) 회원만 조회 가능한 게시글, 본인이 작성한 게시글 수정
  • 쿠키(Cookie)
    • 정의
      • 웹 브라우저(Client 측)에 저장되는 데이터
    • 용도
      • 사용자의 방문 기록, 로그인 상태 유지, 개인 맞춤 설정 등을 저장, HTTP 특성 극복, 광고 정보
    • 특징
      • 클라이언트 측에 저장되며, 서버에 요청을 보낼 때마다 포함되어 전송됨.
    • 수명
      • 만료 날짜를 설정할 수 있음, 세션 쿠키(브라우저 종료 시 삭제)와 영속 쿠키(지정된 기간 동안 유지)로 구분됨.
  • 세션(Session)
    • 정의
      • 사용자와 서버 간의 상태를 유지하기 위한 방법.
    • 용도
      • 로그인 정보, 사용자 활동 등을 서버 측에서 관리.
    • 특징
      • 서버 측에서 저장되며, 세션 ID가 쿠키에 저장되어 클라이언트와 연결됨.
    • 수명
      • 브라우저를 닫거나 일정 시간이 지나면 만료됨.
    • Session 관리
      1. 세션은 메모리를 사용한다.
        • 세션이 많아지면 서버의 장애가 발생할 수 있다.(메모리 리소스 부족)
        • 최소한의 데이터만 저장해야 한다.
      2. HttpSession은 LastAccessedTime을 기준으로 30분의 생명주기를 가지고 있다.
      3. 세션의 시간을 너무 오래 유지하여도 메모리 리소스가 부족할 수 있다.
        • 적당한 시간 설정이 꼭 필요하다.(Default 30분)

 

 

토큰이란?

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

 

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

 

Token 동작순서

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

 

 

JWT란?

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

 

JWT 구조는 이렇게 생겼다.

https://jwt.io/

 

JWT.IO

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

jwt.io

XXXXXX.YYYYYY.ZZZZZZ
(Header).(Payload).(Signature)

 

  1. Header
    • 토큰의 타입과 해싱 알고리즘을 정의한다.
  2. Payload
    • 실제로 인증과 관련된 데이터(Claims)를 담고 있다.
    • Claims의 종류
      • Registered Claims : 미리 정의된 Claims
        • iss(issuer) : 발행자
        • exp(expiration time) : 만료시간
        • sub(subject) : 제목
        • iat(issued At) : 발행 시간
        • jti(JWT ID) : 토큰의 고유 식별자
      • Public Claims : 사용자가 정의할 수 있는 클레임, 공개용 정보 전달 목적
      • Private Claims : 사용자 지정 클레임, 당사자들 간에 정보를 공유하기 위한 목적
  3. Signature
    • Header와 Payload를 서버의 Secret Key로 서명하여 암호화 한다.
    • 암호화는 Header에서 정의한 알고리즘(alg)을 활용한다.
    • 서명을 통해 서버는 Token이 변조되지 않았음을 확인할 수 있다.

base64UrlEncode는 값을 URL에서 사용할 수 있도록 +, /를 각각 -, _로 표기한다.

Header와 Payload는 Encoding된 값이기 때문에 복호화 혹은 값을 수정할 수 있지만 Signature는 서버에서 관리하는 값이기 때문에 Secret Key가 유출되지 않는 이상 복호화 할 수 없다.

 

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

 

여기까지 쓰고 있는데 갑자기 노션이 로딩이 안된다...! 접근이 제한이 된것 같은데 아직 JWT랑 Filter(공통로직 부분을 Filter로 대체)를 정리를 못했는데 오늘 여기서 마무리 해야할 것 같다.

 

스프링 숙련 주차를 하면서 이제 개념들은 알겠는데 내가 코드에 적용할 수 있을까 싶다. 아직 버벅거릴 것 같다.

 

그렇지만 난 해낼 수 있다 ㅋ

 

이제 포트폴리오를 써야한다. 아직 포트폴리오 안에 넣을 프로젝트가 없지만 스프링 입문 과제라도 넣어서 한번 써봐야겠다. 백엔드 개발자로 취뽀하는 그날까지