JSP

[JSP] Authentication - 회원가입

shb 2022. 4. 7. 19:42

* 인증 (Authentication : 자신을 증명)
내가 (스스로) 자신을 증명할 만한 자료를 제시 하는 것.

* 인가 (Authorizatoin : 권한부여)
- 남에 의해서 ‘자격’ 이 부여된것.

* Authentication (인증)   /  Authorization (인가)
인증(Authentication)
- 시스템 접근 시, 등록된 사용자인지 여부를 확인하는 것
- 로그인

인가(Authorization)
- 접근 후, 인증된 사용자에게 권한을 부여하는 것
- 권한에따라 사용 가능한 기능이 제한됨
- 사용자 등급(ex: 일반/VIP/관리자)

 

1. Session 기반의 인증, 인가
- 회원가입, 
- 로그인, 로그아웃  인증 (authentication)
- 권한, 인가 (authorization)


2. 서버측 검증 로직
- GET / POST 구분
- redirect 활용


3. Lombok 사용


서블릿 : controller.HomeController
URLMapping : "/home"
view:  /view/home.jsp

 

controller > HomeController.java 만들고 /WEB-INF/views -> home.jsp 생성

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/home")
public class HomeController extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public HomeController() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String viewPage = "/home.jsp";
		RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/views" + viewPage);
		dispatcher.forward(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

 

회원정보를 넣을 DTO 생성 > ERD 폴더 생성 -> ERMaster 파일 생성 > 내보내기 > DDL


Model 생성
DTO 생성  : 
Lombok 세팅 및 사용해보기
https://projectlombok.org/

DAO  생성
일단 생성자, close, buildlist() 준비

 

domain > UserDTO.java

package domain;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class UserDTO {
	private int uid;
	private String username;
	private String password;
	private String name;
	private String authorities;
	private LocalDateTime regdate;
	
	
	
}

* DAO

domain > userDAO.java

package domain;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import common.D;

//DAO : Data Access Object
//특정 데이터 리소스(ex: DB) 에 접속하여 트랜잭션등을 전담하는 객체
//데이터 리소스별로 작성하거나, 
//혹은 기능별로 작성가능 (ex: 게시판용 DAO, 회원관리용 DAO, ...)
public class UserDAO {
	Connection conn;
	PreparedStatement pstmt;
	Statement stmt;
	ResultSet rs;
	
	// DAO 객체가 생성될때 Connection 로 생성된다.
	public UserDAO() {
		try {
			Class.forName(D.DRIVER);
			conn = DriverManager.getConnection(D.URL, D.USERID, D.USERPW);
//			System.out.println("WriteDAO 생성, 데이터베이스 연결!");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	} // end 생성자
	
	
	// DB 자원 반납 메소드
	public void close() throws SQLException {
		// 리소스 해제
		if(rs != null) rs.close();
		if(stmt != null) stmt.close();
		if(pstmt != null) pstmt.close();
		if(conn != null) conn.close();
	} // end  close()
	
	
	// 목록을 읽어와서
	// ResultSet --> List<DTO> 로 리턴
	private List<UserDTO> buildList(ResultSet rs) throws SQLException {
		List<UserDTO> list = new ArrayList<>();
		
		while(rs.next()){ 
			int uid = rs.getInt("uid");
			String username = rs.getString("username");
			String password = rs.getString("password");
			String name = rs.getString("name");
			String authorities = rs.getString("authorities");	
			LocalDateTime regdate = rs.getObject("regdate", LocalDateTime.class);
			
//			UserDTO dto = new UserDTO();
//			dto.setUid(uid);
//			dto.setUsername(username);
//			dto.setPassword(password);
//			dto.setName(name);
//			dto.setAuthorities(authorities);
//			dto.setRegdate(regdate);
			
			// Builder 사용
			UserDTO dto = UserDTO.builder()
						.uid(uid)
						.username(username)
						.password(password)
						.name(name)
						.authorities(authorities)
						.regdate(regdate)
						.build();
			
			list.add(dto);
			
		}
		
		return list;
	}

	// 회원등록 <-- DTO
	public int register(UserDTO dto) throws SQLException {
		int cnt = 0;
		
		String username = dto.getUsername();
		String password = dto.getPassword();
		String name = dto.getName(); 
		
		int uid;  // auto-generated keys 값
		// auto-generated 컬럼
		String [] generatedCols = {"uid"};  

		try{	
			pstmt = conn.prepareStatement(D.SQL_USER_INSERT, generatedCols);
			pstmt.setString(1, username);
			pstmt.setString(2, password);
			pstmt.setString(3, name);
			cnt = pstmt.executeUpdate();
			
			if(cnt > 0){
				// auto-generated keys 값 뽑아오기
				rs = pstmt.getGeneratedKeys();
				if(rs.next()) {
					uid = rs.getInt(1);
					dto.setUid(uid);  // DTO 에 set
				}
			}
			
		}finally{
			close();
		} // end try
		
		
		return cnt;
	}


	// 특정 username SELECT <- dto
	public List<UserDTO> selectByUsername(UserDTO dto) throws SQLException{
		List<UserDTO> list = null;
		
		try {
			pstmt = conn.prepareStatement(D.SQL_USER_SELECT_BY_USERNAME);
			pstmt.setString(1, dto.getUsername());
			rs = pstmt.executeQuery();
			list = buildList(rs);
		} finally {
			close();
		}
		
		return list;
	}
	
	
	//  특정 id SELECT <-- DTO, 
	// TODO 보류
	

	
} // end DAO

* UserController 작성

controller > UserController 서블릿 생성
서블릿 : controller.UserController
URLMapping : /user/*

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import command.Command;
import command.user.LoginCommand;
import command.user.RegisterCommand;
import common.C;

@WebServlet("/user/*")
public class UserController extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public UserController() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doAction(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");  // 한글 인코딩
		doAction(request, response);
	}

	protected void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("UserController.actionDo() 호출");
		
		String method = request.getMethod();
				
		// URL로부터 URI, ContextPath, Command 분리
		String uri = request.getRequestURI();
		String conPath = request.getContextPath() + "/user";
		String com = uri.substring(conPath.length());
		
		
		// 테스트 출력
		System.out.println("uri: " + uri);  
		System.out.println("conPath: " + conPath);  
		System.out.println("com: " + com);

		Command command = null;   // 어떠한 로직을 수행할지
		String viewPage = null;   // 어떠한 페이지(뷰)를 보여줄지
	
		switch(com) {
		case "/register":
			if(method.equals("GET")) {	// GET 방식의 경우 회원가입 폼	
				C.retrieveRedirectAttribute(request);
				viewPage = "/user/register.jsp";
			} else {  // POST 의 경우
				new RegisterCommand().execute(request, response);
				// redirect 가 진행되면 이미 response 가 commit 됨. 
				// response 가 commit 되지 않은 경우만 jsp forward 진행
				if(!response.isCommitted()) viewPage = "/user/registerOk.jsp";
					
			}
			break;
		case "/login":
			if(method.equals("GET")) {  // GET 의 경우 로그인 폼
				C.retrieveRedirectAttribute(request);
				viewPage = "/user/login.jsp";
			} else {  // POST 의 경우 로그인 처리
				new LoginCommand().execute(request, response);
				
				// 로그인이 실패되면 위에서 redirect 되었을것이다
				// 로그인이 성공하면
				if(!response.isCommitted()) {
					// 기본적으로 home 으로 redirect 한다
					String redirectUrl = request.getContextPath() + "/home";
					
					// 혹시 url prior (원래 가고자 했더 url) 존재했다면 url로 redirect 한다
					String urlPrior = C.retrieveUrlPrior(request);
					if(urlPrior != null) redirectUrl = urlPrior;
					
					response.sendRedirect(redirectUrl);
				}
				
			}
			break;
		case "/logout":
			if(method.equals("POST")) {
				request.getSession().removeAttribute(C.PRINCIPAL);
				// 혹은 session.invalidate();
				response.sendRedirect(request.getContextPath() + "/home");
			}
			break;		
		case "/rejectAuth": 
			C.retrieveRedirectAttribute(request);
			viewPage = "/common/rejectAuth.jsp";
			break;
		} // end switch
		
		// 위에서 결정된 뷰 페이지(viewPage) 로 forward 해줌
		if(viewPage != null) {
			RequestDispatcher dispatcher 
				= request.getRequestDispatcher("/WEB-INF/views" + viewPage);
			// request 에 담겨있는 Command 수행결과도 viewPage 에 전달되는 것이다
			dispatcher.forward(request, response);
		}

	} // end doAction()

	
}

 

case "/register" POST 방식의 경우 
command > user > RegisterCommand.java 생성


* 회원가입 폼  작성
URL : /user/register  (GET) 방식
view: /views/user/register.jsp 

 

/WEB-INF/views > register.jsp  -> 회원가입 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!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.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <title>회원가입</title>
</head>

<body>
    <div class="container mt-3">

        <div class="row mt-5">
            <div class="col-12 text-center">
                <h1>회원가입</h1>
            </div>
        </div>
        <div class="row mt-5">
            <div class="col-12 text-danger">
                ${REDIRECT_ATTR.error }
            </div>
        </div>
        <div class="row">
            <form method="POST" action="${pageContext.request.contextPath }/user/register">
                <div class="form-group mt-3">
                    <label for="username">사용자 아이디</label>
                    <input type="text" class="form-control" id="username" name="username" placeholder="사용자아이디" value="${REDIRECT_ATTR.username }" required>
                </div>
                <div class="form-group mt-3">
                    <label for="name">사용자 이름</label>
                    <input type="text" class="form-control" id="name" name="name" placeholder="사용자 이름" value="${REDIRECT_ATTR.name }" required>
                </div>
                <div class="form-group mt-3">
                    <label for="password">비밀번호</label>
                    <input type="password" class="form-control" id="password" name="password" placeholder="비밀번호" value="" required>
                </div>
                <div class="form-group mt-3">
                    <label for="re-password">비밀번호 확인</label>
                    <input type="password" class="form-control" id="re-password" name="re-password" placeholder="비밀번호 확인" value="" required>
                </div>
                <button type="submit" class="w-100 btn btn-lg btn-primary mt-3">등록</button>
            </form>
        </div>

    </div>
</body>

</html>

/uer/register

 


* RegisterCommand

 

회원가입 프로세스
URL : /user/register  (POST) 방식
Command: command.user.RegisterCommand 작성
DAO 함수 수행
일단 회원 등록 동작 확인

유효성 검증 후 등록
password 와 re-password 다르면 다시 redirect  시키기
redirect attribute 활용하여 백엔드 검증하기
이미 존재하는 username  인 경우 에러!  redirect 시키기

 


userController에서 Command 인터페이스 생성

command > user > RegisterCommand.java

회원등록을 하는 command
throws IOException 추가

POST 방식으로 넘어옴

package command.user;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import command.Command;
import common.C;
import domain.UserDAO;
import domain.UserDTO;

public class RegisterCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// 입력한 값 받아오기
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		String re_password = request.getParameter("re-password");
		String name = request.getParameter("name");
		
		String conPath = request.getContextPath();
		
		// ※ 파라미터 유효성 검증 필요.
		username = username.trim();
		password = password.trim();
		re_password = re_password.trim();
		name = name.trim();		
		
		// '비밀번호(password)'와 '비밀번호 확인(re-password)' 이 같아야 한다
		if(!password.equals(re_password)) {
			response.sendRedirect(conPath + "/user/register");
			
			// redirect 시 전달할 값들을 어떻게 전달할까?
			// query string 형식 말구... (참고 JSP09 단원)
			// TODO
			// hint: session 을 사용!
			C.addRedirectAttribute(request, "error", "비밀번호와 비밀번호확인 입력값은 같아야 합니다");
			C.addRedirectAttribute(request, "username", username);
			C.addRedirectAttribute(request, "name", name);			
			
			return;
		}
		
		UserDTO dto = UserDTO.builder()
				.name(name)
				.username(username.toUpperCase())    // 대문자로 저장
				.password(password)
				.build();
		
		int cnt = 0;
		
		try {
			// 이미 존재하는 아이디 인 경우
			List<UserDTO> list = new UserDAO().selectByUsername(dto);
			if(list.size() > 0) {
				response.sendRedirect(conPath + "/user/register");
				C.addRedirectAttribute(request, "error", "이미 존재하는 아이디(username) 입니다");
				C.addRedirectAttribute(request, "username", username);
				C.addRedirectAttribute(request, "name", name);
				return;
			}
			
			
			// 새로운 회원 등록
			cnt = new UserDAO().register(dto);
		} catch (SQLException e){
			e.printStackTrace();
		}
		
		
		request.setAttribute("result", cnt);
		request.setAttribute("dto", dto);  // auto-generated key 포함
	}

}

username, password 등 파라미터 받아서 > dto 생성 > register 통해서 dao에서 트랜잭션 발생시킴

request에 담아서 보내주기

 

DAO에 register 함수 만들어야 함. 커서 > create method > UserDAO에 생성

-> dto를 매개변수를 받아서 생성, throw SQLException

 

D.java에 회원관련 쿼리문 작성

public static final String SQL_USER_INSERT = 
"INSERT INTO t2_user(username, password, name) VALUES(?, ?, ?)";

 

password - repassword 확인

-> C.java

 

이미 존재하는 아이디의 경우

selectByUsername -> userDAO에 생성

 

setAttribute는 메소드는 속성값을 변경시키는 메소드.
객체명.setAttribute("속성노드명",새로운속성값);

 

SQLException : 클래스 및 해당 하위 유형은 데이터 소스에 액세스하는 동안 발생하는 오류 및 경고에 대한 정보를 제공

e.printStackTrace() : 에러 메세지의 발생 근원지를 찾아 단계별로 에러 출력

 


* C.java - 주로 인증 관련 상수 및 static 메소드

package common;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import domain.UserDTO;

// 주로 인증 관련 상수및 static 메소드
public class C {
	public static final String REDIRECT_ATTR_NAME = "REDIRECT_ATTR";  // redirect 에 전달할 값들을 담을 session name
	public static final String PRINCIPAL = "PRINCIPAL";   // 로그인 하면 저장되는 session name
	private static final String URL_PRIOR = "URL_PRIOR";  // 직전에 가고자 했던 url 이 저장된 session name

	// Redirect 시 전달할 값들을 session 에 추가하기
	// Map<k,v> 형태로 session 에 저장
	public static void addRedirectAttribute(HttpServletRequest request, String name, Object value) {
		if(request == null || name == null || value == null) return;
		
		HttpSession session = request.getSession();
		Map<String, Object> redirectAttr = (HashMap<String, Object>)session.getAttribute(REDIRECT_ATTR_NAME);
		// 기존에 없었으면 session 에 새로 추가
		if(redirectAttr == null) {
			redirectAttr = new HashMap<>();
			session.setAttribute(REDIRECT_ATTR_NAME, redirectAttr);
		}
		
		// name-value 추가
		redirectAttr.put(name, value);
		
	} // end addRedirectAttribute()
	
	//  Session 의 REDIRECT_ATTR 를 Request 에 옮기기
	//  옮긴뒤 Session 에선 삭제.
	public static void retrieveRedirectAttribute(HttpServletRequest request) {
		if(request == null) return;
		
		HttpSession session = request.getSession();
		Map<String, Object> redirectAttr = (HashMap<String, Object>)session.getAttribute(REDIRECT_ATTR_NAME);
		// request 로 옮기고
		request.setAttribute(REDIRECT_ATTR_NAME, redirectAttr);
		
		// session 에선 삭제하기
		session.removeAttribute(REDIRECT_ATTR_NAME);
		
	} // end retrieveRedirectAttribute()


	// 현재 진입하려는 url 에 대한 권한 체크
	// 기본적으로 아래 메소드를 호출하는 것은 authentication(로그인) 은 되어 있어야 한다는 뜻이다
	// 1. 로그인은 되어 있는 상태인지 확인
	// 2. 로그인이 되어 있다면 필요한 권한(authority) 가 있는 상태인지 확인
	// 
	// '이전'으로 갈지 혹은 '특정페이지(ex : 로그인) ' 으로 갈지 동작시켜 주어야 한다.
	// 로그인 이후에는 '원래 가고자 했던 페이지'로 다시 request 가 될수 있도록 redirect 해주어야 한다.
	// 즉 '원래 가고자 했던 페이지' 도 기억해야 한다는 뜻이다.
	
	// secutiryCheck 를 통과하면 true 아니면 false 를 리턴한다
	public static boolean securityCheck(
			HttpServletRequest request,
			HttpServletResponse response,
			String [] authorities    // 필요한 권한(들) OR
			) throws IOException {
		
		String conPath = request.getContextPath();
		
		// 원래 가고자 했던 url 
		String qry = request.getQueryString();
		String url_prior = request.getRequestURL() + ((qry != null) ? "?" + qry : "");
		
		// 로그인 (인증) 되었을때 user 객체 가져오기
		HttpSession session = request.getSession();
		UserDTO user = (UserDTO)session.getAttribute(PRINCIPAL);
		
		// 로그인이 되어 있지 않다면
		// 로그인 페이지로 redirect 하고.  로그인이 완료되면? 원래 가고자 했던 url 로 돌아오게 하기
		if(user == null) {
			session.setAttribute(URL_PRIOR, url_prior);
			response.sendRedirect(conPath + "/user/login");
			return false;
		}
		
		// 접근에 필요한 권한이 있는 경우
		if(authorities != null && authorities.length > 0) {
			String auths = user.getAuthorities();
			
			// 그런데 로그인한 사용자에게 권한이 아예 없다면?
			if(auths == null) {
				response.sendRedirect(conPath + "/user/rejectAuth");
				return false;
			}
			
			// 필요한 권한이 하나라도 있다면 OK
			for(String authority : authorities) {
				if(auths.indexOf(authority) > -1) {
					return true;
				}
			}
			// 필요한 권한이 하나도 없으면 reject
			response.sendRedirect(conPath + "/user/rejectAuth");
			return false;
		}
		
		
		return true;
	} // end securityCheck()
	
	
	// session 에 담겨 있던 이전에 가고자 했던 url 꺼내오기
	public static String retrieveUrlPrior(HttpServletRequest request) {
		HttpSession session = request.getSession();
		String urlPrior = (String)session.getAttribute(URL_PRIOR);
		session.removeAttribute(URL_PRIOR);
		return urlPrior;
	}
	
	
	
	
} // C

* registerOk.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>    
    

<c:choose>
	<c:when test="${result == 0 }">
	<script>
		alert("가입 실패");
		history.back();
	</script>	
	</c:when>
	<c:otherwise>
	<script>
		alert("가입 성공");
		location.href = "login";
	</script>
	</c:otherwise>	
</c:choose>

 

로그인/로그아웃

URL : 로그인 폼 작성하기
UserController
GET/POST 방식

LoginCommand
1. 검증
- 존재하지 않는 아이디(username) 의 경우
- 패스워드가 다른 경우
2. 검증 통과시 로그인 진행
- session 에 로그인한 사용자 정보 저장.

샘플 회원 INSERT 시켜 두기

로그인 헤더 작성
/views/common/header.jsp 작성
home.jsp 에 include 하고 확인

로그인 여부,  권한여부에 따른 header 표시
header.jsp 수정
수동으로 로그아웃 해보면서, 샘플 계정들 하나하나 로그인 시키면서 결과 확인해보기

로그아웃 구현하기

'JSP' 카테고리의 다른 글

[JSP] Authentification - 회원제 게시판  (0) 2022.04.10
[JSP] Authentication - 로그인/로그아웃  (0) 2022.04.10
[JSP] JSTL(JSP Standard Tag Library)  (0) 2022.04.06
[JSP] EL : Expression Language  (0) 2022.04.06
[JSP] MVC model2  (0) 2022.04.06