반응형

세션(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

세션 리스트 확인 방법

  1. static 변수 생성
public static Hashtable sessionList = new Hashtable();
  1. 로그인에 성공하면 세션 생성 및 Key, Value값 삽입 => 이 때 sessionList에도 같이 저장
sessionList.put(session.getId(), session);
  1. 로그아웃하면 세션 파기 => 이 때 sessionList에서도 삭제
sessionList.remove(session.getId());
  1. 세션 리스트 확인용 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>> 형식으로 저장됨
반응형

↓ 클릭시 이동

복사했습니다!