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

Vector Class

배열은 선언시 설정한 크기 이상으로 사용할 수 없다.

Vector Class 를 사용하면 동적으로 크기가 관리된다. 

public class Vector extends AbstractList implements List, Cloneable, java.io.Serializable {
	protected Object elementData[];
 	...
}

 

생성자

10개의 객체를 저장할 수 있는 Vector 인스턴스를 생성.

10개 이상의 인스턴스가 저장되면 자동으로 크기가 증가

Vector();

 

add(Object o)

Vector에 객체를 추가한다. 추가에 성공하면 결과값으로 true, 실패하면 false를 반환한다.

Vector v = new Vector();
Object o = new Object();
v.add(o);

 

 

remove(Object o)

Vector에 저장되어 있는 객체를 제거한다. 제거에 성공하면 결과값으로 true, 실패하면 false를 반환한다.

Vector v = new Vector();
Object o = new Object();
v.add(o);

v.remove(o);

 

 

isEmpty()

Vector가 비어있는지 검사한다. 비어있으면 true, 비어있지 않으면 false를 반환한다.

Vector v = new Vector();
v.isEmpty(); // true

 

 

get(int index)

지정된 위치(index)의 객체를 반환한다. 반환타입이 Object타입이므로 적절한 타입으로의 형변환이 필요하다.

Vector v = new Vector();
Object o = new Object();
v.add(o);

v.get(1); // Object o

 

size()

Vector에 저장된 객체의 개수를 반환

Vector v = new Vector();
Object o = new Object();
v.add(o);

v.size(); // 1

 

728x90

'Language > JAVA' 카테고리의 다른 글

[JAVA] Enum 열거형  (0) 2023.11.04
[JAVA] Getter/Setter  (0) 2023.11.02
[JAVA] 생성자  (0) 2023.10.25
[JAVA] 값 복사와 주소 복사  (0) 2023.10.25
[JAVA] Java Collections / Array / ArrayList  (2) 2023.10.25

📦 Enum

An enum type is a special data type that enables for a variable to be a set of predefined constants.
enum type은 미리 정의된 상수의 집합이 될 수 있도록 하는 특별한 데이터 타입이다.

The variable must be equal to one of the values that have been predefined for it.
변수는 미리 정의된 값 중 하나와 같아야 합니다.

Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST) and the days of the week.
일반적인 예로는 나침반 방향(NORTH, SOUTH, EAST 및 WEST 값)과 요일이 포함됩니다.

 

방향 대신 Day로 예시를 들어보면 다음과 같다.

enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}
Day.SUNDAY // SUNDAY

 

 

enum은 열거체를 비교할 때 실제 값뿐만 아니라 타입까지도 체크한다.

enum의 상숫값이 재정의되더라도 다시 컴파일할 필요가 없다.

 

enum Color {
    RED(3),
    YELLOW(4),
    BLUE(5);
    
    private final int value;
    Color(int value) { this.value = value; }
    public int getValue() { return value; }
}

 

System.out.println(Color.RED.name());      // RED
System.out.println(Color.RED.value());      // 3

 

1. 자바에서 상수 정의하기:: static final 

상수를 정의하는 방법에는 enum이 나타나기 전에 static final을 사용하는 방법이 있었다.

static final을 사용하는 방법 예시는 다음과 같다.

 

예시 상황으로 사용자는 1에서 100사이의 값만 입력할 수 있는 프로그램이 있다고 하자.
여기서 1과 100을 상수로 선언할 것이다. static final을 활용한 코드는 아래와 같다.

 

public class Application {
    private static final int MIN_SIZE = 1;
    private static final int MAX_SIZE = 100;
    
     public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        int userInput = parseInt(in.readLine());
        if(userInput >= MIN_SIZE
                && userInput <= MAX_SIZE) {
            System.out.println("good");
        }
    }
}

 

 

이러한 static final 이 존재하였는데 왜 enum 이라는 열거형이 나왔을까?

 

Why created the enum?

  1. final static으로 상수를 선언하는 경우 상수 이름과 상수 값 자체는 관련이 없게된다.
  2. 이름의 충돌이 발생할 수 있다.

다음 예시를 통해 배경을 더 정확히 이해해보자!

 

final static으로 상수를 선언하는 경우 상수 이름과 상수 값 자체는 관련이 없게된다.

public class Application {
    private static final String SPRING = "봄";
    private static final String SUMMER = "여름";
    private static final String AUTUMN = "가을";
    private static final String WINTER = "겨을";
    
    public static void main(String[] args) {
    	System.out.println(SPRING); // 봄
        System.out.println(SUMMER); // 여름
        System.out.println(AUTUMN); // 가을
        System.out.println(WINTER); // 겨울
        
        String season = "봄";
        
        if (season == SPRING) {
        	System.out.printf("season is %s", SPRING); // season is 봄
        }
    }
}

 

 

이름의 충돌이 발생할 수 있다.

public class Framework {
    public static final int SPRING = 1;
    public static final int VUE = 2;
    public static final int NESTJS = 3;
}

public class Season {	
    public static final int SPRING = 1;
    public static final int SUMMER = 2;
    public static final int AUTUMN = 3;
    public static final int WINTER = 4;
}

// Season.SPRING Framework.SPRING 충돌

 

 

Advantages of using Enum instead of static final

  1. 코드가 단순해지고, 가독성이 좋아진다.
  2. 인스턴스 생성과 상속을 방지한다.
  3. enum 키워드 사용을 통해서 구현 의도가 열거형임을 분명하게 나타낼 수 있다.

 

static final 사용

private static final String SPRING = "봄";
private static final String SUMMER = "여름";
private static final String AUTUMN = "가을";
private static final String WINTER = "겨을";

 

 

enum 사용

public enum Season {
    SPRING("봄"),
    SUMMER("여름"),
    AUTUMN("가을"),
    WINTER("겨울");
    
    private final String value;
    Seson(String value) { this.value = value; }
    
    public String getValue() {
    	return value;
	}
}

 

final static 을 사용하여 선언한 상수보다 enum 을 사용한 상수 선언이 가독성 면에서 더 좋다.

 

 

 

 

 

 

Enum Types (The Java™ Tutorials > Learning the Java Language > Classes and Objects)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

 

11주차 과제: Enum

11주차 과제: Enum · Issue #11 · whiteship/live-study 목표 자바의 열거형에 대해 학습하세요. 학습할 것 (필수) enum 정의하는 방법 enum이 제공하는 메소드 (values()와 valueOf()) java.lang.Enum EnumSet 마감일시 2021

hyeonic.tistory.com

 

 

[Java] 자바의 상수(Constant), final 변수 정리

상수(Constant) 프로그래밍 언어에서 상수는 변하지 말아야 할 데이터를 임시적으로 저장하기 위한 수단으로 사용된다. 즉, 초기화 이후 재할당이 불가능하다는 뜻이다. final 자바에서는 상수를 구

ittrue.tistory.com

 

 

Java: enum의 뿌리를 찾아서...

이번 글에서는 자바 1.5버전부터 새롭게 추가된 열거형 상수인 enum(enumeration)에 대해 알아보겠습니다. 열거형은 서로 연관된 상수들의 집합입니다. 이번 글은 enum 정의와 enum 사용방법, 그리고 enum

www.nextree.co.kr

 

 

Java Enum 활용기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E

techblog.woowahan.com

 

728x90

'Language > JAVA' 카테고리의 다른 글

[JAVA] Vector Class  (1) 2023.11.29
[JAVA] Getter/Setter  (0) 2023.11.02
[JAVA] 생성자  (0) 2023.10.25
[JAVA] 값 복사와 주소 복사  (0) 2023.10.25
[JAVA] Java Collections / Array / ArrayList  (2) 2023.10.25

+ Recent posts