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());
}
}
'BackEnd : Spring > SpringBoot' 카테고리의 다른 글
[Spring Boot] 영속성 컨텍스트 / 엔티티 값 update 하기 (1) | 2024.11.28 |
---|---|
[SpringBoot] JpaRepository @Modifying, @Transactional (0) | 2024.07.02 |
[Springboot] 연관관계 매핑 : 일대일 (0) | 2024.05.27 |
[Springboot] SpringBoot 3.x maven+ QueryDSL 설정하기 (0) | 2024.05.27 |
[Springboot] MockBean 테스트 코드 작성하기 (0) | 2024.05.27 |