본문 바로가기

BackEnd : Spring/SpringBoot

[TalentTree 프로젝트] HttpSession : 로그인된 사용자 정보 가져오기

HttpSession을 사용하게 된 배경

원래 스프링 프로젝트에서 로그인, 로그아웃에서 사용자 정보를 관리하는 것을 Spring Security를 쓰곤 한다.

이 프로젝트 개발 기간이 짧기도 했고, 최초 로그인 시 회원가입으로 구현하기로 하여 세션에 로그인된 사용자 정보를 담기로 했다.

LoginController

로그인과 관련된 로직을 처리하는 Controller이다.

@Controller
@RequiredArgsConstructor
public class LoginController {
    private static String UPLOAD_DIR = System.getProperty("user.dir") + "/src/main/resources/static/bootstrap/assets/uploads";

    private final UserService userService;
    private final Map<String, Integer> loginAttempts = new ConcurrentHashMap<>();
    private final Map<String, Long> lockedAccounts = new ConcurrentHashMap<>();

    private static final int MAX_LOGIN_ATTEMPTS = 5;
    private static final long LOCK_DURATION = 1 * 60 * 1000; // 잠금 지속 시간 (1분)

    private HttpSession regenerateSession(HttpServletRequest request) {
        HttpSession session = request.getSession();
        session.invalidate(); // 기존 세션 무효화
        return request.getSession(true); // 새로운 세션 생성하여 반환
    }

    // 로그인 페이지 열람
    @GetMapping("login")
    public String showLoginForm(Model model) {
        model.addAttribute("userLoginDto", new UserLoginDto());
        return "login";
    }

    // 로그인 처리
    @PostMapping("login")
    public String processLogin(HttpServletRequest request, @ModelAttribute("userLoginDto") UserLoginDto userLoginDto, Model model) {
        String userId = userLoginDto.getUser_id();
        HttpSession session = request.getSession();

        // 계정이 잠겼는지 확인
        if (isAccountLocked(userId)) {
            model.addAttribute("loginError", "계정이 잠겼습니다. 1분 뒤에 다시 시도해주세요.");
            return "login";
        }

        User existingUser;
        try {
            existingUser = userService.findById(userId);
        } catch (Exception e) {
            User newUser = userService.createUser(userLoginDto);
            session = regenerateSession(request); // 새로운 세션 생성
            session.setAttribute("logined", newUser);
            return "redirect:/addinfo";
        }

        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        if (bCryptPasswordEncoder.matches(userLoginDto.getPassword(), existingUser.getPassword())) {
            resetLoginAttempts(userId); // 로그인 시도 횟수 초기화
            session = regenerateSession(request); // 새로운 세션 생성
            session.setAttribute("logined", existingUser);
            User compareUser = (User) session.getAttribute("logined");
            if (compareUser.getGithub() == null && compareUser.getBlog() == null) {
                return "redirect:/addinfo";
            } else {
                return "redirect:/index";
            }
        } else {
            incrementLoginAttempts(userId); // 로그인 시도 횟수 증가
            if (isAccountLocked(userId)) {
                lockedAccounts.put(userId, System.currentTimeMillis()); // 계정 잠금 시간 기록
            }
            if (loginAttempts.getOrDefault(userId, 0) >= MAX_LOGIN_ATTEMPTS) {
                lockedAccounts.put(userId, System.currentTimeMillis()); // 계정 잠금 시간 기록
                model.addAttribute("loginError", "계정이 잠겼습니다. 잠시 후 다시 시도해주세요.");
            } else {
                model.addAttribute("loginError", "비밀번호가 틀렸습니다. 다시 시도해주세요.");
            }
            return "login";
        }
    }

    // 계정이 잠겼는지 확인
    private boolean isAccountLocked(String userId) {
        return lockedAccounts.containsKey(userId) && System.currentTimeMillis() - lockedAccounts.get(userId) < LOCK_DURATION;
    }

    // 로그인 시도 횟수 증가
    private void incrementLoginAttempts(String userId) {
        loginAttempts.put(userId, loginAttempts.getOrDefault(userId, 0) + 1);
    }

    // 로그인 시도 횟수 초기화
    private void resetLoginAttempts(String userId) {
        loginAttempts.remove(userId);
    }

    // 잠금 해제 메커니즘: 잠금 해제 시간이 지나면 잠긴 상태를 해제함
    @Scheduled(fixedRate = 60000) // 매 분마다 실행 (시간 단위로 조절 가능)
    public void unlockAccounts() {
        long now = System.currentTimeMillis();
        lockedAccounts.entrySet().removeIf(entry -> now - entry.getValue() >= LOCK_DURATION);
    }


    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.removeAttribute("logined");
        return "redirect:/login";
    }



    @GetMapping("/addinfo")
    public String calladdinfo(Model model, HttpSession session) throws Exception {
        User newUser = (User) session.getAttribute("logined");
        String userId = newUser.getUser_id();
        User userinfo = userService.findById(userId);
        model.addAttribute("userinfo",userinfo);
        return "addinfo";
    }


    @PostMapping(value="/addinfo", consumes = "multipart/form-data")
    public String processAddInfo(@ModelAttribute("userInfo") UserRequestDto userDto, @RequestParam("img") MultipartFile file, HttpSession session, RedirectAttributes attributes) throws Exception {
        User loginedUser = (User) session.getAttribute("logined");
        // 업데이트된 정보로 사용자 정보 업데이트
        loginedUser = userService.updateUserInfo(loginedUser.getUser_id(), userDto.getFull_name(), userDto.getGithub(), userDto.getBlog());

        String fileName = null;
        // Check if file is empty
        if (file.isEmpty()) {
            attributes.addFlashAttribute("message", "Please select a file to upload.");
            session.setAttribute("logined", loginedUser);
        } else {
            fileName = file.getOriginalFilename();

            // Normalize the file path
            Path path = Paths.get(UPLOAD_DIR, fileName);

            // Save the file on server
            try {
                Files.createDirectories(path.getParent());
                Files.copy(file.getInputStream(), path);
            } catch (IOException e) {
                e.printStackTrace();
            }

            loginedUser = userService.updateUserImage(loginedUser.getUser_id(), fileName);
            session.setAttribute("logined", loginedUser);
        }

        return "redirect:/index";
    }

    @GetMapping("/needlogin")
    public String needlogin() {
        return "needlogin";
    }
    @GetMapping("/unloginedindex")
    public String index() {
        return "unloginedindex";
    }

    @GetMapping("/")
    public String firstpage() {
        return "unloginedindex";
    }

    @GetMapping("/help")
    public String help() {
        return "fragments/help";
    }

}

ExperienceServiceImpl

로그인된 사용자 정보를 가져와 처리하는 Service의 예시이다.

@Service
public class ExperienceServiceImpl implements ExperienceService {

    private final ExperienceDao experienceDao;
    private final UserDao userDao;

    @Autowired
    private HttpSession httpSession;


    @Autowired
    public ExperienceServiceImpl(ExperienceDao experienceDao, UserDao userDao) {
        this.experienceDao = experienceDao;
        this.userDao = userDao;
    }

    @Override
    public ExperienceDto createExperience(ExperienceRequestDto experienceDto) throws Exception {
        // Test용 User 생성
        User newUser = (User) httpSession.getAttribute("logined");
        String userId = newUser.getUser_id();

        User user = userDao.findById(userId);

        Experience experience = new Experience();
        experience.setCompany_name(experienceDto.getCompanyName());
        experience.setStart_date(String.valueOf(experienceDto.getStartDate()));
        experience.setEnd_date(String.valueOf(experienceDto.getEndDate()));
        experience.setContent(experienceDto.getContent());
        experience.setJob(experienceDto.getJob());
        experience.setUser(user);

        Experience insertedExp = experienceDao.createExperience(experience);

        ExperienceDto experienceResponseDto
                = new ExperienceDto(insertedExp.getExp_id(),
                insertedExp.getCompany_name(), insertedExp.getStart_date(),
                insertedExp.getEnd_date(), insertedExp.getContent(),
                insertedExp.getJob());
        return experienceResponseDto;
    }

    @Override
    public List<ExperienceDto> findByUserId() {
        User newUser = (User) httpSession.getAttribute("logined");
        String userId = newUser.getUser_id();

        List<Experience> experiences = experienceDao.findByUserId(userId);
        return experiences.stream()
                .map(experience -> new ExperienceDto(
                        experience.getExp_id(),
                        experience.getCompany_name(),
                        experience.getStart_date(),
                        experience.getEnd_date(),
                        experience.getContent(),
                        experience.getJob()
                ))
                .collect(Collectors.toList());
    }

}