반응형
세션(Session) 이란?
- 일정 시간동안 같은 사용자로부터 들어오는 일련의 요구를 하나의 상태로 보고 그 상태를 일정하게 유지시키는 기술
- 쿠키 로그인 예제에서 확인했듯이 쿠키는 사용자의 정보를 사용자 컴퓨터 메모리에 저장하지만 세션은 서버측에 저장
세션을 사용한 로그인 구현
개념
- 로그인 성공 시 세션을 하나 생성
- 이 세션의 Key 값은 UUID(중복되지 않는 랜덤값, 예측 불가)으로 설정
- Value 값에 사용자 정보를 넣음
- 생성한 세션을 서버측 세션 저장소에 보관
- 세션의 Key값(UUID)을 쿠키를 통해 사용자에게 전달
- 사용자는 로그인 성공 이후 다른 요청을 할 때마다 이 쿠키를 서버에 같이 보내줌
- 서버 측에서 사용자에게 쿠키를 통해 UUID값을 받는다면, 전달받은 UUID를 Key 값으로 갖는 세션을 서버측 세션 저장소에서 검색
- 세션 저장소에 Key값이 일치하는 세션이 있다면, 그 세션의 Value값에는 로그인 한 사용자의 정보가 들어있고, 이 정보를 통해 인증, 인가 진행
- 쿠키 로그인 방식과는 달리 세션의 Key값이 예측 불가능하기 때문에 다른 사용자인 것 처럼 요청을 보내는 것이 불가능 하다는 장점이 있지만 동시 사용자 수가 많아진다면 서버에 저장해야 할 세션의 수가 많아진다는 단점도 존재
- Session을 직접 만들고 저장하는 방법도 있지만 Spring에서 제공하는 HttpSession을 사용하여 로그인 기능 구현
세션 생성 방법
- HttpServletRequest 객체에서 getSession()을 하면 Session을 가져옴
- 이 때 파라미터로 true를 넣어 준다면(default = true, 파라미터를 안 넣어주면 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)
- Entity, DTO, Repository, Service, 화면은 미리 만들어 놓음
- [Spring Boot] 로그인 구현 방법 정리 참고
- 공통 화면 사용을 위해 모든 요청에 아래 코드 추가
- model.addAttribute("loginType", "session-login");
- model.addAttribute("pageName", "세션 로그인");
@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", "바밀번호가 일치하지 않습니다."));
}
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.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";
}
}
결과
- 메인 페이지 (로그인 하지 않은 상태)
- 로그인 페이지 (로그인 하지 않은 상태)
- 메인 페이지 (로그인 성공 => 쿠키가 변경됨)
- 유저 정보 페이지, 관리자 페이지 (로그인 한 상태 => 변경된 쿠키로 Request)
)
- 로그아웃 후 메인 페이지 (쿠키는 그대로이지만 서버의 Session이 삭제되어 로그인 하지 않은 것으로 처리)
URL에 sessionId가 남을때 해결 방법
- cookie가 없는 상황을 대비하기 위해 URL에 써주는 경우가 존재하는데, 대부분의 상황에서는 필요 없으니 해주는게 좋음
- application.properties(application.yml)에 아래 코드 추가
server.servlet.session.tracking-modes = cookie
# 쿠키 파기 시간 설정을 여기서 해줄수도 있음
server.servlet.session.timeout = 1800
세션 리스트 확인 방법
- static 변수 생성
public static Hashtable sessionList = new Hashtable();
- 로그인에 성공하면 세션 생성 및 Key, Value값 삽입 => 이 때 sessionList에도 같이 저장
sessionList.put(session.getId(), session);
- 로그아웃하면 세션 파기 => 이 때 sessionList에서도 삭제
sessionList.remove(session.getId());
- 세션 리스트 확인용 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>> 형식으로 저장됨
반응형
'Spring Boot > 문법 정리' 카테고리의 다른 글
[Spring Boot] Spring Security 인증, 인가 실패 처리 - authenticationEntryPoint, accessDeniedHandler (0) | 2023.01.07 |
---|---|
[Spring Boot] Spring Security를 사용한 로그인 구현 (Form Login) (1) | 2023.01.06 |
[Spring Boot] Cookie를 사용한 로그인 구현 (1) | 2023.01.02 |
[Spring Boot] 로그인 구현 방법 정리 (6) | 2023.01.01 |
[Spring Boot] 연관관계 매핑 - N:M 관계 예제 (0) | 2023.01.01 |