* 인증 (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 |