본문 바로가기
Spring/MVC 2편

로그인 처리1 - 쿠키, 세션

by JHyun0302 2023. 8. 7.
728x90

로그인 처리하기 - 쿠키 사용

 

로그인 상태 유지하기

  • 쿼리 파라미터를 계속 유지하면서 보내는 것은 매우 어렵고 번거로운 작업이다.
  • 서버에서 로그인에 성공하면 HTTP 응답에 쿠키를 담아서 브라우저에 전달. 브라우저는 앞으로 해당 쿠키를 지속해서 보내준다.

 

쿠키 생성

 

모든 요청에 쿠키 정보 포함

 

 

 

쿠키의 종류

  • 영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
  • 세션 쿠키: 만료 날짜를 생략하면 브라우저 종료시 까지만 유지

 

 

LoginController

@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());
    	log.info("login? {}", loginMember);
    	if (loginMember == null) {
    		bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
   	 		return "login/loginForm";
    	}
    
        //로그인 성공 처리
        //쿠키에 시간 정보를 주지 않으면 세션 쿠키(브라우저 종료시 모두 종료)
        Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
        response.addCookie(idCookie);
        return "redirect:/";
}

 

 

  • 로그인에 성공하면 쿠키를 생성하고 HttpServletResponse 에 담는다.
  • 쿠키 이름은 memberId 이고, 값은 회원의 id를담아둔다.
  • 웹브라우저는 종료전까지 회원의 id를 서버에 계속 보내줄 것이다.

 

 

로그아웃 기능

  • 세션 쿠키이므로 웹 브라우저 종료시 서버에서 해당 쿠키의 종료 날짜를 0으로 지정

 

LoginController

@PostMapping("/logout")
public String logout(HttpServletResponse response) {
    expireCookie(response, "memberId");
    return "redirect:/";
}

private void expireCookie(HttpServletResponse response, String cookieName) {
    Cookie cookie = new Cookie(cookieName, null);
    cookie.setMaxAge(0);
    response.addCookie(cookie);
}

 

 

 

 


 쿠키와 보안 문제

 

보안 문제

  1. 쿠키 값은 임의로 변경할 수 있다.
  2.  쿠키에 보관된 정보는 훔쳐갈 수 있다.
  3. 해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.

 

 

 

대안

  • 쿠키에 중요한 값을 노출하지 않고, 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 노출하고, 서버에서 토큰과 사용자 id를 매핑해서 인식한다. 그리고 서버에서 토큰을 관리한다.
  • 토큰은 해커가 임의의 값을 넣어도 찾을 수 없도록 예상 불가능 해야 한다.
  • 해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 해당 토큰의 만료시간을 짧게(예: 30분) 유지한다. 또는 해킹이 의심되는 경우 서버에서 해당 토큰을 강제로 제거하면 된다.

 

 


로그인 처리하기 - 세션 동작 방식

  

세션 형성

 

 

  • 세션 ID를 생성하는데, 추정 불가능해야 한다.
  • UUID는 추정이 불가능하다.
        Cookie: mySessionId=zz0101xx-bab9-4b92-9b32-dadb280f4b61
  • 생성된 세션 ID와 세션에 보관할 값( memberA )을 서버의 세션 저장소에 보관한다.

 

 

 

세션 id를 응답 쿠키로 전달

 

 

★ 중요

  • 여기서 중요한 포인트는 회원과 관련된 정보는 전혀 클라이언트에 전달하지 않는다는 것이다.
  • 오직 추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달한다.

 

로그인

 

★ 정리

  • 쿠키 값을 변조 가능, 예상 불가능한 복잡한 세션Id를 사용한다.
  • 쿠키에 보관하는 정보는 클라이언트 해킹시 털릴 가능성이 있다. → 세션Id가 털려도 여기에는 중요한 정보가 없다.
  • 쿠키 탈취 후 사용 → 서버에서 세션의 만료시간을 짧게(예: 30분) 유지한다. 또는 해킹이 의심되는 경우 서버에서 해당 세션을 강제로 제거하면 된다.

 

 

 

 


로그인 처리하기 - 서블릿 HTTP 세션1

 

LoginController

@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());
    log.info("login? {}", loginMember);
    if (loginMember == null) {
        bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
        return "login/loginForm";
    }
    
    //로그인 성공 처리
    //세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
    HttpSession session = request.getSession(); 
    
    //세션에 로그인 회원 정보 보관
    session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
    return "redirect:/";
}

 

※ request.getSession() ≒ request.getSession(true)

 

 

세션 생성과 조회

public HttpSession getSession(boolean create);

 

 request.getSession(true)

  • 세션이 있으면 기존 세션을 반환한다.
  • 세션이 없으면 새로운 세션을 생성해서 반환한다.

request.getSession(false)

  • 세션이 있으면 기존 세션을 반환한다.
  • 세션이 없으면 새로운 세션을 생성하지 않는다. null 을 반환한다.

 

 

세션에 로그인 회원 정보 보관

session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
  • 하나의 세션에 여러 값을 저장할 수 있다.

 

 

 

@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
	//세션을 삭제한다.
	HttpSession session = request.getSession(false); 
	if (session != null) {
		session.invalidate();
	}
	return "redirect:/";
}

 

  • session.invalidate() : 세션을 제거한다.

 

 

 


로그인 처리하기 - 서블릿 HTTP 세션2

 

@SessionAttribute

 

HomeController

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

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

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

 

이미 로그인 된 사용자를 찾을 때는 기능 (세션을 생성하지 않는다.)

@SessionAttribute(name = "loginMember", required = false) Member loginMember

 

 

 

 

◎ 참고: TrackingModes

  • URL 전달 방식을 끄고 항상 쿠키를 통해서만 세션을 유지하고 싶으면 다음 옵션을 넣어주면 된다.
  • 이렇게 하면 URL에 jsessionid 가 노출되지 않는다.

 

application.properties

server.servlet.session.tracking-modes=cookie

 

 

 


세션 정보와 타임아웃 설정

 

 

세션 정보

 

  • sessionId : 세션Id, JSESSIONID 의 값이다.    ex) 34B14F008AA3527C9F8ED620EFD7A4E1
  • maxInactiveInterval : 세션의 유효 시간.          ex) 1800초, (30분)
  • creationTime : 세션 생성일시
  • lastAccessedTime : 세션과 연결된 사용자가 최근에 서버에 접근한 시간, 클라이언트에서 서버로 sessionId ( JSESSIONID )를 요청한 경우에 갱신된다.
  • isNew : 새로 생성된 세션인지, 아니면 이미 과거에 만들어졌고, 클라이언트에서 서버로 sessionId ( JSESSIONID )를 요청해서 조회된 세션인지 여부

 

 

세션의 종료 시점

 

 

application.properties

server.servlet.session.timeout=60 : 60초, 기본은 1800(30분)

 

 

특정 세션 단위로 시간 설정

session.setMaxInactiveInterval(1800); //1800초

 

 

 

세션 타임아웃 발생

  • session.getLastAccessedTime() : 최근 세션 접근 시간
  • LastAccessedTime 이후로 timeout 시간이 지나면, WAS가 내부에서 해당 세션을 제거한다.

 

 

 

 

 

 

★ 정리

  • 세션에는 최소한의 데이터만 보관해야 한다는 점
                 보관한 데이터 용량 * 사용자 수로 세션의 메모리 사용량이 급격하게 늘어나서 장애로 이어질 수 있다.
  • 세션의 시간을 너무 길게 가져가면 메모리 사용이 계속 누적 될 수 있으므로 적당한 시간을 선택하는 것이 필요

 

반응형

'Spring > MVC 2편' 카테고리의 다른 글

예외 처리 & 오류 페이지  (0) 2023.08.07
로그인 처리2 - 필터, 인터셉터  (0) 2023.08.07
검증2 - Bean Validation  (0) 2023.08.07
검증1 - Validation  (0) 2023.08.06
메시지, 국제화  (0) 2023.08.06