Cookie가 뭐야?

Cookie는 HTTP의 비연결성, 무상태성을 보완하기 위한 방법 중 하나로, 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각을 말한다. 서버가 보내준 조각(쿠키)를 브라우저가 저장을 해 놓았다가 동일한 서버에게 다시 요청을 보낼 때 조각(쿠키)와 함께 전송한다.

쿠키를 통해 동일한 사용자가 요청한 것인지 아닌지를 판단할 때 사용한다. 

 

* HTTP의 비연결성

클라이언트가 서버에 요청했을 때, 그 요청에 맞는 응답을 보낸 후 연결을 끊는 처리 방식

 

* 무상태성

클라이언트와 첫 번째 통신에서 데이터를 주고받았다 해도, 두 번째 통신에서는 이전 데이터를 유지하지 않는다.

 

✅ HTTP의 비연결성, 무상태성을 보완하기 위해 CookieSession이 사용된다.

 

 Cookie의 형태

쿠키는 <Key, Value> 형태로 구성된 String으로 4KB이상 저장할 수 없다.

보통 브라우저에 의해 저장되며,  Request들의 Cookie HTTP 헤더 안에 포함되어 전송된다.

또한 브라우저마다 저장되는 쿠키가 다르다, 크롬에서 로그인해서 받은 쿠키는 파이어폭스에서 사용할 수 없다.

 

Cookie를 이용한 로그인 구현

  1. 로그인 성공 시 서버가 사용자에게 쿠키를 넘겨줌.
  2. 브라우저가 쿠키를 저장
  3. 사용자가 서버에 다음 요청을 할때마다 쿠기도 함께 서버에게 보냄.
  4. 서버는 사용자 요청에 담겨온 쿠키를 통해 로그인 했는지, 유저 정보, 권한 등을 확인할 수 있음.

 

쿠키 설정 방법

// 쿠키 설정
Cookie cookie = new Cookie([cookie-name], [cookie-value]);

// 쿠키 유효기간 설정
cookie.setMaxAge(60 * 60); // 60초 * 60번 == 1시간

// 쿠키를 응답 헤더에 추가
response.addCookie(cookie); // response는 HttpServletResponse 객체이다.

 

로그인 기능 목록

  • 회원가입
    • 아이디 중복 체크 
    • password == passwordCheck 체크 
    • nickName 중복 체크
  • 로그인
    • login password 체크
  • 로그아웃

 

1. SpringBoot Initializr

https://start.spring.io/

 

dependency

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

 

2. Member Entity 생성

package com.millpre.cookielogin.member.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    @Id
    @NotNull
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    private String loginId;

    @NotNull
    private String password;

    @NotNull
    private String nickName;
}

 

3. MemberRepository 생성

package com.millpre.cookielogin.member.entity.repository;

import com.millpre.cookielogin.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {
    boolean existsByLoginId(String loginId); // 존재하는 loginId인지 확인
    boolean existsByNickName(String nickName); // 존재하는 닉네임인지 확인
    Optional<Member> findByLoginId(String loginId); // loginId로 Member찾기
}

 

4. JoinDto 생성

package com.millpre.cookielogin.member.entity.dto;

import com.millpre.cookielogin.member.entity.Member;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class JoinDto {
    @NotBlank(message = "로그인 아이디가 비어있습니다.")
    private String loginId;
    @NotBlank(message = "비밀번호가 비어있습니다.")
    private String password;
    private String passwordCheck;
    @NotBlank(message = "닉네임이 비어있습니다.")
    private String nickName;

    public Member toEntity() {
        return Member.builder()
                .loginId(this.loginId)
                .nickName(this.nickName)
                .password(this.password)
                .build();
    }
}

 

5. LoginDto 생성

package com.millpre.cookielogin.member.entity.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class LoginDto {
    private String loginId;
    private String password;
}

 

6. MemberService 생성

package com.cookie.login.service;

import com.cookie.login.domain.dto.JoinRequest;
import com.cookie.login.domain.dto.LoginRequest;
import com.cookie.login.domain.entity.User;
import com.cookie.login.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    /* loginId 중복 체크
    * 중복O true
    * 중복X false
    * */
    public boolean checkLoginIdDuplicate(String loginId) {
        return userRepository.existsByLoginId(loginId);
    }

    /* nickName 중복 체크
    * 중복O true
    * 중복X false
    * */
    public boolean checkNickNameDuplicate(String nickName) {
        return userRepository.existsByNickName(nickName);
    }

    /* 회원 가입 기능
    * loginId, nickName 중복 체크는 Controller 에서 => 에러 메시지 출력을 위해서
    * */
    public void join(JoinRequest request) {
        userRepository.save(request.toEntity());
    }

    /* 로그인 기능
    * 로그인 아이디가 존재하지 않거나 비밀번호가 일치하지 않는 경우 null return */
    public User login(LoginRequest request) {
        Optional<User> optionalUser = userRepository.findByLoginId(request.getLoginId());

        // loginId가 일치하는 User가 없으면 null return
        if (optionalUser.isEmpty()) return null;

        User user = optionalUser.get();

        // 찾아온 User의 password와 입력된 password가 다르면 null return
        if (!user.getPassword().equals(request.getPassword())) return null;

        return user;
    }

    /* userId(Long)를 입력받아 User return 해주는 기능
    * 인증, 인가 시 사용
    * userId가 DB에 없거나, null이면 null return*/
    public User getLoginUserById(Long userId) {
        if (userId == null) return null;
        Optional<User> optionalUser = userRepository.findById(userId);
        if(optionalUser.isEmpty()) return null;
        return optionalUser.get();
    }
}

 

7. MemberController 생성

package com.cookie.login.controller;

import com.cookie.login.domain.UserRole;
import com.cookie.login.domain.dto.JoinRequest;
import com.cookie.login.domain.dto.LoginRequest;
import com.cookie.login.domain.entity.User;
import com.cookie.login.service.UserService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

@Controller
@RequiredArgsConstructor
@RequestMapping("/cookie-login")
public class HomeController {
    private final UserService userService;
    @GetMapping(value = {"/", ""})
    public String home(@CookieValue(name="userId", required = false) Long userId,
                       Model model) {
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        User loginUser = userService.getLoginUserById(userId);
        System.out.println(loginUser);

        if (loginUser != null) {
            System.out.println(loginUser.getNickName());
            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";
    }

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

        User loginUser = userService.getLoginUserById(userId);

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

        model.addAttribute("loginId", loginUser.getLoginId());
        model.addAttribute("nickname", loginUser.getNickName());
        model.addAttribute("role", loginUser.getRole());

        return "info";
    }

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

        User loginUser = userService.getLoginUserById(userId);

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

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

    @GetMapping("/login")
    public String login(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,
                        HttpServletResponse response,
                        BindingResult bindingResult,
                        Model model) {
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        User user = userService.login(loginRequest);

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

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

        return "redirect:/cookie-login";
    }


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

        // loginId 중복 확인
        if (userService.checkLoginIdDuplicate(joinRequest.getLoginId())) {
            bindingResult.addError(new FieldError("joinRequest", "loginId", "로그인 아이디가 중복됩니다."));
        }

        // 닉네임 중복 체크
        if (userService.checkNickNameDuplicate(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("/logout")
    public String logout(HttpServletResponse response, Model model) {
        model.addAttribute("loginType", "cookie-login");
        model.addAttribute("pageName", "쿠키 로그인");

        Cookie cookie = new Cookie("userId", null);
        cookie.setMaxAge(0);
        response.addCookie(cookie);
        return "redirect:/cookie-login";
    }
}

 

참고

 

[Spring Boot] Cookie를 사용한 로그인 구현

쿠키(Cookie) 란? 쿠키 : 사용자가 웹사이트 접속시 사용자의 개인 장치에 다운로드되고 브라우저에 저장되는 작은 텍스트 파일 웹사이트는 이 쿠키를 이용해 사용자의 장치를 인식하고 일부 데이

chb2005.tistory.com

728x90

+ Recent posts