-
개요 및 흐름도
Spring Security 라이브러리를 일시적으로 제외하고, Spring Boot의 핵심 기능(MVC, JPA, Thymeleaf, HttpSession)만을 활용하여 웹 애플리케이션의 필수 기능인 회원가입, 로그인, 세션 유지, 로그아웃 흐름을 완전히 밑바닥부터 구현함. -
핵심 소스코드 정리
① 도메인 및 DB 레이어 (Entity & Repository)
User.java (Entity): 사용자 정보를 관리하는 테이블 매핑 객체 (Lombok Builder 패턴 적용)
UserRepository.java: Spring Data JPA를 상속받아 아이디 중복 체크(existsByUsername) 및 로그인 조회(findByUsername) 기능 구현
② 비즈니스 로직 레이어 (Service)
UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
// [회원가입] 아이디 중복 검증 후 평문 패스워드로 DB 저장
@Transactional
public Long registerUser(String username, String password, String nickname, String email) {
if (userRepository.existsByUsername(username)) {
throw new IllegalArgumentException("이미 사용 중인 아이디입니다: " + username);
}
User user = User.builder().username(username).password(password).nickname(nickname).email(email).role("USER").build();
return userRepository.save(user).getId();
}
// [로그인 예외 검증] 아이디 존재 여부 및 비밀번호 일치 여부 판별
@Transactional(readOnly = true)
public User login(String username, String password) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 아이디입니다: " + username));
if (!user.getPassword().equals(password)) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
return user;
}
}
③ 웹 요청 제어 레이어 (Controller)
UserController.java: 타임리프
@Controller
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/join")
public String join(@ModelAttribute UserJoinRequest request) {
userService.registerUser(request.getUsername(), request.getPassword(), request.getNickname(), request.getEmail());
return "redirect:/login"; // 가입 성공 시 로그인 페이지로 이동
}
@PostMapping("/login")
public String login(@ModelAttribute UserLoginRequest request, HttpServletRequest servletRequest) {
User loginUser = userService.login(request.getUsername(), request.getPassword());
// 세션 창고에 로그인 유저 객체 저장 (로그인 유지 핵심)
HttpSession session = servletRequest.getSession();
session.setAttribute("loginUser", loginUser);
return "redirect:/"; // 로그인 성공 시 메인 페이지로 이동
}
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // 세션 무효화 (로그아웃)
return "redirect:/";
}
}
ViewController.java: 주소창 입력을 받아 Thymeleaf HTML 템플릿을 열어주는 화면 전용 라우팅 컨트롤러
@Controller
public class ViewController {
@GetMapping("/register") public String registerPage() { return "register"; }
@GetMapping("/login") public String loginPage() { return "login"; }
@GetMapping("/")
public String mainPage(HttpSession session, Model model) {
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser != null) {
model.addAttribute("nickname", loginUser.getNickname()); // 세션 존재 시 닉네임 전달
}
return "index";
}
}
- 핵심 트러블슈팅 (Troubleshooting)
🚨 1. Circular view path [register] 에러 (500)
원인: 새 프로젝트 빌드 시 pom.xml에 타임리프 템플릿 엔진 의존성(spring-boot-starter-thymeleaf)을 누락하여 스프링 부트가 HTML 파일 위치를 찾지 못하고 내부 리다이렉션 루프에 빠짐.
해결: pom.xml에 타임리프 의존성을 명시적으로 추가하고 mvn clean package를 통해 빌드 캐시를 초기화하여 해결함.
🚨 2. Error resolving template [register] 에러
원인: 스프링 부트와 타임리프의 정적 파일 탐색 컨벤션 규칙을 위반함. HTML 파일이 templates 폴더가 아닌 곳에 위치했거나 폴더명 오타(template 등 s 누락) 발생 시 템플릿 리졸버가 인식하지 못함.
해결: 반드시 src/main/resources/templates/ 경로 규칙을 준수하고 파일명 및 확장자를 전면 소문자로 교정하여 해결함.
🚨 3. HTML Form 데이터 수신 시 500 에러
원인: Postman과 달리 브라우저의
해결: DTO 클래스에 Lombok의 @Setter 및 @NoArgsConstructor 어노테이션을 부착하여 해결함.
- 최종 결론 및 회고
정해진 절차와 아키텍처 규칙 준수: 스프링 부트는 파일의 위치, 폴더 이름 규칙(Convention over Configuration)이 조금만 어긋나도 예외를 던짐. 매뉴얼을 준수하는 규칙적인 설계가 디버깅 시간을 가장 획기적으로 줄여준다는 것을 체득함.
세션 기반 로그인 구조 확립: HTTP의 Stateless 특성을 극복하기 위해 HttpSession 메모리 영역을 활용하는 메커니즘을 명확하게 이해함. 추후 Spring Security 프레임워크를 조립할 때도 이 내부 동작 원리가 밑바탕이 될 것임.