Spring - 로그인 (세션, 쿠키)

희운·2025년 5월 24일

SpringBoot

목록 보기
7/10

 @PostMapping("/login")
    public String loginV3(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletRequest request) {
        if (bindingResult.hasErrors()) {
            return "login/loginForm";
        }

        Member loginMember = loginService.login(form.getLoginId(), form.getPassword());

        if (loginMember == null) {
            bindingResult.reject("loginFail", "아아디 또는 비밀번호가 맞지 않습니다.");
            return "login/loginForm";
        }

        /**
         * 이 아래 두개의 코드는 내부적으로 코드가 많이 복잡하다.
         * 전체적인 흐름만 알자.
         *
         * public class StandardSession implements HttpSession {
         *     private final String id;                        // UUID 같은 세션 ID
         *     private final Map<String, Object> attributes;   // 세션 속성 저장소
         *     // … 그 밖의 메타데이터(생성시간, 만료시간 등)
         * }
         */
        //로그인 성공 처리
        //세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
        HttpSession session = request.getSession();  // 없으면 세션 생성 + UUID 발급
        log.info("session.getId()={}", session.getId());//발급한 UUID HttpSession 안에 보관.

        //세션에 로그인 회원 정보 보관-> , 자동으로 Set-Cookie: JSESSIONID=<UUID> 헤더 추가
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);


        return "redirect:/";
    }

HttpSession 구현체 내부에는 대략 이런 필드가 있어요:

// 톰캣(StandardSession)의 내부 예시
public class StandardSession implements HttpSession {
    private final String id;                        // UUID 같은 세션 ID
    private final Map<String, Object> attributes;   // 세션 속성 저장소
    // … 그 밖의 메타데이터(생성시간, 만료시간 등)
}
  • 키(key): 문자열(String)
  • 값(value): 여러분이 저장한 어떤 자바 객체든(Object)

2. session.setAttribute(name, value) 가 하는 일

session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

실제로는 이렇게 동작합니다:

  1. SessionConst.LOGIN_MEMBER → 컴파일 타임에 "loginMember" 라는 문자열 리터럴로 인라인

  2. 내부 attributes.put("loginMember", loginMember) 호출

  3. 이제 이 HttpSession 객체의 속성 맵에

    key:   "loginMember"
    value: <loginMember 도메인 객체>

    가 저장된 상태가 됩니다.

요점: 이 문자열 자체는 “속성 맵의 key”로만 쓰입니다.


3. session.getAttribute(name) 가 하는 일

Member member = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
  1. 이 코드도 "loginMember" 를 키로 넘기면
  2. 내부적으로 attributes.get("loginMember") 이 실행돼서
  3. 앞서 저장한 loginMember 객체가 반환됩니다.

정확히 한 세션 객체(HttpSession)당 하나의 세션 ID만 존재합니다.

public class StandardSession implements HttpSession {
    private final String id;                      // <— 이 한 번 생성된 문자열이 곧 세션 ID
    private final Map<String, Object> attributes; // <— 이 안에 키–값으로 여러분이 setAttribute한 데이터가 저장됩니다.
    // … 그 밖에 생성 시간, 최종 접근 시간, 만료 시간 같은 메타데이터가 있어요.
}
  • id 필드(StandardSession.id)는 세션이 처음 만들어질 때 한 번만 부여되고(new UUID()), 변경되지 않습니다.
  • 이 값을 꺼내 보고 싶다면 session.getId() 를 호출하면 됩니다.
  • 브라우저는 이 ID를 쿠키(JSESSIONID)로 저장했다가 이후 요청 때마다 보내 주고,
  • 톰캣 입장에서는 이 ID 하나로 내부 세션 저장소에서 딱 개의 StandardSession 객체를 조회하죠.

참고:

  • Servlet 3.1 이상부터는 HttpServletRequest#changeSessionId() 메서드를 통해 기존 속성은 유지하면서 새 ID를 발급할 수 있는데, 이건 세션 고정 공격(session fixation) 방지를 위해 옵션으로 제공되는 기능입니다.
  • 그 외에는 “한 세션 = 한 ID”라는 관계가 1:1로 유지된다고 보시면 됩니다.

주석을 보고이해해봐

로그인할때

 @PostMapping("/login")
    public String loginV3(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletRequest request) {
        if (bindingResult.hasErrors()) {
            return "login/loginForm";
        }

        Member loginMember = loginService.login(form.getLoginId(), form.getPassword());

        if (loginMember == null) {
            bindingResult.reject("loginFail", "아아디 또는 비밀번호가 맞지 않습니다.");
            return "login/loginForm";
        }

        /**
         * 이 아래 두개의 코드는 내부적으로 코드가 많이 복잡하다.
         * 전체적인 흐름만 알자.
         *
         * public class StandardSession implements HttpSession {
         *     private final String id;                        // UUID 같은 세션 ID
         *     private final Map<String, Object> attributes;   // 세션 속성 저장소
         *     // … 그 밖의 메타데이터(생성시간, 만료시간 등)
         * }
         */
        //로그인 성공 처리
        //세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
        //여기는 로그인이므로 기존에 없을것이다.(그래서 True)로 해놔야한다.
        //HttpSession 내부에 UUID 를 넣는 필드인 id 가 존재할것이데 거기에 값이 들어간다.
        HttpSession session = request.getSession();  // 없으면 세션 생성 + UUID 발급
        log.info("session.getId()={}", session.getId());//발급한 UUID HttpSession 안에 보관.

        //세션에 로그인 회원 정보 보관-> , 자동으로 Set-Cookie: JSESSIONID=<UUID> 헤더 추가
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);


        return "redirect:/";
    }

로그인후에 홈화면


/@GetMapping("/")
    public String homeLoginV3(HttpServletRequest request, Model model) {


        //세션 자체가 없으면 home ( 굳이 세션을 생성할 필요없다.)
        //false 인게 중요하다 ( Cookie 에 있는 UUID 로 비교후 같은 UUID 를 가지는 HttpSession이 있으면 그것을 가져온다)
        // 만약에 UUID 와 비교했을때 해당하는 HttpSession이 없으면 null 을 반환한다.
        // true 일때와 다르게 UUID 가 일치하는게 없다고 새로생성하지않는다.
        HttpSession session = request.getSession(false);
        if (session == null) {
            return "home";
        }

        Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);

        //세션에 회원 데이터가 없으면 home
        if(loginMember == null) {
            return "home";
        }

        //세션이 유지되면 로그인홈화면 으로 이동
        model.addAttribute("member", loginMember);
        return "loginHome";

    }

UUID 비교는 HttpSession session = request.getSession(false); 여기서 한다. 물론 True 일때도 하지만 (True 일때는 없으면 해당 UUID를 가지는 HttpSession 을 만들어서 반환, 있으면 해당 UUID 를 가지는 HttpSession을 반환)

profile
기록하는 공간

0개의 댓글