2024.01.07 - [Spring Boot] - [Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현
1. 쿠키란 ?
- 사이트에 방문 시, 브라우저는 서버에 request 보냄
- 서버는 이에 응답 (response) 해서 데이터와 페이지 정보등을 넘김 => response에는 브라우저에 저장하고자 하는 쿠키 존재 가능
- 서버가 생성한 쿠키는 브라우저로 전달되고 클라이언트는 이를 저장함
- 다음에 사이트에 방문 시, 클라이언트의 브라우저는 저장된 쿠키를 request와 함께 보냄
- 즉 쿠키는 사용자의 정보를 기억하고 식별하는데 사용되며 데이터를 전달하는 매개체임
- 제약 조건
- 도메인에 제한적 ( ex. 유튜브에서 보낸 쿠키는 유튜브로만 다시 보내짐)
- 유효기간 존재
- 공간적 제약
2. 쿠키 생성, 추가, 읽기
- 쿠키의 기본 구조
Set-Cookie: name=value; expires=[Date]; domain=[Domain]; path=[Path]; [secure]
- name=value
- 쿠키의 이름과 값 설정 (필수 항목)
- 서버와 브라우저가 쿠키를 식별하고 저장하는데 사용
- expires=[Date]
- 쿠키의 유효기간 설정
- 설정하지 않으면 쿠키는 세션 쿠키가 됨 => 브라우저가 닫히면 쿠키가 삭제됨
- domain=[Domain]
- 쿠키가 유효한 도메인 설정
- 설정하지 않으면 쿠키를 생성한 서버의 도메인으로 자동 설정
- path=[Path]
- 쿠키가 유효한 경로 설정
- 도메인 내의 특정 부분으로, 쿠키가 전송될 URL 제한함
- 기본적으로는 쿠키를 생성한 경로에서만 쿠키 사용가능 , '/' 로 설정 시 도메인의 모든 페이지에서 쿠키 사용 가능
- [secure]
- 설정되면 쿠키는 HTTPS 프로토콜을 통해서만 전송됨
- 보안 연결이 유지되는 동안에만 쿠키 데이터가 노출되도록 함
- HttpOnly : JavaScript와 같은 클라이언트 사이드 스크립트에서 쿠키 접근 못하도록 막음
- SameSite : 브라우저가 같은 사이트 요청만 쿠키를 전송하도록 제한, CSRF 공격 방지하는데 유용
- name=value
2-1. 쿠키 생성
- HttpServletResponse 객체 사용
- 필수적으로 name과 value 지정해야 함
// 쿠키 생성
Cookie cookie = new Cookie("쿠키 이름", "쿠키 값");
2-2. 쿠키 속성 설정
- 위에서 언급한 기본 구조에 해당하는 속성 설정 (필수 x)
// 쿠키 유효기간 설정 => 1시간
cookie.setMaxAge(60 * 60);
// 쿠키 유효도메인 설정 => localhost
cookie.setDomain("localhost");
// 쿠키 유효경로 설정 => 도메인의 모든 페이지
cookie.setPath("/");
// 쿠키 보안 설정 => HTTPS 프로토콜을 통해서만 전송
cookie.setSecure(true);
// 클라이언트 사이드 스크립트의 쿠키 접근 제한
cookie.setHttpOnly(true);
2-3. 쿠키 저장
- 클라이언트에 보내기 위해서 HttpServletResponse에 쿠키 추가
// HttpServletResponse response
// 쿠키 저장
response.addCookie(cookie);
2-4. 컨트롤러에서 쿠키 정보 읽기
- @CookieValue 어노테이션 사용해서 클라이언트에게 받은 요청에 포함된 쿠키의 값 읽기
@GetMapping("/info")
public String info(@CookieValue(name = "userId", required = false) Long userId){
System.out.println("쿠키 정보 읽기 (userId) : " + userId); // 읽어온 쿠키 정보 출력
}
2-5. 쿠키 삭제
- 동일한 이름의 쿠키를 생성하고 유효기간을 0으로 설정함 => 이 쿠키를 클라이언트에 다시 보냄으로써 쿠키 삭제
// 동일한 이름의 쿠키 생성
Cookie cookie = new Cookie("동일 쿠키 이름", null);
// 쿠키의 수명을 0으로 설정
cookie.setMaxAge(0);
// HttpServletResponse에 수명이 다 한 cookie 추가 => 쿠키 삭제
response.addCookie(cookie);
3. 로그인 프로젝트에 적용
- 로그인을 제외한 나머지 기능, 라이브러리 등은 아래 게시글 참고
2024.01.07 - [Spring Boot] - [Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현
3-1. CookieLoginController
- home
@GetMapping(value = {"", "/"})
public String home(@CookieValue(name = "memberId", required = false)
Long memberId, Model model) {
// 화면에 출력하기 위해 model에 속성 추가
model.addAttribute("loginType", "cookie-login");
model.addAttribute("pageName", "쿠키 로그인");
Member loginMember = memberService.getLoginMemberById(memberId);
// 로그인 되어있다면 model에 이름 속성 추가
if (loginMember != null) {
model.addAttribute("name", loginMember.getName());
}
return "home";
}
- @CookieValue를 통해서 쿠키의 값을 읽어옴
- 쿠키의 name에 해당하는 값이 존재한다면 로그인 한 것 => model에 속성 추가
- 존재하지 않으면 로그인 하지 않은 것 => model에 name이 null로 들어있음
- join
@GetMapping("/join")
public String joinPage(Model model) {
model.addAttribute("loginType", "cookie-login");
model.addAttribute("pageName", "쿠키 로그인");
// 회원가입을 위해서 model 통해서 joinRequest 전달
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", "쿠키 로그인");
// ID 중복 여부 확인
if(memberService.checkLoginIdDuplicate(joinRequest.getLoginId())){
bindingResult.addError(new FieldError
("joinRequest",
"loginId",
"ID가 존재합니다."));
}
// 비밀번호 = 비밀번호 체크 여부 확인
if(!joinRequest.getPassword().equals(joinRequest.getPasswordCheck())){
bindingResult.addError(new FieldError(
"joinRequest",
"passwordCheck",
"비밀번호가 일치하지 않습니다"));
}
// 에러가 존재할 시 다시 join.html로 전송
if (bindingResult.hasErrors()) {
return "join";
}
// 에러가 존재하지 않을 시 joinRequest 통해서 회원가입 완료
memberService.join(joinRequest);
// 회원가입 시 홈화면으로 이동
return "redirect:/cookie-login";
}
- ID 중복, 비밀번호 체크 확인
- bindingResult.addError 통해서 field error 추가 => 화면에서 field error 출력 위해서
- 에러가 존재하면 다시 입력할 수 있게 join.html로 이동
- 에러가 존재하지 않으면 회원가입 진행 후 홈화면으로 이동
- 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", "쿠키 로그인");
Member member = memberService.login(loginRequest);
// ID나 비밀번호가 틀린 경우 global error return
if(member == null) {
bindingResult.reject("loginFail", "로그인 아이디 또는 비밀번호가 틀렸습니다.");
}
if(bindingResult.hasErrors()) {
return "login";
}
// 로그인 성공 => 쿠키 생성
Cookie cookie = new Cookie("memberId", String.valueOf(member.getId()));
cookie.setMaxAge(60 * 60); // 쿠키 유효 시간 : 1시간
response.addCookie(cookie);
return "redirect:/cookie-login";
}
- @ModelAttribute로 받아온 loginRequest 정보를 통해서 로그인 진행
- ID나 비밀번호가 틀리면 로그인 되지 않음
- bindingResult.reject 통해서 global error 추가
- 화면에서 global error 출력
- 로그인 성공 시 쿠키 생성, 저장
- 쿠키에 ID값 저장, 유효기간 1시간으로 설정
- response.addCookie(cookie) 통해서 쿠키 저장
- ID나 비밀번호가 틀리면 로그인 되지 않음
- 로그인 후 홈 화면으로 리턴
- logout
@GetMapping("/logout")
public String logout(HttpServletResponse response, Model model) {
model.addAttribute("loginType", "cookie-login");
model.addAttribute("pageName", "쿠키 로그인");
// 동일한 이름의 새 쿠키 생성 => 로그아웃
Cookie cookie = new Cookie("memberId", null);
cookie.setMaxAge(0);
response.addCookie(cookie);
return "redirect:/cookie-login";
}
- "memberId"의 이름을 가진 새 쿠키 생성하고 유효기간 0으로 설정
- response.addCookie(cookie)를 통해서 유효하지 않은 쿠키 전달 => 로그아웃
- 로그아웃 시 홈 화면으로 리턴
- info
@GetMapping("/info")
public String info(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
model.addAttribute("loginType", "cookie-login");
model.addAttribute("pageName", "쿠키 로그인");
Member loginMember = memberService.getLoginMemberById(memberId);
if(loginMember == null) {
return "redirect:/cookie-login/login";
}
model.addAttribute("member", loginMember);
return "info";
}
- 로그인 된 멤버 찾음
- 로그인 되어 있으면 model에 loginMember 넣어서 전달
- admin
@GetMapping("/admin")
public String adminPage(@CookieValue(name = "memberId", required = false) Long memberId, Model model) {
model.addAttribute("loginType", "cookie-login");
model.addAttribute("pageName", "쿠키 로그인");
Member loginMember = memberService.getLoginMemberById(memberId);
if(loginMember == null) {
return "redirect:/cookie-login/login";
}
if(!loginMember.getRole().equals(MemberRole.ADMIN)) {
return "redirect:/cookie-login";
}
return "admin";
}
- 로그인 된 멤버 찾음
- 멤버의 role이 ADMIN이 아니라면 홈 화면으로 이동
- 멤버의 role이 ADMIN이 맞다면 admin.html로 이동
4. 실행결과
- 홈 화면
- 회원가입 화면
- 회원가입 화면 (오류)
- 로그인 화면
- 로그인 화면 (오류)
- 로그인 후 홈 화면
- 마이페이지
- 관리자 계정으로 들어간 관리자 페이지
5. 보안 고려사항
- 개발자 도구로 들어가면 생성된 쿠키의 정보를 볼 수 있음
- 쿠키의 정보를 암호화해서 보안 강화 가능
- 유효 기간을 적절히 설정해서 보안 강화 가능
- CSRF(Cross-Site Request Forgery) 공격에 대비해서 적절한 대책 마련 필요
'Spring Boot' 카테고리의 다른 글
[Spring Boot] 로그인 기능 구현 (3) - 스프링 시큐리티 로그인 (4) | 2024.01.07 |
---|---|
[Spring Boot] 로그인 기능 구현 (2) - 세션 로그인 (0) | 2024.01.07 |
[Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현 (2) | 2024.01.07 |
[Spring Boot] 페이징 기능 구현 ( + 페이징, 정렬, 검색, 에러 메시지 포함 예제) (1) | 2024.01.03 |
[SpringBoot] MySQL 연동 (maven, gradle) (0) | 2023.12.12 |