오봉이와 함께하는 개발 블로그

스프링 MVC 2 - 로그인 처리 쿠키 사용, 쿠키 보안 문제 본문

BE/Spring

스프링 MVC 2 - 로그인 처리 쿠키 사용, 쿠키 보안 문제

오봉봉이 2022. 8. 28. 22:49
728x90

로그인 처리 - 쿠키 사용

로그인 상태 유지해야 한다.
로그인 상태를 어떻게 유지할 수 있을까?
먼저 쿼리 파라미터를 계속 유지하면서 보내는 방법이 있을 수 있겠다. 하지만 이 방법은 매우 어렵고 번거로운 작업이기 때문에 쿠키를 사용해보자.

쿠키

서버에서 로그인에 성공하면 HTTP 응답에 쿠키를 담아서 브라우저에 전달하자.
그러면 브라우저는 앞으로 해당 쿠키를 지속해서 보내준다.

쿠키에는 영속 쿠키세션 쿠키가 있는데 우리는 브라우저 종료시 로그아웃 되길 기대하기 때문에 우리는 세션 쿠키가 필요하다.

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

LoginController - 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());
    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:/";
}

쿠키 생성 로직

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

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

실행을 시키면 크롬 브라우저를 통해 HTTTP 응답 헤더에 쿠키가 추가된 것을 확인할 수 있다.
로그인에 성공하면 로그인 한 사용자 전용 홈 화면을 보여주자.

홈 - 로그인 처리

@Slf4j
@Controller
@RequiredArgsConstructor
public class HomeController {

    private final MemberRepository memberRepository;

//    @GetMapping("/")
//    public String home() {
//        return "home";
//    }

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

        // login
        Member loginMember = memberRepository.findById(memberId);
        if (loginMember == null) {
            return "home";
        }

        model.addAttribute("member", loginMember);
        return "loginHome";
    }
}
  • @CookieValue를 사용하면 편리하게 쿠키를 조회할 수 있다.
  • 로그인하지 않은 사용자도 홈에 접근할 수 있기 때문에 required = false를 사용하자.
    • 값이 필수가 아니라는 것

홈 - 로그인 사용자 전용

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>홈 화면</h2>
    </div>

    <h4 class="mb-3" th:text="|로그인: ${member.name}|">로그인 사용자 이름</h4>

    <hr class="my-4">

    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg" type="button"
                    th:onclick="|location.href='@{/items}'|">
                상품 관리
            </button>
        </div>
        <div class="col">
            <form th:action="@{/logout}" method="post">
                <button class="w-100 btn btn-dark btn-lg" onclick="location.href='items.html'" type="submit">
                    로그아웃
                </button>
            </form>
        </div>
    </div>

    <hr class="my-4">

</div> <!-- /container -->

</body>
</html>
  • th:text="|로그인: ${member.name}|"
    • 로그인에 성공항 사용자의 이름을 출력한다.

로그아웃 기능

로그아웃 방법은 다음과 같다.

  • 세션 쿠키이므로 웹 브라우저 종료시
  • 서버에서 해당 쿠키의 종료 날짜를 0으로 지정
  @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);
  }

실행해보면 로그아웃도 응답 쿠키를 생성하는데 Max-Age=0을 확인할 수 있다.
해당 쿠키는 즉시 종료된다.

쿠키와 보안 문제

쿠키를 사용해서 로그인id를 전달해서 로그인을 유지할 수 있었다. 하지만 여기에는 심각한 보안 문제가 있다.

보안 문제

  • 쿠키의 값은 임의로 변경할 수 있다.
    • 클라이언트가 쿠키를 강제로 변경하면 다른 사용자가 된다.
    • 실제 웹브라우저 개발자모드 -> Application -> Cookie 변경으로 확인 가능
    • Cookie: memberId=1 -> Cookie: memberId=2로 바꾸면 다른 사용자의 이름이 보인다.
  • 쿠키에 보관된 정보는 훔쳐갈 수 있다.
    • 만약 쿠키에 개인정보나, 신용카드 정보가 있다면 심각한 문제가 발생한다.
    • 쿠키 정보는 웹 브라우저에도 보관되고, 네트워크 요청마다 계속 클라이언트에서 서버로 전달된다.
    • 쿠키의 정보가 나의 로컬PC에서 털릴 수도, 네트워크 전송 구간에서 털릴 수도 있다.
  • 해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.
    • 해커가 쿠키를 훔쳐가서 그 쿠키로 악의적인 요청을 계속 시도할 수 있다.

대안

  • 쿠키에 중요한 정보를 노출시키지 않고, 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 노출하고, 서버에서 토큰과 사용자 id를 매핑해서 인식하여 서버에서 토큰을 관리한다.
  • 토큰은 해커가 임의의 값을 넣어도 찾을 수 없도록 예상 불가능 해야 한다.
  • 해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 해당 토큰의 만료시간을 짧게 유지한다.
  • 해킹이 의심되는 경우 서버에서 해당 토큰을 강제로 제거한다.
출처 : 인프런 김영한 지식공유자님 강의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
728x90
Comments