SpringBootProject

Project Part1 - 로그인 구현하기. (4. 세션을 활용한 로그인 기법)

dding-shark 2024. 11. 26. 15:22
728x90

3.2. 기법2. 세션 활용하기.

  • 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를 전송 
- 그래서 중요하지 않은 것들을 주로 쿠키를 통해 저장 시키는 방식을 사용한다. 


728x90