728x90
- 쿠키 :
- 사용자가 웹사이트 접속시 사용자의 개인 장치에 다운로드되고 브라우저에 저장되는 작은 텍스트 파일을 사용해 통신한다.
- 쿠키 기반 로그인의 장점 :
- 클라이언트에서 자동으로 인증 정보 관리
- 쿠키는 클라이언트에 저장되어, HTTP 요청마다 자동으로 서버로 전송된다. 이를 통해 추가적인 인증 토큰을 매 요청마다 헤더에 추가할 필요가 없다.
- 브라우저 자동 관리로 인해 개발자는 별도의 인증 상태 관리 코드를 최소화할 수 있다.
- 세션 만료 및 자동 로그아웃 설정 용이
- 쿠키의 만료 시간을 설정하면 사용자가 세션을 지속하거나 자동으로 로그아웃할 수 있게 되어 보안이 용이하다.
- 만료 시간이 지나면 쿠키가 자동 삭제되므로, 장시간 접속하지 않으면 보안 리스크가 감소문제가 있긴하다.
- 다양한 보안 설정 가능
- HttpOnly 설정을 통해 JavaScript로 쿠키에 접근하지 못하도록 하여, XSS(교차 사이트 스크립팅) 공격에 대한 방어를 강화할 수 있다.
- Secure 옵션을 통해 HTTPS 연결에서만 쿠키가 전송되도록 설정 가능해, 전송 시 데이터 암호화를 보장할 수 있다.
- SameSite 속성을 설정하여, CSRF(사이트 간 요청 위조) 공격 방지에 도움을 줄 수 있다.
- 브라우저 호환성 및 표준 지원
- 모든 주요 브라우저는 쿠키를 지원하며, 클라이언트와 서버 간 상호운용성이 높아 별도의 설정 없이 쿠키를 활용할 수 있다.
- 쿠키는 HTTP 표준에 포함되어 있어, 서버-클라이언트 간 일관된 인증 방식을 사용할 수 있다.
- 간편한 인증 상태 유지
- 사용자가 새로고침을 하거나 페이지를 이동해도 로그인 상태가 유지된다.
- 로컬 스토리지나 세션 스토리지에 비해 자동화된 상태 관리가 용이하다.
- 서버에서 쉽게 관리 가능
- 서버는 클라이언트가 보낸 쿠키 값을 통해 사용자의 로그인 상태를 확인하고, 세션 관리 로직을 일관되게 유지할 수 있다.
- 특히 세션 ID를 쿠키에 저장해 서버에서 세션 데이터로 직접 관리할 때 유용하다.
- 클라이언트에서 자동으로 인증 정보 관리
- 쿠키 기반 로그인 방식의 단점
- 보안 취약점 노출 가능성
- XSS(교차 사이트 스크립팅) 공격 위험: 쿠키가 클라이언트에 저장되므로 악성 스크립트로 인해 쿠키가 유출될 가능성이 있습니다. 이를 통해 공격자는 사용자의 세션을 탈취할 수 있다.
- 이를 완화하기 위해
HttpOnly속성 설정이 필요하지만, 완벽한 방어는 못한다.
- 이를 완화하기 위해
- CSRF(사이트 간 요청 위조) 공격 위험: 사용자가 인증된 상태에서 악성 사이트의 요청을 통해 자동으로 서버에 쿠키가 전송될 수 있어, 서버에서 예기치 않은 동작이 발생할 위험이 있다.
- 이를 방지하려면 쿠키에
SameSite속성을 설정해야 하지만, 완전히 차단하기는 어렵다.
- 이를 방지하려면 쿠키에
- XSS(교차 사이트 스크립팅) 공격 위험: 쿠키가 클라이언트에 저장되므로 악성 스크립트로 인해 쿠키가 유출될 가능성이 있습니다. 이를 통해 공격자는 사용자의 세션을 탈취할 수 있다.
- 클라이언트 저장 공간 제한
- 쿠키 용량 제한: 각 쿠키는 보통 4KB 정도로 제한되며, 브라우저별로 저장할 수 있는 쿠키의 개수에도 제한이 있다.
- 용량 제한으로 인해 복잡한 데이터나 많은 정보를 쿠키에 저장할 수 없고, 큰 데이터는 세션이나 로컬 스토리지에 의존해야 한다.
- 쿠키 용량 제한: 각 쿠키는 보통 4KB 정도로 제한되며, 브라우저별로 저장할 수 있는 쿠키의 개수에도 제한이 있다.
- 네트워크 트래픽 증가
- 매 요청 시 쿠키 전송: 쿠키는 클라이언트와 서버 간의 모든 HTTP 요청에 포함되어 전송되므로, 쿠키 크기가 클수록 네트워크 트래픽이 증가하고, 성능 저하를 유발할 수 있다.
- 특히 대역폭이 제한된 네트워크 환경에서는 성능에 큰 영향을 미칠 수 있다.
- 매 요청 시 쿠키 전송: 쿠키는 클라이언트와 서버 간의 모든 HTTP 요청에 포함되어 전송되므로, 쿠키 크기가 클수록 네트워크 트래픽이 증가하고, 성능 저하를 유발할 수 있다.
- 사용자 설정에 의한 제약
- 쿠키 비활성화 가능성: 일부 사용자는 개인정보 보호를 위해 브라우저에서 쿠키를 비활성화할 수 있습니다. 이 경우 쿠키를 기반으로 한 로그인 기능이 정상 작동하지 핞을 수 있다.
- 쿠키가 비활성화된 경우를 대비한 별도의 예외 처리가 필요할 수 있다.
- 쿠키 비활성화 가능성: 일부 사용자는 개인정보 보호를 위해 브라우저에서 쿠키를 비활성화할 수 있습니다. 이 경우 쿠키를 기반으로 한 로그인 기능이 정상 작동하지 핞을 수 있다.
- 자동 로그아웃 및 세션 만료 문제
- 쿠키 만료 관리의 복잡성: 쿠키 기반 세션 관리에서는 서버와 클라이언트 간 쿠키 만료 시간 동기화가 필요합니다. 만료 시간을 잘못 설정하면 예상치 못한 로그아웃 또는 세션 유지 문제가 발생할 수 있다.
- 민감한 정보 저장에 부적합
- 데이터 보안 문제: 쿠키는 클라이언트 측에 저장되므로, 민감한 정보를 포함하지 않는 것이 권장된다.
- 중요한 정보는 세션 ID 등으로 대체하고, 서버에서 데이터를 조회하는 방식으로 처리해야 한다..
- 데이터 보안 문제: 쿠키는 클라이언트 측에 저장되므로, 민감한 정보를 포함하지 않는 것이 권장된다.
- 보안 취약점 노출 가능성
쿠키를 활용한 컨트롤러 구현
3.1. 기법1. 쿠키 활용하기.
- 장점 : 편하다 쉽다! 간편하다!
- 단점 : 보안상의 이슈가 발생하기 쉽다.
- 클라이언트 컴퓨터에 정보를 저장하고 꺼내 쓴다는 아이디어에서 나온 것 같은데, PASSWORD 처럼 민감한 정도를 저장하기엔 도전해야하는 문제들이 많이 생기는 것도 사실이다.
하지만 기능에 따라서는 충분히 고려해볼만한 데이터 통신 방식임으로 나중에 쿠키를 갖고 뭔가를 하는 서비스를 개발하면(음.. 아마 장바구니 같은거에 활용 할 수 있을거 같다.) 그때 조금 고려해보자. - 하지만, 실습의 목적정도로 쿠키를 이용한 로그인(가장 쉽고 가장 직관적이다 라는 장점때문에) 먼저 구현을 해보도록 하겠다.
3.2 쿠키를 사용한 로그인 서비스 로직 (로그인 후에 서비스 사용에 관한것 까지)
- 로그인 성공 시 서버가 쿠키에 사용자 정보를 넣어줌
- 클라이언트 측에서는 다음 요청을 할 때마다 이 쿠키를 서버에 같이 보낸다. (로그인 한 User가 누구인지 알아야한다.)
- 서버에서는 이 쿠키를 확인해 로그인 했는지와 유저 정보, 권한 등을 확인할 수 있음. (Service 제공 목록이 달라진다. ex.내가 쓴 글 삭제에서 '내가'는 어떻게 알아낼 것인가? 의 문제)

기본적인 쿠키를 활용한 로그인 흐름도.
3.3 쿠키를 활용한 구현할 메소드 목록
- 쿠키 생성 작업.(로그인)
- 쿠키 생명시간 설정 및 파기 작업.(로그아웃)
3.4 구현
- 구현을 원래 Api만 개발하려 했지만, 기본적인 웹뷰 복습도 할겸 풀스택으로 개발하게 되었다.
@PostMapping("/login")
public String login(@ModelAttribute LoginRequest loginRequest, BindingResult bindingResult,
HttpServletResponse response, Model model) {
... 대충 로그인 구현 코드
// 로그인 성공 => 쿠키 생성
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("loginType", "cookie-login");
model.addAttribute("pageName", "쿠키 로그인");
Cookie cookie = new Cookie("userId", null); // userId = null 로 유저 로그인 X 표현
cookie.setMaxAge(0); // 쿠키 유효 시간 : 만료(0시간)
response.addCookie(cookie); //쿠키 생성
return "redirect:/cookie-login"; // 로그아웃 후 페이지 이동
}
// 코드 구현에서 보이는것 처럼 User의 정보가 직접 저장되기때문에 보안상의 이슈가 발생하기 쉽다.
나머지 코드 구현및 테스트 사진
@Controller
@RequiredArgsConstructor
@RequestMapping("/cookie-login")
public class CookieLoginController {
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.getLoginUser(userId);
if(loginUser != null) {
model.addAttribute("nickname", loginUser.getNickname());
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.checkLoginIdDuplicate(joinRequest.getLoginId())) {
bindingResult.addError(new FieldError("joinRequest", "loginId", "중복된 로그인ID."));
}
// 닉네임 중복 체크
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("/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);
// 로그인 아이디나 비밀번호가 틀린 경우 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("loginType", "cookie-login");
model.addAttribute("pageName", "쿠키 로그인");
Cookie cookie = new Cookie("userId", null);
cookie.setMaxAge(0);
response.addCookie(cookie);
return "redirect:/cookie-login";
}
@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.getLoginUser(userId);
if(loginUser == null) {
return "redirect:/cookie-login/login";
}
model.addAttribute("user", loginUser);
return "info";
}
//권한 인가에 관한 Test시 필요한 Admin 페이지 접근.
@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.getLoginUser(userId);
if(loginUser == null) {
return "redirect:/cookie-login/login";
}
if(!loginUser.getRole().equals(UserRole.ADMIN)) {
return "redirect:/cookie-login";
}
return "admin";
}
}
728x90
'SpringBootProject' 카테고리의 다른 글
| Project Part1 - 로그인 구현하기. (5. Spring Security) (0) | 2024.11.28 |
|---|---|
| Project Part1 - 로그인 구현하기. (4. 세션을 활용한 로그인 기법) (0) | 2024.11.26 |
| Project Part1 - 로그인 구현하기. (2. 로그인 컨트롤러, 로그인 기법) (0) | 2024.11.25 |
| Project Part1 - 로그인 구현하기. (1. 기본적인 로직) (0) | 2024.11.24 |
| SpringBoot Project 시작 (0) | 2024.11.24 |