👨💻 지난 포스팅에서 회원 가입 화면을 구현하고, H2 데이터베이스를 설정하여 초기 데이터를 셋팅하는 과정을 다뤘습니다. 이번 포스팅에서는 로그인, 로그아웃 과정을 다뤄보겠습니다. 🚀
Spring Boot와 JSP를 활용한 로그인 기능 구현하기
웹 애플리케이션에서 로그인 기능은 필수적인 요소입니다.
이번 글에서는 Spring Boot와 JSP를 활용하여 로그인 기능을 구현하는 방법을 단계별로 정리하겠습니다.
🚀 학습 목표
1. signin.jsp 파일 생성 및 로그인 화면 요청 기능 구현
2. 로그인 처리 기능 만들기
3. 로그아웃 기능 만들기
1. 로그인 페이지(signin.jsp) 생성 및 요청 처리
📌 로그인 화면 JSP 생성
로그인 페이지를 구현하기 위해 signin.jsp 파일을 생성합니다.
이 파일은 기존 signUp.jsp 파일을 복사하여 수정하면 쉽게 만들 수 있습니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- header.jsp -->
<%@ include file="/WEB-INF/view/layout/header.jsp"%>
<!-- 로그인 화면 -->
<div class="col-sm-8">
<h2>로그인</h2>
<h5>Bank App에 오신 것을 환영합니다.</h5>
<form action="/user/sign-in" method="post">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" class="form-control" placeholder="Enter username" id="username" name="username" value="길동">
</div>
<div class="form-group">
<label for="pwd">Password:</label>
<input type="password" class="form-control" placeholder="Enter password" id="pwd" name="password" value="1234">
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">로그인</button>
</div>
</form>
</div>
<!-- footer.jsp -->
<%@ include file="/WEB-INF/view/layout/footer.jsp"%>
📌 로그인 페이지 요청을 처리하는 컨트롤러
클라이언트가 로그인 페이지에 접근할 수 있도록 UserController에 signInPage() 메서드를 추가합니다.
/**
* 로그인 화면 요청
* 주소 설계 http://localhost:8080/user/sign-in
*/
@GetMapping("/sign-in")
public String signInPage() {
// 인증 검사 불필요
// prefix: /WEB-INF/view/
// suffix: .jsp
return "user/signin";
}
이제 브라우저에서 http://localhost:8080/user/sign-in으로 이동하면 로그인 화면을 확인할 수 있습니다.
결과 화면
즉, 뷰 리졸버는 ModelAndView 객체를 View 영역으로 전달하기 위해 알맞은 View 정보를 설정하는 역할을 한다.
2. 로그인 처리 기능 구현
📌 UserRepository 인터페이스에 로그인 관련 메서드 추가
로그인 처리를 위해 UserRepository에 findByUsernameAndPassword() 메서드를 추가합니다.
package com.tenco.bank.repository.interfaces;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.tenco.bank.repository.model.User;
//interface 와 user.xml 매칭 - (메서드 명 기준)
@Mapper // 반드시 선언해주어 한다.
public interface UserRepository {
public int insert(User user);
public int updateById(User user);
public int deleteById(Integer id);
public User findById(Integer id);
public List<User> findAll();
// 코드 추가 1단계 - 조회 : username, password
// 주의!! - 파미리터가 2개 이상일 경우 @Param 어노테니션을 반드시 선언
public User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
}
MyBatis에서 사용할 SQL 쿼리는 user.xml 파일에 추가합니다.
<select id="findByUsernameAndPassword" resultType="com.tenco.bank.repository.model.User">
select * from user_tb where username = #{username} and password = #{password}
</select>
✅ SigninDTO 생성
SigninDTO는 로그인 폼 데이터를 담는 객체로 사용합니다.
package com.tenco.bank.dto;
import com.tenco.bank.repository.model.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SigninDTO {
private String username;
private String password;
public User toUser() {
return User.builder()
.username(this.username)
.password(this.password)
.build();
}
}
📌 로그인 서비스(UserService) 구현
UserService에서 로그인 처리를 담당할 signIn() 메서드를 작성합니다.
/**
* 로그인 서비스 처리
* @param SigninDTO
* @return userEntity or null
*/
public User signIn(SigninDTO dto) {
// 유효성 검사는 Controller 에서 먼저 하자.
User userEntity = null;
try {
userEntity = userRepository.findByUsernameAndPassword(dto.getUsername(), dto.getPassword());
} catch (DataAccessException e) {
throw new DataDeliveryException("잘못된 처리 입니다",HttpStatus.INTERNAL_SERVER_ERROR);
} catch (Exception e) {
// 예외 처리 - 에러 페이지 호출
throw new RedirectException("알 수 없는 오류" , HttpStatus.SERVICE_UNAVAILABLE);
}
// 예외 클래스 발생 안됨. 프로세스 입장에서 예외로 처리 throw 처리 함
if(userEntity == null) {
throw new DataDeliveryException("아이디 혹은 비번이 틀렸습니다",
HttpStatus.BAD_REQUEST);
}
return userEntity;
}
📌 로그인 요청 처리 (UserController 추가)
로그인 요청을 POST 방식으로 처리하고, 인증이 성공하면 세션(HttpSession)에 사용자 정보를 저장합니다.
package com.tenco.bank.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.tenco.bank.dto.SignUpDTO;
import com.tenco.bank.dto.SigninDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.repository.model.User;
import com.tenco.bank.service.UserService;
import jakarta.servlet.http.HttpSession;
@Controller
@RequestMapping("/user") // 대문 처리
public class UserController {
@Autowired
private final UserService userService;
// 코드 추가
// 세션 메모리지에 접근하기 위한 HttpSession 을 선언 해주세요
@Autowired
private final HttpSession session;
// 코드 수정 - DI 처리
public UserController(UserService userService, HttpSession session) {
this.userService = userService;
this.session = session;
}
/**
* 회원 가입 페이지 요청
* 주소 설계 http://localhost:8080/user/sign-up
* @return signUp.jsp 파일 리턴
*/
@GetMapping("/sign-up")
public String signUpPage() {
// prefix: /WEB-INF/view/
// suffix: .jsp
return "user/signUp";
}
// 회원 가입 요청 처리
// 주소 설계 http://localhost:8800/user/sign-up
// Get, Post -> sign-up 같은 도메인이라도 구분이 가능하다.
// REST API 를 사용하는 이유에 대해한번 더 살펴 보세요
@PostMapping("/sign-up")
public String signProc(SignUpDTO dto) {
// 1. 인증검사 x
// 2. 유효성 검사
if(dto.getUsername() == null || dto.getUsername().isEmpty()) {
throw new DataDeliveryException("username을 입력 하세요",
HttpStatus.BAD_REQUEST);
}
if(dto.getPassword() == null || dto.getPassword().isEmpty()) {
throw new DataDeliveryException("password을 입력 하세요",
HttpStatus.BAD_REQUEST);
}
if(dto.getFullname() == null || dto.getFullname().isEmpty()) {
throw new DataDeliveryException("fullname을 입력 하세요",
HttpStatus.BAD_REQUEST);
}
userService.createUser(dto);
// todo 로그인 페이지로 변경 예정
return "redirect:/user/sign-up";
}
/**
* 로그인 화면 요청
* 주소 설계 http://localhost:8080/user/sign-in
*/
@GetMapping("/sign-in")
public String signInPage() {
// 인증 검사가 불필요 하다.
// prefix: /WEB-INF/view/
// suffix: .jsp
return "user/signin";
}
// 코드 추가
/**
* 로그인 처리
* @param signInFormDto
* @return 메인 페이지 이동 (수정 예정)
* 생각해보기
* GET 방식 처리는 브라우저 히스토리에 남겨지기 때문에
* 예외적으로 로그인 POST 방식으로 처리 한다. (보안)
*/
@PostMapping("/sign-in")
public String signInProc(SigninDTO dto) {
// 1. 유효성 검사
if(dto.getUsername() == null ||
dto.getUsername().isEmpty()) {
throw new DataDeliveryException("username을 입력하시오", HttpStatus.BAD_REQUEST);
}
if(dto.getPassword() == null ||
dto.getPassword().isEmpty()) {
throw new DataDeliveryException("password을 입력하시오", HttpStatus.BAD_REQUEST);
}
User principal = userService.signIn(dto);
// 세션메모리에 등록 처리
session.setAttribute("principal", principal);
// 처리 후에 새로운 request 를 생성할 수 있도록 리다이렉트 처리 합니다.
// TODO - 추후 계좌 목록 페이로 변경해 주세요
// return "redirect:/account/list";
System.out.println("111111111");
return "redirect:/main-page";
}
}
3. 로그아웃 기능 구현
세션을 초기화하여 로그아웃을 처리하고, 로그인 페이지로 리다이렉트합니다.
@GetMapping("/logout")
public String logout() {
session.invalidate(); // 세션 삭제
return "redirect:/user/sign-in"; // 로그인 페이지로 이동
}
4. 세션(HttpSession)과 DispatcherServlet의 역할
📌 세션은 어떻게 관리될까?
- Spring Boot 애플리케이션에서 세션 정보는 웹 애플리케이션 서버(WAS, 예: Tomcat) 가 관리합니다.
- 서버는 각 클라이언트마다 JSESSIONID 라는 고유한 세션 ID를 발급하여 사용자의 로그인 상태를 유지합니다.
- HttpSession 객체를 통해 개발자는 세션 데이터를 저장하고 가져올 수 있습니다.
session.setAttribute("principal", user); // 세션 저장
User user = (User) session.getAttribute("principal"); // 세션에서 데이터 가져오기
session.invalidate(); // 세션 삭제 (로그아웃)
📌 DispatcherServlet의 역할
- 클라이언트가 HTTP 요청을 보내면 DispatcherServlet이 요청을 수락합니다.
- 요청을 분석하여 적절한 컨트롤러(Controller)로 전달합니다.
- 컨트롤러는 서비스(Service) 계층을 호출하여 비즈니스 로직을 수행합니다.
- 최종적으로 응답(View 또는 JSON)을 생성하여 클라이언트에게 반환합니다.
5. Spring Boot 로그인 처리 흐름 정리
📌 로그인 요청이 처리되는 과정
- 사용자가 signin.jsp 에서 username, password를 입력하여 로그인 요청을 보냄
- DispatcherServlet이 요청을 수락하고, UserController 의 signInProc() 호출
- UserService 에서 로그인 검증 후, DB에서 사용자 정보 조회
- 로그인 성공 시, HttpSession에 사용자 정보 저장
- redirect:/main-page 를 통해 로그인 후 페이지로 이동
- 이후 요청마다 세션을 사용하여 로그인 상태 유지
6. 결론
Spring Boot 기반의 로그인 기능을 구현하면서 DispatcherServlet과 세션(HttpSession)의 동작 원리를 함께 이해할 수 있었습니다.
- DispatcherServlet 은 요청을 분석하여 컨트롤러에 전달하고, 최종 응답을 반환하는 역할을 합니다.
- 세션(HttpSession) 은 사용자의 로그인 정보를 유지하고, WAS가 이를 관리합니다.
- 로그인 후 JSESSIONID를 사용하여 클라이언트의 로그인 상태를 유지합니다.
- 로그아웃 시 session.invalidate() 를 호출하여 세션을 삭제합니다.
이러한 요청 처리 흐름을 이해하면, Spring Boot에서 인증 및 인가 시스템을 더욱 효과적으로 구현할 수 있습니다. 🚀
🚀다음 포스팅에서는 계좌생성에 대해서 적어볼게요! 🚀
회원가입이 궁금하다면??
Spring[Spring boot] Bank App - 회원가입(화면구현)
👨💻 지난 포스팅에서 회원 가입 로직을 설계하고 구현한 것에 이어, 이번에는 회원 가입 화면을 구현하고 H2 데이터베이스를 설정하여 초기 데이터를 셋팅하는 과정을 다뤄보겠습니다. 🚀
seohong.tistory.com
회원가입
회원가
'Spring' 카테고리의 다른 글
[Spring boot] Bank App - 계좌 목록 조회 (0) | 2025.02.18 |
---|---|
[Spring boot] Bank App - 계좌 생성 (0) | 2025.02.17 |
Spring[Spring boot] Bank App - 회원가입(화면구현) (2) | 2025.02.15 |
[Spring boot] Bank App - 회원가입 (0) | 2025.02.15 |
[Spring boot] Bank App - MyBatis 설정 (DB 접근 기술) (0) | 2025.02.14 |