Notice
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
01-24 16:43
Today
Total
관리 메뉴

그날그날 공부기록

쿠키, 세션 사용해보기 본문

Spring 공부

쿠키, 세션 사용해보기

given_dragon 2023. 11. 28. 11:40

231126 쿠키, 세션

회원의 로그인 정보를 유지시켜야 하는 상황.

홈 → 로그인 → 메인 페이지 이동(홈)


우선 쿠키에 사용자 정보를 보관해 보자.

다음은 로그인 컨트롤러의 login 메서드이다.

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

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

    // 로그인 성공 처리

    // 쿠키에 시간 정보를 주지 않으면 세션 쿠키(브라우저 종료시 모두 종료)
    Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
    response.addCookie(idCookie);
    return "redirect:/";
}

form 데이터를 LoginForm(DTO)로 바인딩하고 문제가 있다면 bindingResult에 에러 정보가 담긴다.
올바른 사용자인지 확인한 후, Cookie 객체에 name과 value를 넣어 HttpServletResponse에 넣어 반환한다.

setMaxAge(int age)로 쿠키 유지 시간을 설정할 수 있고,
음수 or 설정X는 세션쿠키로 설정되어 브라우저가 종료된다면 해당 쿠키는 삭제된다.
age를 0으로 설정한다면 해당 name의 쿠키는 즉시 삭제된다.

 

세션쿠키로 브라우저에 저장된 것을 확인할 수 있다.

 

세션쿠키로 브라우저에 저장된 것을 확인할 수 있다.

로그인이 완료되고, 홈 컨트롤러의 homeLogin 메서드가 실행된다.
@CookieValue를 사용하면 HttpServletRequest에서 쿠키를 일일이 찾지 않아도 된다.

@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
    if (memberId == null) {
        return "home";
    }

    // 로그인
    Member loginMember = memberRepository.findById(memberId);
    if (loginMember == null) {
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

 

하지만 쿠키 값은 브라우저에 저장되어 쉽게 볼 수 있고, 사용자가 임의로 변경할 수 있다.
따라서 쿠키 값에는 중요한 정보를 보관하면 안 된다.

 


 

중요한 정보를 서버에 저장하기 위해 세션을 사용해 보자.

쿠키를 사용하긴 하지만 기존에 저장하던 사용자의 정보 대신, 랜덤한 토큰값을 저장한다.
그리고 서버에는 이 토큰값을 Key, 사용자 정보를 Value로 하여 저장해 둔다.

이렇게 한다면 토큰을 예측, 변조하기 힘들고, 탈취당하더라도 해당 세션정보를 삭제하면 된다.

세션을 생성, 조회, 만료 기능만 간단히 구현하였다.

@Component
public class SessionManager {
    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();

    /*세션 생성*/
    public void createSession(Object value, HttpServletResponse response) {
        // session id 생성
        String sessionId = UUID.randomUUID().toString();

        // 세션 저장소에 정보 저장
        sessionStore.put(sessionId, value);

        // sessionId로 응답 쿠키를 생성하여 클라이언트에 전달
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(mySessionCookie);
    }

    /*세션 조회*/
    public Object getSession(HttpServletRequest request) {
        // 쿠키 확인
        Cookie sessionCookie = findCookie(request);
        if (sessionCookie == null) {
            return null;
        }

                // 쿠키에 저장되어 있던 key로 세션 저장소의 정보 가져오기
        return sessionStore.get(sessionCookie.getValue());
    }

    /*세션 만료*/
    public void expire(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request);
        if (sessionCookie != null) {
            sessionStore.remove(sessionCookie.getValue());
        }
    }

    public Cookie findCookie(HttpServletRequest request) {
        if (request.getCookies() == null) {
            return null;
        }
                // 쿠키들에서 세션에 관련된 쿠키 가져오기
        return Arrays.stream(request.getCookies()).filter(cookie -> cookie.getName()
                .equals(SESSION_COOKIE_NAME))
                .findFirst()
                .orElse(null);
    }
}

 

 

이제 세션을 바탕으로 로그인 메서드를 다시 작성하면 다음과 같다.
쿠키를 생성하는 과정 대신, createSession 메서드를 통해 세션을 생성한다.

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

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

        sessionManager.createSession(loginMember, response);
    return "redirect:/";
}

브라우저에는 서버의 세션에 접근하기 위한 토큰이 쿠키로 저장된다.

 

브라우저에는 세션 데이터 접근을 위한 토큰이 쿠키로 저장된다.

 

로그인이 완료되고 homeLogin이 실행된다.
쿠키의 토큰을 사용해 서버의 세션 저장소에서 유저 정보를 조회하여 처리한다.

@GetMapping("/")
public String homeLogin(HttpServletRequest request, Model model) {
        // 세션 관리자에 저장된 회원 정보 조회
    Member loginMember = (Member)sessionManager.getSession(request);
    if (loginMember == null) {
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

 


 

이렇게 세션을 만들지 않아도, Servlet에서 세션 기능을 제공한다.
서블릿을 통해 HttpSession을 생성하면, 쿠키 이름(name)은 JSESSIONID이고 값(value)은 랜덤 한 값이다.

  • Tomcat이 지정하는 세션 ID에 대한 쿠키이름의 기본값이 JSESSIONID

 

세션을 생성하기 위해 HttpServletRequest의 getSession(boolean create)을 사용한다.

  • boolean create
    • true(Default) → 기존 세션이 있다면 반환, 없다면 새로 생성해서 반환
    • false → 기존 세션 반환, 없다면 null 반환

setAttribute(String name, Object value)를 통해 세션에 데이터를 저장한다.

@PostMapping("/login")
public String login(@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";
    }

    // 로그인 성공 처리
    // 세션 생성
    HttpSession session = request.getSession();
    session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
    return "redirect:/";
}

 

브라우저에 쿠키 저장 확인

 

브라우저에 쿠키 저장 확인

세션 정보가 저장되고, homeLogin 메서드가 작동한다.
HttpSession을 가져와 getAttribute를 통해 사용자 정보를 가져온다.

@GetMapping("/")
public String homeLogin(HttpServletRequest request, Model model) {
        HttpSession session = request.getSession(false);
    if (session == null) {
        return "home";
    }

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

    if (loginMember == null) {
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

 

 

스프링은 세션을 편리하게 사용할 수 있도록 @SessionAttribute을 지원한다.

@GetMapping("/")
public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {

    if (loginMember == null) {
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

서블릿에서 제공하는 세션은 시간만료 등을 알아서 처리해 주고, 옵션을 통해 임의로 조정도 가능하다.

  • 💡 여기서 session id에 대해서 헷갈려서 추가로 찾아봤다.
    • JSESSIONID값은 Tomcat에서 생성하는 값이다.
    • 이 id를 Key로 갖고, 저장공간(session)을 value로 갖는 Map이 존재한다.
      map → { key: session-id, value:Map }해당 value에 LOGIN_MEMBER을 key로, Member를 value로 가지는 Map이 저장되는 것이다.

'Spring 공부' 카테고리의 다른 글

ArgumentResolver  (0) 2023.12.19
필터, 인터셉터  (0) 2023.12.19
스프링 시큐리티 로그인 동작 따라가보기  (2) 2023.09.25
빈 생명주기, 콜백 이용하기  (0) 2022.08.16
Map, List에 빈 주입 & 사용하기  (0) 2022.08.09
Comments