backend/springboot

Session을 사용한 로그인 구현

ssoheeh 2024. 4. 16. 22:42

세션(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>> 형식으로 저장

 

 

참고 - https://chb2005.tistory.com/