수업내용/Spring

[2023.1.6.금] 스프링 MVC 웹 애플리케이션 3 (로그인, 내정보 조회)

주니어주니 2023. 1. 6. 19:16

 

 

 

* 의존성 관계 

 

 

- 스프링 컨테이너가 객체 생성 + 의존성 관계 분석해서 조립까지 해줌

- ContextLoaderListener 가 만드는 스프링 컨테이너, DispatcherServlet 이 만드는 스프링 컨테이너

 

 

 

 

 

* 개발자가 해야할 일

1) 매핑정보 (어떤 요청 URI로 받을지 결정) ( @PostMapping("/register") ) 
2) 메소드 생성
3) 매개변수를 뭐로 받을지 결정
4) 반환타입 결정

 

 

 


 

 

Controller의 요청핸들러 메소드 

 

@PostMapping("/login")
public String login(String id, String password) 

 

  • 요청핸들러 메소드의 매개변수가 기본자료형 혹은 String 인 경우
    • 매개변수의 이름과 동일한 이름으로 요청파라미터 값을 조회해서 매개변수로 전달한다.
      (로그인 폼의 name 변수를 직접 매개변수로 넣음)
    • 매개변수의 타입이 기본자료형 타입인 경우 해당 타입으로 형변환해서 전달한다.
      (String을 int로) 
    • 매개변수의 타입이 기본자료형(정수, 실수, 문자, 불린)일 때, 요청파라미터값이 존재하지 않으면 오류가 발생한다. (String 문자열일 때는 값을 넣지않고 넘겨도 오류 X)
      요청파라미터 값을 해당 타입으로 변환할 수 없을 때 오류가 발생한다. (입력폼에 입력한 값이 매개변수의 타입과 다를 때)

  • @RequestParam
    • 요청파라미터 값을 요청핸들러의 매개변수와 매핑시키는 어노테이션이다.
    • 주요 속성
      • name : 요청파라미터의 이름 (입력폼의 name)
      • required : 기본값은 true, false로 지정하면 name에 지정한 요청파라미터값이 없어도 오류가 발생하지 않는다.
      • defaultValue : name에 지정한 요청파라미터값이 존재하지 않을 때의 기본값을 설정한다.
        -  defaultValue의 값은 문자열로 설정되지만 매개변수에 대입될 때는 해당 타입으로 형변환된다.
    • public String list(@RequestParam(name = "page", required = false, defaultValue = "1") int page,
                                @RequestParam(name = "rows", required = false, defaultValue = "10") int rows,
                                @RequestParam(name = "sort", required = false, defaultValue = "date") String sort)


  • 요청핸들러 메소드의 매개변수

    HttpServletRequest      : 요청객체
    HttpServletResponse    : 응답객체
    HttpSession                   : 세션객체
    WebRequest                 : Spring이 제공하는 객체. 요청객체가 가지고 있는 정보 대부분을 제공하는 객체
    TimeZone                      : 시간정보
    Locale                            : 지역정보 (국가, 언어)
    InputStream                   : 클라이언트와 연결된 읽기 전용 스트림
    OutputStream                : 클라이언트와 연결된 쓰기 전용 스트림
    Reader                           : 클라이언트와 연결된 텍스트 읽기 전용 스트림
    Writer                              : 클라이언트와 연결된 텍스트 읽기 전용 스트림
    @RequestParam            : 요청파라미터와 매개변수를 매핑시키는 어노테이션
    @PathVariable                : 요청 URL 경로에 포함된 파라미터값과 매개변수를 매핑시키는 어노테이션
    @ModelAttribute             : 요청파라미터의 해당값을 저장하는 객체를 매핑시키는 어노테이션
    @RequestBody               : 요청메시지의 바디부 정보와 매개변수를 매핑시키는 어노테이션
    @Valid                             : 요청파라미터값의 유효성 여부를 검증시키는 어노테이션
    Model                               : 뷰에 전달할 정보를 저장하는 객체
    Errors                               : 요청파라미터값의 유효성 검증 결과를 저장하는 객체
    BindingResult                   : 요청파라미터값의 유효성 검증 결과를 저장하는 객체
    SessionStatus                  : 세션에 저장된 정보를 삭제하는 객체
    기본자료형                       : 요청파라미터값을 전달받는다.
    String                                : 요청파라미터값을 전달받는다.
    사용자정의 객체                : 요청파라미터값을 전달받는다.

 

  • 요청 URL 매핑하기
    • 요청 URL과 요청핸들러 메소드를 매핑시키는 어노테이션
      • @RequestMapping : 요청방식에 상관없이(GET, POST) 요청URL을 기준으로 매핑
      • @GetMapping         : 요청방식이 GET 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑
      • @PostMapping        : 요청방식이 POST 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑 
      • @PutMapping          : 요청방식이 PUT 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑 
      • @DeleteMapping     : 요청방식이 DELETE 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑 
  • 요청방식
    • GET         : 서버에서 정보 조회하는 요청 (링크 클릭, 주소창에 입력)
    • POST       : 서버에서 정보 추가하는 요청 (입력폼에 입력)
    • PUT          : 서버에서 정보 변경하는 요청
    • DELETE   : 서버에서 정보 삭제하는 요청

      * 일반적인 웹 애플리케이션에서는 GET, POST 두가지 방식을 사용한다.
      * REST 방식의 웹 애플리케이션에서는 GET, POST, PUT, DELETE 방식을 전부 사용한다.

 

 

 


 

 

 

* SessionUtils 객체에 사용자 정보 담기 

 

 

 

내 정보 조회, 회원 탈퇴, 게시글 작성/수정/삭제, 게시글 추천하기 등등

-> 세션객체의 로그인된 사용자 정보가 필요

-> 요청핸들러 메소드의 매개변수로 세션객체 (HttpSession session) 가 항상 필요 

 

-> 근데 너무 번거로워! 

-> 가급적이면 Controller에서는 톰캣이 제공하는 객체 (HttpRequest, HttpResponse, HttpSession)를 안적고싶어

-> com.sample.utils -> SessionUtils 객체 생성

 

 

 

* 스프링이 제공하는 RequestContextHolder의 RequestAttributes 메소드

: 지금 현재 요청 정보(요청객체의 속성, 세션객체의 속성)가 들어있는 RequestAttributes 객체 반환

   -> 이 객체는 setAttribute, removeAttribute, getAttribute 메소드를 가지고 있음 

 

 

 

- HomeController

 

HttpSession session 매개변수의 session.setAttribute -> SessionUtils. setAttribute("loginUser", user)

 

 

 

 

 


 

 

 

1. 로그인 폼 요청

 

 

* HomeController (내부이동 -> 로그인폼 jsp를 바로 반환) 

 

 

 

* login-form.jsp (+자바스크립트로 유효성체크)

 

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<title>애플리케이션</title>
</head>
<body>
<c:set var="menu" value="login" />
<%@ include file="common/navbar.jsp" %>
<div class="container">
	<div class="row mb-3">
		<div class="col-12">
			<h1 class="border bg-light p-2 fs-4">로그인</h1>
		</div>
	</div>
	<div class="row mb-3">
		<div class="col-12">
			<p>아이디와 비밀번호를 입력하고 로그인 버튼을 클릭하세요</p>
			<form id="form-register" class="border bg-light p-3" method="post" action="login">
				<div class="mb-3">
					<label class="form-label">아이디</label>
					<input type="text" class="form-control form-control-sm" name="id" />
				</div>
				<div class="mb-3">
					<label class="form-label">비밀번호</label>
					<input type="password" class="form-control form-control-sm" name="password" />
				</div>
				<div class="text-end">
					<a href="/home" class="btn btn-secondary btn-sm">취소</a>
					<button type="submit" class="btn btn-primary btn-sm">로그인</button>
				</div>
			</form>
		</div>
	</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
	$("#form-register").submit(function(){
		let id = $("#form-register :input[name=id]").val();
		let password = $("#form-register :input[name=password]").val();
		
		if(id == ""){
			alert("아이디는 필수입력값입니다.");
			return false;
		}
		if(password == ""){
			alert("비밀번호는 필수입력값입니다.");
			return false;
		}
		return true;
	})
})
</script>
</body>
</html>

 

 

 

 


 

 

2. 로그인

 

* HomeController (로그인폼, 로그인, 로그아웃 요청 -> 업무로직 호출 -> 페이지 반환) 

 

 

** SessionUtils.setAttributes("loginUser", loginUserInfo);

    : "loginUser" 라는 이름으로 loginUserInfo 객체 저장 => 세션에 저장됨 !!

 

 

 

* LoginUserInfo 객체 생성 (세션객체에 담을 아이디, 이름만 담긴 로그인 객체)

- 보통은 세션객체에 사용자의 모든 정보를 담지않고, 아이디와 이름 정도만 담음

 

 

 

 

 

* UserService (로그인 업무로직 - 유효성 체크)

 

 

 

 

* navbar.jsp (로그인한 사용자별로 다른 메뉴 출력)

 

 

 

 

 

 

 

로그인하기 정리

 

 

 

 

 


 

3. 로그아웃 

 

 

* HomeController (그냥 세션에서 삭제하기만 하면 됨) 

 

 

 

 

로그아웃하기 정리 

 

 

 


 

 

4. 내 정보 조회 

 

 

* UserDetailDto (내정보 조회의 항목들로 이루어진 객체를 따로 만들기)

 

 

 

 

* UserService (업무로직) 

 

 

 

 

 

* UserController (업무로직 호출) 

 

 

- @RequestMapping("/user") : 이 클래스 내의 메소드를 실행할때 앞에 무조건 /user가 붙음 

 

 

Q. 왜 컨트롤러에서 로그인 유효성 체크를 할까? 

 

 웹(표현계층) 친화적인 코딩 : 컨트롤러
서버 친화적인 코딩 : 서비스 

 

 

 

 

Q. Service에서 Dto는 왜 싱글턴으로 안만드는지?
A. 정보를 담는 객체는 싱글턴으로 만들지 않는다. -> 동시에 실행하면 정보가 덮어써질 수 있기 때문에 

 

 

 

* detail.jsp 

 

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<title>애플리케이션</title>
</head>
<body>
<c:set var="menu" value="user" />
<%@ include file="../common/navbar.jsp" %>
<div class="container">
	<div class="row mb-3">
		<div class="col-12">
			<h1 class="border bg-light p-2 fs-4">내 정보</h1>
		</div>
	</div>
	<div class="row mb-3">
		<div class="col-3">
			<div class="card">
				<div class="card-header">
					메뉴
				</div>
				<div class="card-body">
					<div class="list-group">
						<a href="/user/info" class="list-group-item list-group-item-action active">내 정보 보기</a>
						<a href="/user/password" class="list-group-item list-group-item-action">비밀번호 변경</a>
						<a href="/user/delete" class="list-group-item list-group-item-action">탈퇴하기</a>
					</div>
				</div>
			</div>
		</div>
		<div class="col-9">
			<p>내 정보를 확인하세요.</p>
			<table class="table table-bordered">
				<colgroup>
					<col width="15%">
					<col width="35%">
					<col width="15%">
					<col width="35%">
				</colgroup>
				<tbody>
					<tr>
						<th>아이디</th>
						<td>${user.id }</td>
						<th>가입일</th>
						<td><fmt:formatDate value="${user.createdDate }" pattern="yyyy년 M월 d일" /></td>
					</tr>
					<tr>
						<th>이름</th>
						<td>${user.name }</td>
						<th>접근권한</th>
						<td>
							<c:forEach var="userRole" items="${user.userRoles }">
								<c:choose>
									<c:when test="${userRole.roleName eq 'ROLE_GUEST' }">
										<span class="badge text-bg-primary">${userRole.roleName }</span>
									</c:when>
									<c:when test="${userRole.roleName eq 'ROLE_USER' }">
										<span class="badge text-bg-warning">${userRole.roleName }</span>
									</c:when>
									<c:when test="${userRole.roleName eq 'ROLE_ADMIN' }">
										<span class="badge text-bg-success">${userRole.roleName }</span>
									</c:when>
								</c:choose>
							</c:forEach>
						</td>
					</tr>
					<tr>
						<th>이메일</th>
						<td>${user.email }</td>
						<th>전화번호</th>
						<td>${user.tel }</td>
					</tr>
				</tbody>
			</table>
		</div>
	</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</body>
</html>