세션(Session)
- 일정 시간동안 같은 사용자로부터 들어오는 일련의 요구를 하나의 상태로 보고 그 상태를 일정하게 유지시키는 기술
- 쿠키는 사용자의 정보를 사용자 컴퓨터 메모리에 저장하지만 세션은 서버측에 저장
세션을 사용한 로그인 구현
개념
- 로그인 성공 시 세션을 하나 생성
이 세션의 Key 값은 UUID(중복되지 않는 랜덤값, 예측 불가)로 설정
Value 값에 사용자 정보 넣음 - 생성한 세션을 서버측 세션 저장소에 보관
- 세션의 Key값(UUID)을 쿠키를 통해 사용자에게 전달
- 사용자는 로그인 성공 이후 다른 요청을 할 때마다 이 쿠키를 서버에 같이 보내줌
- 서버 측에서 사용자에게 쿠키를 통해 UUID 값을 받는다면 전달받은 UUID를 Key 값으로 갖는 세션을 서버측 세션 저장소에서 검색
- 세션 저장소에 Key 값이 일치하는 세션이 있다면 그 세션의 Value 값에는 로그인한 사용자의 정보가 들어있고 이 정보를 통해 인증, 인가 진행
- 쿠키 로그인 방식과는 달리 세션의 Key 값이 예측 불가능하기 때문에 다른 사용자인것처럼 요청을 보내는 것이 불가능하다는 장점
- 동시 사용자 수가 많아진다면 서버에 저장해야할 세션의 수가 많아진다는 단점
- Session을 직접 만들고 저장하는 방법도 있지만 Spring에서 제공하는 HttpSession을 사용하여 로그인 기능 구현
세션 생성 방법
- HttpServletRequest 객체에서 getSession()을 하면 Session 가져옴
- 이 때 파라미터로 true를 넣어준다면(defualt = true) Session이 있으면 가져오고 없으면 Session을 생성해서 return -> 이를 사용하여 세션 생성
- 만약 파라미터로 false 넣어준다면 Session 있으면 가져오고 없으면 null return
// Session이 있으면 가져오고 없으면 null return
HttpSession session = httpServletRequest.getSession(false);
// Session이 있으면 가져오고 없으면 Session을 생성해서 return (default = true)
HttpSession session = httpServeltRequest.getSession(true);
- Session 생성 후 setAttribute()를 통해 이 세션의 Key, Value 값 넣어줄 수 있음
- Key 값으로 "userId", Value 값으로 user의 Id 넣어줌
session.setAttribute("userId", user.getId());
// Session의 유효 시간 설정 (1800초 = 30분)
session.setMaxInactiveInterval(1800);
세션 확인 방법
- HttpServletRequest 객체에서 getSession() 을 해서 Session 가져옴
- session.getAttribute("userId")를 통해 Session에서 "userId"를 Key로 가진 Value 가져올 수 있음
Long loginUserId = (Long) session.getAttribute("userId");
- @SessionAttribute 를 사용하여 더 쉽게 Session에서 값 꺼내올 수 있음
@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";
}
세션 파기 방법
HttpSession session = request.getSession(false);
session.invalidate();
세션을 사용한 로그인 구현 (Controller)
@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.getLoginUserById(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", "바밀번호가 일치하지 않습니다."));
}
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(1800); // Session이 30분동안 유지
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.getLoginUserById(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.getLoginUserById(userId);
if(loginUser == null) {
return "redirect:/session-login/login";
}
if(!loginUser.getRole().equals(UserRole.ADMIN)) {
return "redirect:/session-login";
}
return "admin";
}
}
로그인 성공 -> 쿠키가 변경됨 JSESSIONID
유저 정보 페이지, 관리자 페이지 (로그인한 상태 -> 변경된 쿠키로 Request)
로그아웃 후 메인페이지 (쿠키는 그대로지만 서버의 세션이 삭제되어 로그인하지 않은 것으로 처리)
URL에 sessionId가 남을 때 해결 방법
- cookie가 없는 상황을 대비하기 위해 URL에 써주는 경우가 존재하는데, 대부분의 상황에서는 필요없으니 해주는게 좋음
- application.properties(application.yml)에 아래 코드 추가
server.servlet.session.tracking-modes = cookie
# 쿠키 파기 시간 설정을 여기서 해줄수도 있음
server.servlet.session.timeout = 1800
server:
servlet:
session:
#timeout: 30m
tracking-modes: cookie
세션 리스트 확인 방법
1. static 변수 생성
public static Hashtable sessionList = new Hashtable();
2. 로그인에 성공하면 세션 생성 및 Key, Value 값 삽입 -> 이 때 sessionList에도 같이 저장
sessionList.put(session.getId(), session);
3. 로그아웃하면 세션 파기 -> 이 때 sessionList에서도 삭제
sessionList.remove(session.getId());
4. 세션 리스트 확인용 URL 매핑 및 sessionList 내용 출력 구현
@GetMapping("/session-list")
@ResponseBody
public Map<String, String> sessionList() {
Enumeration elements = sessionList.elements();
Map<String, String> lists = new HashMap<>();
while(elements.hasMoreElements()) {
HttpSession session = (HttpSession)elements.nextElement();
lists.put(session.getId(), String.valueOf(session.getAttribute("userId")));
}
return lists;
}
결과
- db가 아닌 로컬 변수에 저장하기 때문에 서버 재실행 시 로그인되어 있어도 sessionList는 비어있음
- 아무도 로그인하지 않은 상황
한 명 로그인
로그아웃
- SessionId는 쿠키 값
- Session이 저장될 때는 <SessionId, <Key, Value>> 형식으로 저장
'backend > springboot' 카테고리의 다른 글
지연 로딩과 조회 성능 최적화 (1) | 2024.04.19 |
---|---|
API 개발 기본 - Entity와 DTO (0) | 2024.04.18 |
Cookie를 사용한 로그인 구현 (0) | 2024.04.16 |
로그인 구현 방법 세팅 (0) | 2024.04.16 |
99클럽 코테 스터디 7일차 TIL - springboot 변경 감지와 병합 (0) | 2024.04.07 |