[Spring] 로그인 처리 - 쿠키 사용

·2024년 3월 30일

Spring

목록 보기
8/26
post-thumbnail

💡로그인 처리 - 쿠키 사용

로그인 상태 유지하기

  • 쿼리 파라미터를 계속 유지하면서 보내는 것은 매우 어렵고 번거로운 작업이다.

📗쿠키

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

쿠키에는 영속 쿠키세션 쿠키가 있다.

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

➡️ 브라우저 종료 시 로그아웃이 되길 기대하므로, 우리에게 필요한 것은 세션 쿠키이다.

📗로그인

(1)로그인 성공 시 세션 쿠키를 생성하자.

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());

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

📌실행

  • 크롬 브라우저를 통해 HTTP 응답 헤더에 쿠키가 추가된 것을 확인할 수 있다.

(2) 홈 - 로그인 처리

@Slf4j
@Controller
@RequiredArgsConstructor
public class HomeController {
    private final MemberRepository memberRepository;

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

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

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

        model.addAttribute("member", loginMember);
        return "loginHome";
    }
}
  • 기존 home()에 있는 @GetMapping("/")`은 주석 처리하자.
  • @CookieValue를 사용하면 편리하게 쿠키를 조회할 수 있다.
  • 로그인 하지 않은 사용자도 홈에 접근할 수 있기 때문에 required = false를 사용한다.

📌로직 분석

  • 로그인 쿠키(memberId)가 없는 사용자는 기존 home으로 보낸다.
  • 로그인 쿠키가 있어도 회원이 없으면 home으로 보낸다.
  • 로그인 쿠키(memberId)가 있는 사용자는 로그인 사용자 전용 홈 화면인 loginHome으로 보낸다.
  • 추가로 홈 화면에서 회원 관련 정보도 출력해야 하므로 member데이터도 모델에 담아서 전달한다.

홈 - 로그인 사용자 전용

loginHome.html

<!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}|" : 로그인에 성공한 사용자 이름을 출력한다.
  • 상품관리, 로그아웃 버튼을 노출한다.

실행


  • 로그인에 성공하면 사용자 이름이 출력되면서 상품관리, 로그아웃 버튼을 확인할 수 있다.
  • 로그인에 성공 시 세션쿠키가 지속해서 유지되고, 웹브라우저에서 서버에 요청시 memberId쿠키를 계속 보내준다.

📗로그아웃

📌로그아웃 하는 두가지 방법

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

LoginController - logout 기능 추가

@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를 확인할 수 있다.
  • 해당 쿠키는 즉시 종료된다.

💡 쿠키와 보안 문제

📌보안 문제

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

📌대안

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

0개의 댓글