s00jin 님의 블로그

3. [로그인] 쿠키 로그인 구현하기 - Spring/SpringBoot 본문

프로젝트/하고 싶은거 다해보는 내 사이트

3. [로그인] 쿠키 로그인 구현하기 - Spring/SpringBoot

s00jin 2025. 7. 1. 20:52

쿠키란 무엇인가

간단하게 말하면 사용자 정보 같은 파일이 로컬에 저장되는 것이다!

사용자의 장치에 다운로드 되고 브라우저에 저장되는 작은 텍스트 파일
  • 로그인 성공 시 서버가 쿠키에 사용자 정보를 넣어줌
  • 클라이언트 측에서 다음 요청을 할 때마다 이 쿠키를 서버에 같이 보내줌
  • 서버에서는 이 쿠키를 확인해 로그인 했는지 확인

쿠키 생성

  • 쿠키 생성
    • new Cookie() 메서드 사용
    • Cookie cookie = new Cookie(”키”, “값”);
    • Cookie cookie = new Cookie(”userId”, String.valueOf(user.getId()));
  • 유효 시간 설정
    • cookie.setMaxAge(60*60);
    • 1시간 설정 ⬆️
  • response(응답)에 쿠키 태우기
    • response.addCookie(cookie);
    • response는 HttpServletResponse의 객체

쿠키 확인

  • @CookieValue 어노테이션 사용
  • @CookieValue(name = "userId", required = false) Long userId

쿠키 파기

  • 똑같은 Key 값을 넣어주고 Value 값은 null을 넣어 새로 쿠키 생성
    • Cookie cookie = new Cookie("userId", null);
  • setMaxAge(0)을 통해 유효 시간을 0초로 설정
    • cookie.setMaxAge(0);
  • response에 addCookie를 통해 다시 넣어주기 → 쿠키 파기됨
    • response.addCookie(cookie);
더보기

[Spring]Cookie와 활용법(읽기, 생성 및 저장)

⬆️ 이 블로그에 쿠키에 대한 설명이 잘 나와있는것 같으니 더 궁금하면 확인!


구현

UserRepository

package org.mySite.user.repository;

public interface UserRepository extends JpaRepository<User, Long> {
    // 1개라도 존재하면 바로 탈출 existsBy~
    boolean existsByLoginId(String loginId);
    boolean existsByNickname(String nickname);
    Optional<User> findByLoginId(String loginId);
    Optional<User> findById(Long Id);
}

UserService

package org.mySite.user.service;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public User getLoginUser(Long loginRequest){
        User user = userRepository.findById(loginRequest)
                .orElseThrow(() -> new IllegalArgumentException("UserId not found"));

        return user;
    }

    // ------ 회원가입 --------

    // loginId 중복 체크
    public boolean checkLoginId(String loginId) {
        // 1건이라도 있으면 바로 스캔 종료 후 true 리턴
        return userRepository.existsByLoginId(loginId);
    }

    // nickname 중복 체크
    public boolean checkNickname(String nickname) {
        return userRepository.existsByNickname(nickname);
    }

    // JoinRequest을 입력 받아 User로 변환
    public void join(JoinRequest request) {
        userRepository.save(request.toEntity());
    }

    // ------로그인-------
    public User login(LoginRequest request){

        // 일치하는 User 찾기
        User user = userRepository.findByLoginId(request.getLoginId())
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        // 찾은 User 비밀번호와 입력된 비밀번호 일치하는지 확인
        if(!user.getPassword().equals(request.getPassword())) {
            return null;
        }

        return user;
    }
}

CookieLoginController

package org.mySite.user.controller;

@Controller
@RequiredArgsConstructor
@RequestMapping("/cookie-login")
public class CookieLoginController {

    private final UserService userService;

    // 쿠키 로그인 페이지
    @GetMapping(value = {"", "/"})
    // @CookieValue 어노테이션을 통해 쿠키값 받아오기 가능
    public String home(@CookieValue(name = "userId", required = false) Long userId, Model model){
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        // 로그인 구현 전 임시 추가
        //model.addAttribute("nickname", "kk");

        if (userId != null) {
            User loginUser = userService.getLoginUser(userId);
            model.addAttribute("nickname", loginUser.getNickname());

        }

        return "home";
    }

    // 회원가입 페이지
    @GetMapping("/join")
    public String joinPage(Model model){
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        model.addAttribute("joinRequest", new JoinRequest());
        return "join";
    }

    // 회원가입
    @PostMapping("/join")
    public String join(@Valid @ModelAttribute JoinRequest joinRequest, BindingResult bindingResult, Model model){
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        // loginId 중복 체크
        if(userService.checkLoginId(joinRequest.getLoginId())) {
            bindingResult.addError(new FieldError("joinRequest", "loginId", "중복된 아이디입니다."));
        }

        // 닉네임 중복 체크
        if (userService.checkNickname(joinRequest.getNickname())){
            bindingResult.addError(new FieldError("joinRequest", "nickname", "중복된 닉네임입니다."));
        }

        // password와 passwordCheck 확인
        if (!joinRequest.getPassword().equals(joinRequest.getPasswordCheck())) {
            bindingResult.addError(new FieldError("joinRequest", "passwordCheck", "비밀번호가 일치하지 않습니다."));
        }

        if (bindingResult.hasErrors()) {
            return "join";
        }

        userService.join(joinRequest);
        return "redirect:/cookie-login";
    }

    // 로그인 페이지
    @GetMapping("/login")
    public String loginPage(Model model) {
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        model.addAttribute("loginRequest", new LoginRequest());
        return "login";
    }

    // 로그인
    @PostMapping("/login")
    public String login(@ModelAttribute LoginRequest loginRequest, BindingResult bindingResult, HttpServletResponse response, Model model) {
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        User user = userService.login(loginRequest);
        // System.out.println("입력된 로그인 ID: " + loginRequest.getLoginId());

        // 틀릴 경우 global error return
        if(user == null){
            bindingResult.reject("loginFail", "로그인 아이디 또는 비밀번호가 틀렸습니다.");
        }

        if(bindingResult.hasErrors()) {
            return "login";
        }

        // 로그인 성공 => 쿠키 생성
        Cookie cookie = new Cookie("userId", String.valueOf(user.getId()));
        cookie.setMaxAge(60*60); // 유효 시간 1시간
        response.addCookie(cookie);

        return "redirect:/cookie-login";
    }

    // -----------로그아웃 ------------------
    @GetMapping("/logout")
    public String logout(HttpServletResponse response, Model model){
        model.addAttribute("pageName", "쿠키 로그인");
        model.addAttribute("loginType", "cookie-login");

        Cookie cookie = new Cookie("userId", null);
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        return "redirect:/cookie-login";
    }

    // ------------ 회원 정보 ------------------
    @GetMapping("/info")
    public String infoPage(@CookieValue(name = "userId", required = false) Long userId, Model model){
        model.addAttribute("pageName", "쿠키 로그인");
        model.addAttribute("loginType", "cookie-login");

        User user = userService.getLoginUser(userId);

        if (user == null){
            return "redirect:/cookie-login/login";
        }

        model.addAttribute("user", user);

        return "info";
    }

    // --------- 관리자 페이지 --------------
    @GetMapping("/admin")
    public String adminPage(@CookieValue(name = "userId", required = false) Long userId, Model model){
        model.addAttribute("pageName", "쿠키 로그인");
        model.addAttribute("loginType", "cookie-login");

        User user = userService.getLoginUser(userId);

        if (user == null){
            return "redirect:/cookie-login/login";
        }

        if (!user.getRole().equals(UserRole.ADMIN)){
            return "redirect:/cookie-login";
        }

        return "admin";
    }
}


템플릿

admin

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="|${pageName}|"></title>
</head>
<body>
<div>
  <h1><a th:href="|/${loginType}|">[[${pageName}]]</a></h1><hr>
  <h2>관리자 페이지</h2>
  <h3>인가에 성공하였습니다.</h3>
</div>
</body>
</html>
 

home

<!DOCTYPE html>
<html lang="ko">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="|${pageName}|"></title>
</head>
<body>
    <div>
        <h1><a th:href="|/${loginType}|">[[${pageName}]]</a></h1>
        <hr>
        <div th:if="${nickname == null}">
            <h3>로그인 상태가 아닙니다.</h3>
            <button th:onclick="|location.href='@{/{loginType}/join (loginType=${loginType})}'|">회원 가입</button>
            <button th:onclick="|location.href='@{/{loginType}/login (loginType=${loginType})}'|">로그인</button>

        </div>
        <div th:unless="${nickname == null}">
            <h3>[[${nickname}]]님 환영합니다!</h3>
            <button th:onclick="|location.href='@{/{loginType}/info (loginType=${loginType})}'|">유저 정보</button>
            <button th:onclick="|location.href='@{/{loginType}/admin (loginType=${loginType})}'|">관리자 페이지</button>
            <button th:onclick="|location.href='@{/{loginType}/logout (loginType=${loginType})}'|">로그아웃</button>
        </div>
    </div>

</body>
</html>

 

info

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="|${pageName}|"></title>
</head>
<body>
<div>
  <h1><a th:href="|/${loginType}|">[[${pageName}]]</a></h1><hr>
  <h2>유저 정보</h2>
  <div>
    <div th:text="|loginId : ${user.loginId}|"></div>
    <div th:text="|nickname : ${user.nickname}|"></div>
    <div th:text="|role : ${user.role}|"></div>
  </div>
</div>

</body>
</html>
 

join

  <!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="|${pageName}|"></title>
</head>
<body>
<div>
  <h1><a th:href="|/${loginType}">[[${pageName}]]</a></h1><hr>
  <h2>회원 가입</h2>
  <form th:method="post" th:action="|@{/{loginType}/join (loginType=${loginType})}|" th:object="${joinRequest}">
    <div>
      <label th:for="loginId">로그인 아이디: </label>
      <input type="text" th:field="*{loginId}" th:errorclass="error-input"/>
      <div class="error-class" th:errors="*{loginId}"></div>
    </div>
    <br>
    <div>
      <label th:for="password">비밀번호 : </label>
      <input type="password" th:field="*{password}" th:errorclass="error-input"/>
      <div class="error-class" th:errors="*{password}"></div>
    </div>
    <br/>
    <div>
      <label th:for="passwordCheck">비밀번호 체크 : </label>
      <input type="password" th:field="*{passwordCheck}" th:errorclass="error-input"/>
      <div class="error-class" th:errors="*{passwordCheck}"></div>
    </div>
    <br/>
    <div>
      <label th:for="nickname">닉네임 : </label>
      <input type="text" th:field="*{nickname}" th:errorclass="error-input"/>
      <div class="error-class" th:errors="*{nickname}"></div>
    </div>
    <br/>
    <button type="submit">회원 가입</button>
  </form>
</div>
</body>
</html>

<style>
  .error-class {
    color: red;
  }
  .error-input {
    border-color: red;
  }
</style>

 

login

 <!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="|${pageName}|"></title>
</head>
<body>
<div>
  <h1><a th:href="|/${loginType}|">[[${pageName}]]</a></h1> <hr/>
  <h2>로그인</h2>
  <form th:method="post" th:action="|@{/{loginType}/login (loginType=${loginType})}|" th:object="${loginRequest}">
    <div>
      <label th:for="loginId">로그인 아이디 : </label>
      <input type="text" th:field="*{loginId}"/>
    </div>
    <br/>
    <div>
      <label th:for="password">비밀번호 : </label>
      <input type="password" th:field="*{password}"/>
    </div>
    <div th:if="${#fields.hasGlobalErrors()}">
      <br/>
      <div class="error-class" th:each="error : ${#fields.globalErrors()}" th:text="${error}"/>
    </div>
    <br/>
    <button type="submit">로그인</button>
    <button type="button" th:onclick="|location.href='@{/{loginType}/join (loginType=${loginType})}'|">회원가입</button><br>

  </form>
</div>

</body>
</html>

<style>
  .error-class {
      color: red;
  }
  .error-input {
      border-color: red;
  }
</style>