[백엔드] 스프링 MVC 패턴쿠키, 세션 로그인 적용

유승선 ·2022년 11월 1일
0

Spring 강의

목록 보기
9/12
post-thumbnail

로그인 처리 기능을 조금 더 자세하게 배워보고 싶었다. 사실 로그인 기능은 정말 쉬운 방법만 생각한다면 정말 쉽다. 그저 데이터베이스에 저장되어있는 기록이랑 비교를 해서 검증만 된다면 로그인 처리를 하면 되지만 백엔드 개발자로서의 지식을 활용하기 위해서는 HTTP 지식과 연동해 로그인을 활용해봐야겠다.

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

내 HTTP 웹 포스팅에도 간략하게 정리해놨지만 클라이언트와 서버가 쿠키로 요청과 응답을 주고받는 가장 기본적인 구조이다. 먼저 특정 화면에서 서버가 쿠키를 주고 싶을때 응답 요청Set-Cookie 헤더에 쿠키 정보를 담아서 보내게된다.

이후에 클라이언트가 응답에서 쿠키를 받았다면 쿠키 저장소에 저장을 한 뒤, 같은 웹 경로에 접근하게 되면 쿠키 저장소에 저장되어 있던 쿠키를 가지고 와서 사용자의 정보를 가지고 요청을 보낼 수 있다.

이렇게 쿠키는 모든 요청에 자동 포함되고 쿠키에 지속시간을 결정 하는데에는 영속 쿠키세션 쿠키 가 존재한다.

Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);

컨트롤러에서 사용된 쿠키 로직이다. response 란 평범한 서블릿 HttpServletResponse 객체를 의미하고 쿠키에 정보를 입력 해줄때 memberId 라는 이름의 쿠키로 정보를 담아서 응답 해주었다. 이로서, 응답헤더에 쿠키가 추가되었다.

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

새로운 어노테이션인데 @CookieValue 라는것을 배웠다. 안에 name 을 세팅하는거는 쿠키의 이름을 지정해주었고, required = false 는 모든 사람이 쿠키가 필요한게 아니기 때문에 이렇게 해주었다. 그리고 @CookieValue 옆에 Long memberId 라고 정보를 넣어줬는데 @ModelAttribute 와 같이 해당 정보를 변수에 넣는것이다.

이후에 로직은 간단하다. 멤버 리포지토리에서 쿠키에 담긴 멤버의 정보를 바탕으로 타임리프에 모델을 보내줬다.

몇가지 주의할점은 그냥 home 과 loginHome을 차별화 해준 점인거 같다.

타임리프에 추가된 부분이다. th:text = 정보에 추가된 모델의 정보를 꺼내서 보여주는것을 확인할 수 있다. 저렇게 테그스트를 추가해준다면 로그인 : {사용자 이름} 이 나온다.

추가적으로 로그인에 성공시, 위에 쿠키를 만들때 타임아웃 시간을 따로 적지 않았기에 자동으로 세션 종료시에 소멸되는 쿠키가 생성된다. 그리고 웹 브라우저에서 서버에 요청시 memberId 쿠키를 지속적으로 보내준다.

로그 아웃 처리하기 - 쿠키

쿠키를 사용한 로그인에서 로그 아웃 기능을 만드는것은 어렵지 않다. 그냥 새로운 쿠키를 생성해줘서 똑같이 응답을 하지만 이번에는 쿠키의 지속 시간을 0으로 만들어서 응답이 끝난후에 바로 소멸되게 만들면 되는것이다.


쿠키와 보안 문제

쿠키에는 아쉽지만 많은 보안 문제가 존재한다. 예전에도 배웠던 내용중 하나이지만, 쿠키는 임의로 변경이 가능하다. 그렇기 때문에 쿠키에만 의존하는 웹 사이트라면은 사용자가 의도적으로 쿠키 정보를 바꿔서 새로운 사용자로 위장하고 웹사이트에 기존 의도와 다르게 사용할 수 있다. (예: 티케팅 웹사이트에서 쿠키 정보를 변경 후 지속적으로 사용)

쿠키에 예민한 정보를 담을 경우, (개인정보 ,신용카드 정보) 서버로 전달되는 과정에서 쿠키가 털리게 된다면 곧 개인정보의 유출로 이어지기 때문에 쿠키에만 의존하는 웹사이트는 보안상 문제가 많다.

대안 방법:

  1. 쿠키에 중요한 값을 노출하지 않고 랜덤값을 노출한다. 그리고 서버에서 토큰과 사용자 id를 매핑해서 서버가 토큰을 관리하게 해준다.

  2. 토큰은 해커가 임의에 값을 넣어도 찾을 수 없게 하고 예상 불가능하게 만들어야한다.

  3. 토큰이 털리게 되더라도 시간이 지나면 사용 못하게 만료시간을 짧게 해준다.


로그인 처리하기 - 세션 사용

앞서 설명했듯이 쿠키만으로 모든 정보를 보관하는것은 보안 이슈가 심각하기때문에 결국 중요한 정보는 모두 서버에 저장하는게 좋다. 그리고 추정 불가능한 식별자 값으로 정보를 연결시키고 이렇게 서버에 중요한 정보를 보관하고 연결을 유지하는 방법을 세션이라고 한다.

쿠키와 비슷한 원리지만 추가되는 과정이 있다. 먼저 똑같이 로그인 정보를 받게 되고 DB를 통한 검증이 완료되면 세션 저장소라는것을 생성하게 된다.

이후에는 그 세션안에 추정 불가능한 아이디를 sessionId 로 저장시키고 사용자의 값을 value 로 연결 시켜준다.

이후에는 생성된 추정 불가능 세션아이디를 쿠키에 담아서 쿠키 저장소에 정보를 보관하게 해주는것이다.

Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
response.addCookie(idCookie);

전 버전에서 쿠키를 통해서 정보를 전해주는 방식을 생각해보자. 쿠키의 값에 memberId 라는 정보를 담아서 로그인 사용자의 아이디 정보를 담아서 쿠키 저장소에 보관하게 만들었다. 이렇게 보낼 경우 쿠키에 헤더 정보만 봐도 멤버의 아이디를 저장한것을 바로 알아 낼 수 있다. 그렇지만 세션을 통한 쿠키 전달은 아예 쿠키에 담기는 아이디를 추정 불가능 아이디로 만들게 되서 오직 서버에서만 그 쿠키에 정보를 해석할 수 있다.

추가적으로 로그아웃 기능을 통해서만 쿠키를 제거할 수 있었던 전 버전에 비교해서 세션은 일정 시간이 지나면은 바로 삭제할 수 있어서 보안상으로도 더 유리하다.


로그인 처리하기 - 세션 구현 (스프링)

스프링과 상관없이 직접 세션을 구현했을때 자바에서 Map을 사용했다. 실제로도 서블릿도 세션을 구현하기 위해 HttpSession 기능을 제공하는데 Map을 사용한 구현과 굉장히 유사하다. 서블릿을 통해 HttpSession 을 생성하면 다음과 같은 쿠키를 생성한다.

쿠키 이름은 JSESSIONID고 값은 추정 불가능한 랜덤값이다.

@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";
   }
   //로그인 성공 처리
   //세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
   HttpSession session = request.getSession();
   //세션에 로그인 회원 정보 보관
   session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
   return "redirect:/";
}

강의에서 나온 코드다. SessionConst.LOGIN_MEMBER 같은 경우는 그냥 이름을 중복적으로 써야하기 때문에 넣은 상수 클래스로 값은 "loginMember"다.

세션을 생성하는것은 request.getSession() 을 사용하면 된다. getSession() 을 하게되면은 default 값으로 안에 true 가 들어가고 false 를 지정할 수 있는데 차이점은 아래와 같다.

.setAttribute("이름",값) 은 세션에 데이터를 보관하는 방법이라고 생각하면된다.

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

로그아웃 기능이다. 쿠키 같은 경우에는 쿠키의 시간을 0으로 해주었지만 세션은 invalidate() 를 사용해서 세션을 삭제 해줄 수 있다.

@GetMapping("/")
public String homeLoginV3(HttpServletRequest request, Model model) {
   //세션이 없으면 home
   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";
}

로그인을 구현한 것이다. getSession 같은 경우 원래 세션이 존재한다면은 기존 세션을 불러오고 아니면은 새롭게 생성하지만 일반적인 로그인 화면에서 항상 세션을 새로 생성한 이유는 없음으로 false 로 값을 지정해주었다. 그리고 세션이 존재하지 않는다면 일반적인 home.html 을 만들어주고 세션이 존재한다면 세션에서 회원을 가지고 와야함으로 .getAttribute로 로그인 시점에 세션에 보관한 회원 객체를 찾아주면 된다.

@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 를 활용해서 더 쉽게 로그인 회원 정보를 구하는것을 확인 가능하다.

추가적인 정보지만 로그인 URL 에 쿠키아이디가 나오는게 싫다면 properties 에 저 라인을 추가하면 된다. 이런 방법은 웹 브라우저가 쿠키를 지원하지 않을때 쿠키대신 URL을 통해서 세션을 유지하는 유지보수가 힘든 방법을 얘기한다.


세션 타임아웃 설정

앞서 설명에 세션에 정보를 파기하는게 좋다고 얘기했는데 꼭 보안상에 이유만으로 삭제하는게 아니고 다른 이유도 존재한다. 세션같은 경우는 로그아웃을 하면 invalidate() 가 콜 되면서 세션을 파기하지만 로그아웃을 안할경우에는 그 정보가 남아있고 이것은 문제가 된다.

HTTP는 비연결성임으로 로그아웃을 하지않고 그냥 웹 브라우저 종료를 하게 될경우 서버 입장에서 언제 세션 데이터를 삭제해야하는지 판단이 힘들다. 그리고 세션이 무한정으로 남게되면 아래와 같은 문제가 있다.

세션 종료 시점은 언제가 좋을까? 가장 좋은 방법은 사용자가 서버에 최근에 요청한 시간을 기준으로 30분 정도를 유지해주는것이다.

타임아웃 기능덕에 개발자는 몇가지 라인을 추가하는것으로 보수유지가 편해진다.

개인적으로 중요하게 생각하는 부분이다. 서버에 장애로 이어질 수 있는 가능성이 세션에서도 시작된다. 항상 최소한의 데이터만 보관해야 한다는것을 잊지말고 타임아웃 기능을 추가하는걸 습관하 하자.

profile
성장하는 사람

0개의 댓글