💡 학습 목표
- signin.jsp 파일 생성 및 로그인 화면 요청 기능 구현
- 로그인 처리 기능 만들기
- 로그아웃 기능 만들기
1. signin.jsp 파일 생성 및 로그인 페이지 요청 기능 구현
결과 화면
user/signIn.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- header.jsp -->
<%@ include file="/WEB-INF/view/layout/header.jsp"%>
<!-- start of content.jsp(xxx.jsp) -->
<div class="col-sm-8">
<h2>로그인</h2>
<h5>Bank App에 오신걸 환영합니다</h5>
<!-- 예외적으로 로그인은 보안 때문에 post로 던지자 -->
<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="asd1234">
</div>
<button type="submit" class="btn btn-primary">로그인</button>
</form>
</div>
<!-- end of col-sm-8 -->
</div>
</div>
<!-- end of content.jsp(xxx.jsp) -->
<!-- footer.jsp -->
<%@ include file="/WEB-INF/view/layout/footer.jsp"%>
UserController - 로그인 화면 요청 기능 추가 및 코드 수정
// 회원 가입 요청 처리
// 주소 설계 <http://localhost:8800/user/sign-up>
// Get, Post -> sign-up 같은 도메인이라도 구분이 가능하다.
// REST API 를 사용하는 이유에 대해한번 더 살펴 보세요
@PostMapping("/sign-up")
public String signProc(SignUpDTO dto) {
// .. 생략
// 코드 수정
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";
}
아래 그림은 스프링 컨테이너 안에서의 간략한 동작 흐름 입니다. viewResolver를 확인 하고 SSR 에 대한 개념을 다시 떠올려 보세요
즉, 뷰 리졸버는 ModelAndView 객체, Model 를 View 영역으로 전달하기 위해 알맞은 View 정보를 설정하는 역할을 한다.
2. 로그인 처리 기능 만들기
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;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class SignInDTO {
private String username;
private String password;
public User toUser() {
return User.builder()
.username(this.username)
.password(this.password)
.build();
}
}
UserController - 로그인 기능 처리 요청
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.SignInDTO;
import com.tenco.bank.dto.SignUpDTO;
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 // IoC에 대상(싱글톤 패턴으로 관리됨)
@RequestMapping("/user") // 대문 처리
public class UserController {
private UserService userService;
private final HttpSession session;
// DI 처리
@Autowired // 노란색 경고는 사용할 필요 없음 - 가독성 위해서 선언해도 됨
public UserController(UserService service, HttpSession session) {
this.userService = service;
this.session = session;
}
/**
* 회원 가입 페이지 요청
* 주소 설계 : <http://localhost:8080/user/sign-up>
* @return signUp.jsp
*/
@GetMapping("/sign-up")
public String signUpPage() {
// prefix: /WEB-INF/view/
// return: user/signUp
// suffix: .jsp
return "user/signUp";
}
/**
* 회원 가입 로직 처리 요청
* 주소 설계 : <http://localhost:8080/user/sign-up>
* @param dto
* @return
*/
@PostMapping("/sign-up")
public String signUpProc(SignUpDTO dto) {
System.out.println("test : " + dto.toString());
// controller 에서 일반적이 코드 작업
// 1. 인증검사 (여기서는 인증검사 불 필요)
// 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-in";
}
/*
* 로그인 화면 요청
* 주소설계 : <http://localhost:8080/user/sign-in>
*/
@GetMapping("/sign-in")
public String singInPage() {
// 인증검사 x
// 유효성검사 x
return "user/signIn";
}
/**
* 회원가입 요청 처리
* 주소설계 : <http://localhost:8080/user/sign-in>
* @return
*/
@PostMapping("/sign-in")
public String signProc(SignInDTO 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);
}
// 서비스 호출
User principal = userService.readUser(dto);
// 세션 메모리에 등록 처리
session.setAttribute("principal", principal);
// 새로운 페이지로 이동 처리
// TODO - 계좌 목록 페이지 이동처리 예정
return "redirect:/index";
}
}
UserRepository 로그인 처리 기능 추가
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;
// UserRepository 인터페이스와 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();
// 코드 추가
// 로그인 기능 x (username, password) --> return User ..
// 주의 ! - 매개변수 2개 이상 시 반드시 @Param 어노테이션을 사용하자!
public User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
}
user.xml 쿼리 추가
<select id="findByUsernameAndPassword" resultType="com.tenco.bank.repository.model.User" >
select * from user_tb where username = #{username} and password = #{password}
</select>
UserService 기능 추가
public User readUser(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);
}
if(userEntity == null) {
throw new DataDeliveryException("아이디 혹은 비밀번호가 틀렸습니다.", HttpStatus.BAD_REQUEST);
}
return userEntity;
}
3. 로그아웃 기능 만들기
<aside> 💡 세션 메모리지는 누가 관리하고 있을까?
Spring Boot 애플리케이션에서 세션 정보는 웹 애플리케이션 서버(Web Application Server, WAS)에 의해 관리됩니다. 스프링 컨테이너는 애플리케이션 내의 빈(Bean) 생명주기와 비즈니스 로직을 처리하는 반면, HTTP 세션 상태는 WAS가 처리합니다.
세션 정보는 WAS의 메모리 내에서 관리되는데, 이는 클라이언트마다 서버에 할당된 고유한 세션 ID를 통해 사용자의 상태를 추적합니다. 예를 들어, Apache Tomcat, Jetty, Undertow 등의 서버는 각각 세션을 자체적으로 관리하는 메커니즘을 가지고 있습니다.
스프링 컨테이너는 HttpSession 객체를 통해 세션에 접근할 수 있는 방법을 제공합니다. 개발자는 스프링의 컨트롤러 내에서 HttpSession 객체를 주입받아 세션 데이터에 접근하거나 수정할 수 있습니다. 그러나 실제 세션 데이터의 저장과 관리는 WAS가 담당합니다.
</aside>
JSESSIONID 란 뭘까? : 스프링 서버에 클라이언트가 최초 요청시 응답으로 JSESSIONID 를 넘겨 받습니다.
+------------------+
| Client |
+------------------+
|
v
+------------------+
| Apache Web |
| Server |
+------------------+
|
v (동적 컨텐츠 생성시 동작)
+------------------+
| Tomcat | WAS
| (Servlet |
| Container) |
| |
| *세션 스토리지* |
| +------------+ | - 스프링 컨테이너 기반 애플리케이션 -
| | Filter | | HttpServletRequest, HttpServletResponse 생성하고
| +------------+ | DispatcherServlet의 service 메서드가 호출됩니다
| |
| | * 동시에 여러 요청이 오면 서블릿 컨테이너의 멀티스레딩 능력 *
| | HTTP 요청을 처리하기 위해 스레드를 생성하거나 스레드 풀에서
| | 스레드를 할당하는 역할
| |
+-----------|------+
|
v
+----------------------------+ - 스프링 프레임워크의 핵심 부분 - IoC, DI, AOP ...
| Spring | Spring Container: 스프링 프레임워크의 핵심 부분으로 빈(Bean)의 생명
| Container | 주기를 관리하며 스프링 기반 애플리케이션의 구동 환경입니다.
| |
| +---------------------+ |
| | DispatcherServlet | |
| +---------------------+ |
| | |
| v |
| +------------------+ |
| | Interceptor | | *추상화된 API 와 인터페이스들을 통해 세션 데이터에 접근*
| +------------------+ |
| PreHandle |
| | |
| v |
| +------------------+ |
| | Controller | |
| +------------------+ |
| AOP | |
| Advice | |
| | |
| v |
| Interceptor |
| PostHandle |
| | |
| v |
| Interceptor |
| AfterCompletion |
| |
| |
+-----------|----------------+
|
v
+------------------+
| Tomcat |
| (Servlet |
| Container) |
| |
| |
| +------------+ |
| | Filter | |
| +------------+ |
| | |
| v |
+------------------+
|
v
+------------------+
| Response |
+------------------+
|
v
+------------------+
| Apache Web |
| Server |
+------------------+
|
v
+------------------+
| Client |
+------------------+
DispatcherServlet은 스프링 MVC 프레임워크의 핵심 구성 요소
스프링 부트 웹 애플리케이션은 내장형 서블릿 컨테이너를 사용합니다.
스프링 부트 애플리케이션 시작 시, 내장형 서블릿 컨테이너는 DispatcherServlet의 단일
인스턴스를 생성합니다. 이 DispatcherServlet 인스턴스는 웹 애플리케이션의
모든 HTTP 요청을 처리하게 됩니다.
DispatcherServlet은 웹 애플리케이션 컨텍스트(WebApplicationContext)와 연결되어
있습니다. 웹 애플리케이션 컨텍스트는 웹 관련 설정과 빈들을 포함합니다.
예를 들어, 웹 요청을 처리하는 컨트롤러(Controller), 서비스(Service), 데이터베이스
상호작용 등과 관련된 빈들이 여기에 포함될 수 있습니다.
따라서, HTTP 요청이 들어오면, DispatcherServlet은 웹 애플리케이션 컨텍스트에서 적절한
빈을 찾아 해당 요청을 처리합니다. 따라서 DispatcherServlet은 웹 요청의 시작점과 끝점,
즉 요청의 수신부터 응답의 전송까지 전체 웹 요청/응답 생명주기를 관리하는 중심적인 역할
을 합니다.
UserController - 로그아웃 코드 추가
// 코드 추가
@GetMapping("/logout")
public String logout() {
session.invalidate(); // 로그아웃 됨
return "redirect:/user/sign-in";
}
'Spring boot > Bank App 만들기' 카테고리의 다른 글
16. 계좌 생성(유효성, 인증검사 중 누가 먼저 일까?) (0) | 2024.08.08 |
---|---|
15. 헤더 링크 설정 및 JSTL 태그 활용 (0) | 2024.08.08 |
13. 회원 가입(트랜잭션, 예외 처리, H2 테이블 생성) (0) | 2024.08.08 |
12. MyBatis 설정 (DB 접근 기술이란?) (0) | 2024.08.08 |
11. 어노테이션 정리 (0) | 2024.08.08 |