0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Spring Boot] 시큐리티 없이 구현하는 순수 회원 관리 시스템 (Join / Login / Session)

0
Posted at
  1. 개요 및 흐름도
    Spring Security 라이브러리를 일시적으로 제외하고, Spring Boot의 핵심 기능(MVC, JPA, Thymeleaf, HttpSession)만을 활용하여 웹 애플리케이션의 필수 기능인 회원가입, 로그인, 세션 유지, 로그아웃 흐름을 완전히 밑바닥부터 구현함.

  2. 핵심 소스코드 정리
    ① 도메인 및 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: 타임리프

태그의 데이터를 @ModelAttribute로 바인딩하고, HttpServletRequest를 통해 세션을 제어하는 컨트롤러
@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";
    }
}
  1. 핵심 트러블슈팅 (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과 달리 브라우저의

전송은 Jackson 라이브러리가 아닌 내부 프로퍼티 에디터를 거치므로, DTO 객체에 빈 생성자(@NoArgsConstructor)와 Setter가 구성되어 있지 않으면 데이터를 바인딩하지 못함.

해결: DTO 클래스에 Lombok의 @Setter 및 @NoArgsConstructor 어노테이션을 부착하여 해결함.

  1. 최종 결론 및 회고
    정해진 절차와 아키텍처 규칙 준수: 스프링 부트는 파일의 위치, 폴더 이름 규칙(Convention over Configuration)이 조금만 어긋나도 예외를 던짐. 매뉴얼을 준수하는 규칙적인 설계가 디버깅 시간을 가장 획기적으로 줄여준다는 것을 체득함.

세션 기반 로그인 구조 확립: HTTP의 Stateless 특성을 극복하기 위해 HttpSession 메모리 영역을 활용하는 메커니즘을 명확하게 이해함. 추후 Spring Security 프레임워크를 조립할 때도 이 내부 동작 원리가 밑바탕이 될 것임.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?