Session:session은 비밀번호와 같은 인증 정보를 쿠키에 저장하지 않고 대신에 사용자의 식별자인 session id 를 저장한다. 서버에는 인증 정보와 더불어 이 ID에 해당하는 사용자의 정보를 저장한다.
세션은 서버에 저장하기 때문에 사용자가 수백, 수천, 수만으로 늘어난다면 해당 유저의 정보를 찾고 데이터 매칭에 오랜 시간이 걸리면서 서버에 부하가 생긴다.
사용자의 로그인 상태를 유지하는 기능을 위해 쿠키에 아이디와 비밀번호를 담아두고 있습니다. 이렇게 된다면 개인 소유가 아닌 컴퓨터에서 사용할 경우 사용자의 개인정보가 털릴것이고, http로 개인정보를 주고 받다보면 쿠키가 유출, 조작이 될 수 있는 보안상 큰 문제를 야기한다.
Session 을 사용하기 위한 기본 준비물:
Session id
생성시 유니크한 id로 지정 해야 한다. : 다른 이용자랑 겹치면 같은 유저라고 판단하게되는 문제가 생긴다.
만료 시간 및 파기 기능이 있어야 한다.
클라이언트는 쿠키에 SessionId를 저장한 후 서버도 이걸 가지고 있어야 한다.
3.2 세션을 사용한 로그인 서비스 로직
로그인 성공 시 세션을 하나 생성
이 세션의 Key 값은 UUID(중복되지 않는 랜덤값, 예측 불가)으로 설정 // 이미지 이름 지을때도 사용!
Value 값에 사용자 정보를 넣음
생성한 세션을 서버측 세션 저장소에 보관
세션의 Key값(UUID)을 쿠키를 통해 사용자에게 전달
사용자는 로그인 성공 이후 다른 요청을 할 때마다 이 쿠키를 서버에 같이 보내줌
서버 측에서 사용자에게 쿠키를 통해 UUID값을 받는다면, 전달받은 UUID를 Key 값으로 갖는 세션을 서버측 세션 저장소에서 검색
세션 저장소에 Key값이 일치하는 세션이 있다면, 그 세션의 Value값에는 로그인 한 사용자의 정보가 들어있고, 이 정보를 통해 인증, 인가 진행
쿠키 로그인 방식과는 달리 세션의 Key값이 예측 불가능하기 때문에 다른 사용자인 것 처럼 요청을 보내는 것이 불가능 하다는 장점이 있지만 동시 사용자 수가 많아진다면 서버에 저장해야 할 세션의 수가 많아진다는 단점도 존재
Session을 직접 만들고 저장하는 방법도 있지만 Spring에서 제공하는 HttpSession을 사용하여 로그인 기능 구현
HTTPSession 활용법
//CHECK SESSION
// Session이 있으면 가져오고 없으면 null return
HttpSession session = httpServletRequest.getSession(false);
// Session이 있으면 가져오고 없으면 Session을 생성해서 return (default = true)
HttpSession session = httpServeltRequest.getSession(true);
//SETTING SESSION
session.setAttribute("userId", user.getId());
// Session의 유효 시간 설정 (단위 = 초 => 600 = 10분)
session.setMaxInactiveInterval(600);
// GET SESSION
Long loginUserId = (Long) session.getAttribute("userId");
//@SessionAttribute 을 활용한 세션 활용법
@GetMapping("/")
public String home(@SessionAttribute(name = "userId", required = false) Long userId) {
if(userId == null) {
System.out.println("로그인 하지 않음");
}
else {
System.out.println("로그인 유저의 Id : " + userId);
}
return "home";
}
//DELETE SESSION
HttpSession session = request.getSession(false);
session.invalidate();
그림으로 나타낸 세션을 활용한 로그인 구조.
3.4 구현
@Controller
@RequiredArgsConstructor
@RequestMapping("/session-login")
public class SessionLoginController {
private final UserService userService;
@GetMapping(value = {"", "/"})
public String home(Model model, @SessionAttribute(name = "userId", required = false) Long userId) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션 로그인");
User loginUser = userService.getLoginUser(userId);
if(loginUser != null) {
model.addAttribute("nickname", loginUser.getNickname());
}
return "home";
}
@GetMapping("/join")
public String joinPage(Model model) {
model.addAttribute("loginType", "session-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", "session-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", "비밀번호일치 X."));
}
if(bindingResult.hasErrors()) {
return "join";
}
userService.join(joinRequest);
return "redirect:/session-login";
}
@GetMapping("/login")
public String loginPage(Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션 로그인");
model.addAttribute("loginRequest", new LoginRequest());
return "login";
}
@PostMapping("/login")
public String login(@ModelAttribute LoginRequest loginRequest, BindingResult bindingResult,
HttpServletRequest httpServletRequest, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션 로그인");
User user = userService.login(loginRequest);
// 로그인 아이디나 비밀번호가 틀린 경우 global error return
if(user == null) {
bindingResult.reject("loginFail", "로그인 아이디 또는 비밀번호가 틀렸습니다.");
}
if(bindingResult.hasErrors()) {
return "login";
}
// 로그인 성공 => 세션 생성
// 세션을 생성하기 전에 기존의 세션 파기
httpServletRequest.getSession().invalidate();
HttpSession session = httpServletRequest.getSession(true); // Session이 없으면 생성
// 세션에 userId를 넣어줌
session.setAttribute("userId", user.getId());
session.setMaxInactiveInterval(600); // Session이 10분동안 유지
return "redirect:/session-login";
}
@GetMapping("/logout")
public String logout(HttpServletRequest request, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션 로그인");
HttpSession session = request.getSession(false); // Session이 없으면 null return
if(session != null) {
session.invalidate();
}
return "redirect:/session-login";
}
@GetMapping("/info")
public String userInfo(@SessionAttribute(name = "userId", required = false) Long userId, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션 로그인");
User loginUser = userService.getLoginUser(userId);
if(loginUser == null) {
return "redirect:/session-login/login";
}
model.addAttribute("user", loginUser);
return "info";
}
@GetMapping("/admin")
public String adminPage(@SessionAttribute(name = "userId", required = false) Long userId, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션 로그인");
User loginUser = userService.getLoginUser(userId);
if(loginUser == null) {
return "redirect:/session-login/login";
}
if(!loginUser.getRole().equals(UserRole.ADMIN)) {
return "redirect:/session-login";
}
return "admin";
}
}
3.5 쿠키 vs 세션
- 쿠키 VS 세션이 정확이 어떤 차이인지 모르렜어요...
- 사실 이 둘의 기술적 목적이 다른거 때문에 HTTP 의 상태성부터 시작해서 나오는... 조금 비교하는게 맞는건가 싶긴하지만... 나중에 따로 포스트를 해보도록..
- 중요한 정보를 직접 주는 것과, 서버에서 User에게 할당한 Key(목욕탕 번호..?)를 주는 것의 차이를 들 수 있다. -> 요청시마다 자기소개하기 vs 번호를 받은 번호로 말하고 들어가기.
- ex. 쿠키에 저장된 아이디와 비밀번호를 Text 형태로 요청 할 때마다 전송. vs 일정 시간(로그아웃, 브라우저나감, 일저 시간 지남..) 동안 User 정보를 갖고 올 수있는 Key를 전송
- 그래서 중요하지 않은 것들을 주로 쿠키를 통해 저장 시키는 방식을 사용한다.