romworld

Spring 26 - security (애너테이션, PL/SQL&패키지를 통한 비밀번호 암호화/복호화 , 상관관계 서브쿼리) 본문

Spring

Spring 26 - security (애너테이션, PL/SQL&패키지를 통한 비밀번호 암호화/복호화 , 상관관계 서브쿼리)

inderrom 2023. 2. 15. 14:07
1. 스프링 시큐리티 애너테이션을 구현
오라클의 함수/패키지를 통해 비밀번호 암호화(Encrypt)/벽호화(Decrypt)

 

PL/SQL

  • Package
  • User function
  • Stored procesure
  • Trigger
  • Annoymous Block

<servlet-context.xml>

<!-- 스프링 시큐리티 애너테이션을 활성화
    - Secured : 스프링 시큐리티 모듈을 지원하기 위한 애너테이션
    - PreAuthorize : 메서드가 실행되기 전에 적용할 접근 정책을 지정할 때 사용
    - PostAuthorize : 메서드가 실행된 후에 적용할 접근 정책을 지정할 때 사용

    pre-post-annotations="enabled" : PreAuthorize, PostAuthorize를 사용할 수 있게 됨
    secured-annotations="enabled" : Secured를 사용할 수 있게 됨
 -->
<security:global-method-security pre-post-annotations="enabled"
secured-annotations="enabled"/>

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:security="http://www.springframework.org/schema/security"
   xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
      http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
      http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	<!-- 스프링 웹(view) 설정 파일 -->
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<!-- static folder설정(정적 폴더 설정) => css, images, upload, js
		http://localhost/resources/
	 -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
		<beans:property name="order" value="2" />
	</beans:bean>
	
	<!-- tiles 설정 시작
	TilesConfigurer tilesConfigurer = new TilesConfigurer();
	tiles-config.xml : 타일즈 설정 파일
	
	 -->
	<beans:bean id="tilesConfigurer"
		class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
		<beans:property name="definitions">
			<beans:list>
				<beans:value>/WEB-INF/spring/tiles-config.xml</beans:value>
			</beans:list>
		</beans:property>
	</beans:bean>
	
	<beans:bean id="tilesViewResolver"
		class="org.springframework.web.servlet.view.UrlBasedViewResolver">
		<beans:property name="viewClass" 
			value="org.springframework.web.servlet.view.tiles3.TilesView"/>
		<beans:property name="order" value="1" />
	</beans:bean>
	<!-- tiles 설정 끝 -->
	
	<!-- 스프링 시큐리티 애너테이션을 활성화
		- Secured : 스프링 시큐리티 모듈을 지원하기 위한 애너테이션
		- PreAuthorize : 메서드가 실행되기 전에 적용할 접근 정책을 지정할 때 사용
		- PostAuthorize : 메서드가 실행된 후에 적용할 접근 정책을 지정할 때 사용
		
		pre-post-annotations="enabled" : PreAuthorize, PostAuthorize를 사용할 수 있게 됨
		secured-annotations="enabled" : Secured를 사용할 수 있게 됨
	 -->
	<security:global-method-security pre-post-annotations="enabled"
	secured-annotations="enabled"/>
	
	<context:component-scan base-package="kr.or.ddit" />
	
</beans:beans>

 

 

<security-context.xml>

  • 주석처리

 

 

<BoardController.java>

package kr.or.ddit.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/board")
@Controller
public class BoardController {
	
	// 요청 URI : /board/list : 모두가 접근 가능
	@GetMapping("/list")
	public String list() {
		//forwarding
		//board 폴더의 list.jsp를 포워딩
		return "board/list";
	}
	
	// 요청 URI : /board/register : 로그인한 회원만 접근 가능
	@PreAuthorize("hasRole('ROLE_MEMBER')")
	@GetMapping("/register")
	public String register() {
		//forwarding
		return "board/register";
	}
}

로그인한 회원만 접근 가능하게 했으므로 로그아웃 후 다시 주소를 입력하면

로그인 화면으로 바뀐다.

 

  • board/list : 모두가 접근 가능 => 로그인한 사용자만 접근 가능
  • PreAuthorize("isAuthenticated()")
package kr.or.ddit.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/board")
@Controller
public class BoardController {
	
	// 요청 URI : /board/list : 모두가 접근 가능 => 로그인한 사용자만 접근 가능 (권한에 상관없이)
	// Authentication : 인증(로그인)
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/list")
	public String list() {
		//forwarding
		//board 폴더의 list.jsp를 포워딩
		return "board/list";
	}
	
	// 요청 URI : /board/register : 로그인한 회원만 접근 가능
	@PreAuthorize("hasRole('ROLE_MEMBER')")
	@GetMapping("/register")
	public String register() {
		//forwarding
		return "board/register";
	}
}

<NoticeController.java>

  • @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MEMBER')")
package kr.or.ddit.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/notice")
@Controller
public class NoticeControlelr {
	
	// 요청 URI : /notice/list : 모두 접근 가능
	@GetMapping("/list")
	public String list() {
		//forwarding
		return "notice/list";
	}
	
	// 요청 URI : /notice/register : 로그인한 관리자(ROLE_ADMIN)나 회원(ROLE_MEMBER) 권한을 가진 사용자만 접근 가능 
	// authentication : 인증(로그인) / authorization : 인가(권한)
	@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MEMBER')")
	@GetMapping("/register")
	public String register() {
		//forwarding
		return "notice/register";
	}
	
}

 

PL/SQL 연습 &  실습(암호화/복호화)

--PL/SQL
/
SET SERVEROUTPUT ON;
/
DECLARE
    R_NM VARCHAR2(100);
BEGIN
    SELECT  EMP_NM INTO R_NM
    FROM    EMP
    WHERE   EMP_NUM = 'EMP001';
    
    DBMS_OUTPUT.PUT_LINE('R_MM : ' || R_NM);
END;
/
--자바의 매개변수 = PL/SQL에서는 IN바인드변수
CREATE OR REPLACE FUNCTION FN_MEMNAME(P_EMP_NUM IN VARCHAR2)  
    RETURN VARCHAR2
IS
    R_NM VARCHAR2(100);
BEGIN
    SELECT  EMP_NM INTO R_NM
    FROM    EMP
    WHERE   EMP_NUM = 'EMP001';
    
    DBMS_OUTPUT.PUT_LINE('R_MM : ' || R_NM);
    
    RETURN R_NM;
END;
/
SELECT FN_MEMNAME('EMP001') FROM DUAL;
/
--선언부
CREATE OR REPLACE PACKAGE PKG_GET_NM
IS
    FUNCTION FN_MEMNAME(P_EMP_NUM IN VARCHAR2) RETURN VARCHAR2;
    FUNCTION FN_GET_CUS_NM(P_CAR_NUM IN VARCHAR2) RETURN VARCHAR2;
END;
/
--본문
CREATE OR REPLACE PACKAGE BODY PKG_GET_NM
IS
    --첫번재 함수 BODY
    FUNCTION FN_MEMNAME(P_EMP_NUM IN VARCHAR2) RETURN VARCHAR2
    IS
        --변수 : SCALAR변수, BIND변수, COMPOSITE변수, REFERENCE변수
        R_NM VARCHAR2(100);
        --R_PAY EMP.EMP_PAY%TYPE; --NUMBER
    BEGIN
        SELECT EMP_NM INTO R_NM
        FROM   EMP
        WHERE  EMP_NUM = P_EMP_NUM;
        
        RETURN R_NM;
    END FN_MEMNAME;
    
    --두번재 함수 BODY
    FUNCTION FN_GET_CUS_NM(P_CAR_NUM IN VARCHAR2) RETURN VARCHAR2
    IS
        --REFERENCE변수
        R_CUS_NM CUS.CUS_NM%TYPE;   --VARCHAR2(30)
    BEGIN
        SELECT B.CUS_NM INTO R_CUS_NM
        FROM   CAR A, CUS B
        WHERE  A.CUS_NUM = B.CUS_NUM
        AND    A.CAR_NUM = P_CAR_NUM;
        
        RETURN R_CUS_NM;
    END FN_GET_CUS_NM;
END PKG_GET_NM;
/

 

 

암호화/복호화 권한 부여

 

  • cmd 실행

- 접속

sqlplus sys/java@localhost:1521 as sysdba

-권한 부여

grant execute on dbms_crypto to spring;
grant execute on dbms_crypto to public;
grant execute on dbms_obfuscation_toolkit to public;

 

sql developer에서 실행

  • DES 는 비공개키, 대칭키
/
CREATE OR REPLACE PACKAGE pkg_crypto
IS
    FUNCTION encrypt ( input_string IN VARCHAR2 ) RETURN RAW;
    FUNCTION decrypt ( input_string IN VARCHAR2 ) RETURN VARCHAR2;
END pkg_crypto;
/

CREATE OR REPLACE PACKAGE BODY pkg_crypto
IS
-- 에러 발생시에 error code 와 message를 받기 위한 변수 지정.
    SQLERRMSG   VARCHAR2(255);
    SQLERRCDE   NUMBER;
    
    FUNCTION encrypt (input_string IN VARCHAR2 )
     RETURN RAW
    IS
        key_data_raw        RAW(64);
        converted_raw       RAW(64);
        encrypted_raw       RAW(64); 
    BEGIN
        -- 들어온 data 와 암호 키를 각각 RAW 로 변환한다.
        converted_raw := UTL_I18N.STRING_TO_RAW(input_string , 'AL32UTF8');
        key_data_raw  := UTL_I18N.STRING_TO_RAW(  '12345678' , 'AL32UTF8');

        -- DBMS_CRYPTO.ENCRYPT 로 암호화 하여 encrypted_raw 에 저장.
        encrypted_raw := DBMS_CRYPTO.ENCRYPT(  src => converted_raw ,
                   typ => DBMS_CRYPTO.DES_CBC_PKCS5 , -- typ 부분만 변경하면 원하는 알고리즘을 사용할 수 있다. key value byte 가 다 다르니 확인해야 한다.
                   key => key_data_raw ,
                   iv =>  NULL);
        RETURN encrypted_raw;
    END encrypt;

    FUNCTION decrypt (input_string IN VARCHAR2 )
     RETURN VARCHAR2
    IS
        converted_string    VARCHAR2(64);
        key_data_raw        RAW(64);
        decrypted_raw       VARCHAR2(64);
    BEGIN
        key_data_raw := UTL_I18N.STRING_TO_RAW(  '12345678' , 'AL32UTF8');
        decrypted_raw := DBMS_CRYPTO.DECRYPT( src => input_string ,
                                              typ => DBMS_CRYPTO.DES_CBC_PKCS5 ,
                                              key => key_data_raw ,
                                              iv =>  NULL);

        -- DBMS_CRYPTO.DECRYPT 수행 결과 나온 복호화 된 raw data를 varchar2로 변환하면 끝!
        converted_string := UTL_I18N.RAW_TO_CHAR(decrypted_raw, 'AL32UTF8');
        RETURN converted_string;
    END decrypt ;
END pkg_crypto;
/

실행

 

- test 해보기

암호화 처리됨

- test에서 나온 값을 decrypt로 출력하면 test가 나옴! 

-- cryptoVO.getEncrypt;(객체.멤버변수); 와 비슷한 개념
SELECT pkg_crypto.encrypt('test') from dual;

SELECT pkg_crypto.decrypt('A04B686B118AF67B') from dual;

mem 테이블로 실습

 

SELECT pkg_crypto.decrypt('A04B686B118AF67B') from dual;

SELECT  USER_NO
        , USER_PW
        , pkg_crypto.encrypt(USER_PW) CRYPTO_PW
FROM    MEM;

 

- USER _PW 컬럼에 UPDATE

-- 업데이트
--CORRELATED SUBQUERY(상관관계 서브쿼리)
UPDATE  MEM A
SET     A.USER_PW = (
            SELECT  pkg_crypto.encrypt(B.USER_PW)
            FROM    MEM B
            WHERE B.USER_NO = A.USER_NO --******
    );
    
SELECT * FROM MEM;

commit 한다.

 

 

**여기서  직원등록시 직원번호가 넘어가지지 않는 오류가 생긴다 

왜냐면 security 등록을 했기 때문에!

수정해주자! 

 

ajax 안에 추가 (직원번호, 매니저)

beforeSend : function(xhr) { // 데이터 전송 전 헤더에 csrf값 설정 xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}"); }

 

<create.jsp>

<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<link rel="stylesheet" href="/resources/css/bootstrap.min.css" />
<script src="https://ssl.daumcdn.net/dmaps/map_js_init/postcode.v2.js"></script>
<script type="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<script type="text/javascript">
	$(function() {
		//다음 우편번호 검색
		$("#btnPostno").on("click", function() {
			new daum.Postcode({
				//다음 창에서 검색이 완료되면 콜백함수에 의해 결과 데이터가 data 객체로 들어옴
				oncomplete : function(data) {
					//우편번호
					$("#zipCode").val(data.zonecode);
					$("#address").val(data.address);
					$("#detAddress").val(data.buildingName);
				}
			}).open();
		});

		// 직원번호 자동등록
		// processType:false는 파일업로드 시 사용(let formData = new formData())
		// contentType:"application/json;charset:utf-8"(보내는 타입)
		// data:JSON.stringify(data);
		// type:"post"
		// success:function(){}
		$.ajax({
			url : "/emp/getEmpNum",
			type : "post",
			beforeSend : function(xhr) {   // 데이터 전송 전  헤더에 csrf값 설정
                xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");
			},
			success : function(result) {
				console.log("result : " + result);
				$("#empNumAjax1").val(result);
			}
// 			,
// 			error : function(error) { 
// 				console.log("error : "+JSON.stringify(error));
// 				$("#empNumAjax1").val(error.responseText);
// 			}
		});

		// 매니저 선택하기
		$("#btnEmpMjNum").on(
				"click",
				function() {
					$.ajax({
						url : "/emp/getEmpAll",
						type : "post",
						beforeSend : function(xhr) {   // 데이터 전송 전  헤더에 csrf값 설정
			                xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");
						},
						success : function(result) {
							//result : List<EmpVO> empVOList
							let str = "";

							$.each(result, function(index, empVO) {
								console.log("empVO.empNum :" + empVO.empNum);
								console.log("empVO.empNm :" + empVO.empNm);

								str += "<tr class='trSelect'><th scope='row'>"
										+ (index + 1) + "</th>";
								str += "<td>" + empVO.empNum + "</td><td>"
										+ empVO.empNm + "</td></tr>";
							});

							console.log("str :" + str);
							//.html() : 새로고침 / .append() : 누적
							$("#trAdd").html(str); // 초기화
							//$("#trAdd").append(str);
						}
					});
				});

		// 동적으로 생성된 요소의 이벤트 처리
		//  $(".trSelect").on("click",function(){})은 작동을 안할것임
		$(document).on("click", ".trSelect", function() {
			//this : tr이 여러개인데 그 중 클릭한 바로 그 tr
			//td들을 가져옴. 그 안에 데이터를 가져와보자
			let resultStr = $(this).children().map(function() {
				return $(this).text();
			}).get().join(",");
			console.log("resultStr : " + resultStr);

			//resultStr : 4, EMP004, 훈이 => split(",")을 사용해서 배열로 만들고
			// [1]는 매니저번호(empMjNum)로 입력, [2]는 매 니저명(empMjNm)으로 입력
			// arr[0] : 4 / arr[1] : EMP004 /arr[2] : 훈이
			let arr = resultStr.split(",");
			$("#empMjNum").val(arr[1]);
			$("#empMjNm").val(arr[2]);
		});
	});
</script>
<!-- 요청URI : /emp/createPost
	요청파라미터 : {empNum=EMP001,zipCode=12345,address=대전...,empMjNum=}
	요청방식 : post
 -->
<form:form modelAttribute="empVO" action="/emp/createPost" method="post"
	class="row g-3">
	<!-- 직원번호 시작 -->
	<div class="col-md-6" style="clear: both;">
		<label for="empNum" class="form-label">직원번호</label>
		<!-- 	  <input type="text" name="empNum" class="form-control" id="empNum" -->
		<%-- 	  	value="${empNum}" placeholder="직원번호를 입력해주세요" /> --%>
		<%-- 	  <form:input path="empNum" class="form-control" placeholder="직원번호를 입력해주세요" /> --%>
		<input type="text" name="empNum" class="form-control" id="empNumAjax1"
			value="" placeholder="직원번호를 입력해주세요" readonly />
	</div>
	<!-- 직원번호 끝 -->
	<!-- 주소 시작 -->
	<div class="col-md-2">
		<label for="zipCode" class="form-label">우편번호</label> <input
			type="text" name="zipCode" class="form-control" id="zipCode"
			placeholder="우편번호를 검색해주세요" readonly />
		<button type="button" class="btn btn-primary" id="btnPostno">검색</button>
	</div>
	<div class="col-12">
		<label for="address" class="form-label">주소</label> <input type="text"
			name="address" class="form-control" id="address"
			placeholder="주소를 검색해주세요" readonly>
	</div>
	<div class="col-12">
		<label for="detAddress" class="form-label">상세주소</label> <input
			type="text" name="detAddress" class="form-control" id="detAddress"
			placeholder="상세주소를 입력해주세요">
	</div>
	<!-- 주소 끝 -->
	<!-- 연락처 시작 -->
	<div class="col-md-6">
		<label for="empPne" class="form-label">연락처</label> <input type="text"
			name="empPne" class="form-control" id="empPne"
			placeholder="연락처를 입력해주세요" required />
		<form:errors path="empPne" />
	</div>
	<!-- 연락처 끝 -->
	<!-- 직원명 시작 -->
	<div class="col-md-6">
		<label for="empNm" class="form-label">직원명</label> <input type="text"
			name="empNm" class="form-control" id="empNm"
			placeholder="직원명을 입력해주세요" required />
		<form:errors path="empNm" />
	</div>
	<!-- 직원명 끝 -->
	<!-- 급여 시작 -->
	<div class="col-md-6">
		<label for="empPay" class="form-label">급여</label> <input type="number"
			name="empPay" class="form-control" id="empPay"
			placeholder="급여를 입력해주세요" required />
		<form:errors path="empPay" />
	</div>
	<!-- 급여 끝 -->
	<!-- 매니저 등록 시작 -->
	<div class="col-md-6">
		<label for="empMjNm" class="form-label">매니저명</label> <input
			type="hidden" name="empMjNum" class="form-control" id="empMjNum" />
		<input type="text" class="form-control" id="empMjNm"
			placeholder="직원명을 입력해주세요" readonly />
		<button type="button" class="btn btn-primary" id="btnEmpMjNum"
			data-toggle="modal" data-target="#exampleModal">검색</button>
	</div>
	<!-- 매니저 등록 끝 -->
	<div class="col-12">
		<button type="submit" class="btn btn-primary">등록</button>
	</div>
</form:form>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1"
	aria-labelledby="exampleModalLabel" aria-hidden="true">
	<div class="modal-dialog">
		<div class="modal-content">
			<div class="modal-header">
				<h1 class="modal-title fs-5" id="exampleModalLabel">Modal title</h1>
				<button type="button" class="btn-close" data-dismiss="modal"
					aria-label="Close"></button>
			</div>
			<div class="modal-body">
				<!-- -------- 직원 목록 시작 ------------- -->
				<div class="bd-example">
					<table class="table table-hover">
						<thead>
							<tr>
								<th scope="col">#</th>
								<th scope="col">직원번호</th>
								<th scope="col">이름</th>
							</tr>
						</thead>
						<tbody id="trAdd">
							<tr>
								<th scope="row">1</th>
								<td></td>
								<td></td>
							</tr>
						</tbody>
					</table>
				</div>
				<!-- -------- 직원 목록 끝 ------------- -->
			</div>
			<div class="modal-footer">
				<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
				<button type="button" class="btn btn-primary">Save changes</button>
			</div>
		</div>
	</div>
</div>

 

 

<list.jsp>

직원목록도 수정

 

 

<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<link rel="stylesheet" href="/resources/css/bootstrap.min.css" />
<link rel="stylesheet" href="/resources/css/sweetalert2.min.css" />
<script type="text/javascript" src="/resources/js/bootstrap.min.js"></script>
<script src="https://ssl.daumcdn.net/dmaps/map_js_init/postcode.v2.js"></script>
<script type="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<script type="text/javascript" src="/resources/js/sweetalert2.min.js"></script>
<div class="card shadow mb-4">
	<div class="card-header py-3">
		<h6 class="m-0 font-weight-bold text-primary">직원목록</h6>
	</div>
	<div class="card-body">
		<div class="table-responsive">
			<div id="dataTable_wrapper" class="dataTables_wrapper dt-bootstrap4">
			<!-- 검색 영역 시작 action을 생략하면 현재의 URI, method를 생략하면 기본이 get -->
			<form name="frm" id="frm" action="/emp/list" method="get">
				<div class="row">
					<div class="col-sm-12 col-md-6">
						<div class="dataTables_length" id="dataTable_length">
							<label>Show 
							<!-- 한 화면에 보여질 행의 수 -->
							<select aria-controls="dataTable"
								name="show" id="show"
								class="custom-select custom-select-sm form-control form-control-sm">
								<option value="10" <c:if test="${param.show == 10}">selected</c:if>>10</option>
									<option value="25" <c:if test="${param.show == 25}">selected</c:if>>25</option>
									<option value="50" <c:if test="${param.show == 50}">selected</c:if>>50</option>
									<option value="100" <c:if test="${param.show == 100}">selected</c:if>>100</option></select> entries
							</label>
						</div>
					</div>
					<div class="col-sm-12 col-md-6">
						<div id="dataTable_filter" class="dataTables_filter">
							<label>Search:<input type="search" name="keyword" 
								class="form-control form-control-sm" 
								placeholder="검색어를 입력해주세요"
								aria-controls="dataTable"
								value="${param.keyword}">
							</label>
							<label>
								<button type="submit" class="btn btn-primary btn-icon-split btn-sm">
									<span class="icon text-white-50">
										<i class="fas fa-flag"></i>
									</span>
									<span class="text">검색</span>
								</button>
							</label>
						</div>
					</div>
				</div>
			</form>
			<!-- 검색 영역 끝 -->				
				<div class="row">
					<div class="col-sm-12">
						<table class="table table-bordered dataTable" id="dataTable"
							width="100%" cellspacing="0" role="grid"
							aria-describedby="dataTable_info" style="width: 100%;">
							<thead>
								<tr role="row">
									<th class="sorting sorting_asc" tabindex="0"
										aria-controls="dataTable" rowspan="1" colspan="1"
										aria-sort="ascending"
										aria-label="Name: activate to sort column descending"
										style="width: 66px;">번호</th>
									<th class="sorting" tabindex="0" aria-controls="dataTable"
										rowspan="1" colspan="1"
										aria-label="Position: activate to sort column ascending"
										style="width: 79px;">직원번호</th>
									<th class="sorting" tabindex="0" aria-controls="dataTable"
										rowspan="1" colspan="1"
										aria-label="Office: activate to sort column ascending"
										style="width: 54px;">직원명</th>
									<th class="sorting" tabindex="0" aria-controls="dataTable"
										rowspan="1" colspan="1"
										aria-label="Age: activate to sort column ascending"
										style="width: 31px;">급여</th>
									<th class="sorting" tabindex="0" aria-controls="dataTable"
										rowspan="1" colspan="1"
										aria-label="Start date: activate to sort column ascending"
										style="width: 69px;">매니저</th>
								</tr>
							</thead>
							<tbody>
								<!-- data : List<EmpVO> empVOList 
									empVO : EmpVO
								stat.index : 0부터 시작
								stat.count : 1부터 시작
								
								data : AtriclePage
								data.content : ArticlePage의 content 멤버변수(List<EmpVO>)
								-->
								<c:forEach var="empVO" items="${data.content}" varStatus="stat">
								<tr 
									<c:if test="${empVO.rnum % 2 == 0}">class="even"</c:if>
									<c:if test="${empVO.rnum % 2 != 0}">class="odd"</c:if>
								>
									<td class="sorting_1">${empVO.rnum}</td>
									<td><a href="/emp/detail?empNum=${empVO.empNum}">${empVO.empNum}</a></td>
									<td>${empVO.empNm}</td>
									<td>${empVO.empPay}</td>
									<td data-bs-toggle="modal" data-bs-target="#exampleModal">
										<a class="showMj" data-emp-mj-num="${empVO.empMjNum}" 
										data-bs-toggle="modal" href="#exampleModal">${empVO.empMjNm}</a>
									</td>
								</tr>
								</c:forEach>
							</tbody>
						</table>
					</div>
				</div>
				<<div class="row">
            <div class="col-sm-12 col-md-5">
                <div class="dataTables_info" id="dataTable_info" role="status"
                    aria-live="polite">
                    <c:if test="${param.show==null}">
                        <c:set var="show" value="1" />
                    </c:if>
                    <c:if test="${param.show!=null}">
                        <c:set var="show" value="${param.show}" />
                    </c:if>
                    <!-- scope(공유영역) : page(기본), request, session, application -->
                    <!-- 종료행 : currentPage * show -->
                    <c:set var="endRow" value="${data.currentPage * show}" />							
                    <!-- 시작행 : 종료행 - (size-1) -->
                    <c:set var="startRow" value="${endRow - (show-1)}" />
                    <!-- 전체행수 : total -->
                    <c:if test="${endRow > data.total}">
                        <c:set var="endRow" value="${data.total}"/>
                     </c:if>
                    Showing ${startRow} to ${endRow} of ${data.total} entries
                </div>
            </div>
					<div class="col-sm-12 col-md-7">
						<div class="dataTables_paginate paging_simple_numbers"
							id="dataTable_paginate">
							<ul class="pagination">
								<li class="paginate_button page-item previous 
									<c:if test='${data.startPage <6 }'>disabled</c:if>
								"id="dataTable_previous">
								<!-- keyword${param.keyword} -->
								<a href="/emp/list?currentPage=${data.startPage-5}&show=${param.show}&keyword=${param.keyword}"
									aria-controls="dataTable" data-dt-idx="0" tabindex="0"
									class="page-link">Previous</a></li>
								<c:forEach var="pNo" begin="${data.startPage}" end="${data.endPage}">
								<li class="paginate_button page-item
										<c:if test='${param.currentPage==pNo}'>active</c:if>
									 ">
									<a href="/emp/list?currentPage=${pNo}&show=${param.show}&keyword=${param.keyword}"
									aria-controls="dataTable" data-dt-idx="1" tabindex="0"
									class="page-link">${pNo}</a></li>
									</c:forEach>
								<li class="paginate_button page-item next
									<c:if test='${data.endPage == data.totalPages}'>disabled</c:if>
								" id="dataTable_next">
								<a href="/emp/list?currentPage=${data.startPage+5}&show=${param.show}&keyword=${param.keyword}" aria-controls="dataTable" data-dt-idx="7" tabindex="0"
									class="page-link">Next</a></li>
							</ul>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>
</div>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5" id="exampleModalLabel">매니저정보</h1>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <!-- 내용 시작 -->
         <div class="mb-3 row">
		    <label for="staticEmail" class="col-sm-2 col-form-label">직원 번호</label>
		    <div class="col-sm-10">
		      <input type="text" readonly class="form-control-plaintext" id="empNum" value="">
		    </div>
		  </div>
         <div class="mb-3 row">
		    <label for="staticEmail" class="col-sm-2 col-form-label">직원 명</label>
		    <div class="col-sm-10">
		      <input type="text" readonly class="form-control-plaintext" id="empNm" value="">
		    </div>
		  </div>
         <div class="mb-3 row">
		    <label for="staticEmail" class="col-sm-2 col-form-label">연락처</label>
		    <div class="col-sm-10">
		      <input type="text" readonly class="form-control-plaintext" id="empPne" value="">
		    </div>
		  </div>
         <div class="mb-3 row">
		    <label for="staticEmail" class="col-sm-2 col-form-label">주소</label>
		    <div class="col-sm-10">
		      <input type="text" readonly class="form-control-plaintext" id="empAddr" value="">
		    </div>
		  </div>
         <div class="mb-3 row">
		    <label for="staticEmail" class="col-sm-2 col-form-label">급여</label>
		    <div class="col-sm-10">
		      <input type="text" readonly class="form-control-plaintext" id="empPay" value="">
		    </div>
		  </div>
         <div class="mb-3 row">
		    <label for="staticEmail" class="col-sm-2 col-form-label">매니저</label>
		    <div class="col-sm-10">
		      <input type="text" readonly class="form-control-plaintext" id="empMjNm" value="">
		    </div>
		  </div>
        <!-- 내용 끝 -->
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>
<script type="text/javascript">
$(function(){
// 	$(document).on("click",".showMj",function(){
	$(".showMj").on("click",function(){
		console.log("ajax로 매니저 정보 가져와보자");
		console.log("요청URI : /emp/showMj");
		console.log("요청파라미터(json) : {'empNum':'EMP006'}");
		console.log("요청방식 : post");
		//this : class="showMj"이 여러개이고, 이 중에서 하나를 클릭한 바로 그 요소
		//data-emp-mj-num="EMP006"
		let empMjNum = $(this).data("empMjNum");
		console.log("empMjNum : " + empMjNum);
		
		//json object
		let data = {"empMjNum":empMjNum};
		
		$.ajax({
			url:"/emp/showMj",
			contentType:"application/json;charset:utf-8",
			data:JSON.stringify(data),//마샬링,일렬화(serialization)
			type:"post",
			dataType:"json",
			beforeSend : function(xhr) {   // 데이터 전송 전  헤더에 csrf값 설정
                xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");
},
			success:function(result){
				//result : {"empNum":"EMP006","empAddr":"12345 제주특별자치도 제주시 첨단로 242 개똥이빌딩 405"
// 					,"empPne":"010-123-1234","empNm":"개똥이","empPay":12345,"empMjNum":"EMP002"
// 					,"empMjNm":"김철수","serVOList":null}
				console.log("result : " + JSON.stringify(result));
				
				$("#empNum").val(result.empNum);
				$("#empNm").val(result.empNm);
				$("#empPne").val(result.empPne);
				$("#empAddr").val(result.empAddr);
				$("#empPay").val(result.empPay);
				$("#empMjNm").val(result.empMjNm);
			}
		});
		
	});
	
	//show가 바뀜
	$("#show").on("change",function(){
		//currentPage=1&keyword=개똥이&show=10
		let currentPage = "${param.currentPage}";
		let keyword = "${param.keyword}";
		
		console.log("currentPage : " + currentPage + ", keyword : " + keyword);
		
		let show = $(this).val();
		let show2 = $("#show option:selected").val();
		let show3 = $("select[name='show']").val();
		let show4 = $("#show option").index($("#show option:selected"));
		
		console.log("show : " + show);
		
		location.href="/emp/list?show="+show+"&currentPage=1&keyword="+keyword;
	});
});
</script>

 

 

도서등록도 바꾸자!

<create.jsp> (book)

  • taglib 추가
  • <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
  • <sec:csrfInput/>
  • 스프링 시큐리티를 사용할 경우 action 경로 뒤에 csrf 토큰을 입력해야함.(multipart/form-data로 전송할 경우에만)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="/resources/ckeditor/ckeditor.js">
</script>
<meta charset="UTF-8">
<title>책 등록하기</title>
<script type="text/javascript">
$(function(){
	// 이미지 미리보기 시작 -
	$("#input_imgs").on("change", handleImgFileSelect);
	// e: change 이벤트
	function handleImgFileSelect(e){
		// 파일객체에 파일들
		let files = e.target.files;
		// 이미지 배열
		let fileArr = Array.prototype.slice.call(files);
		// fileArr에서 하나 꺼내면 f(파일객체 1개)
		fileArr.forEach(function(f){
			// 이미지만 가능
			if(!f.type.match("image.*")){
				alert("이미지 확장자만 가능합니다.")
				return;
			}
			// 이미지를 읽을 객체
			let reader = new FileReader();
			//reader.readAsDataURL(f);의 이벤트
			reader.onload = function(e){
				let img_html = "<img src=\"" + e.target.result +"\" style='width:15%;' />";
				
				$(".imgs_wrap").append(img_html);
			}
			// 이미지를 읽는다.
			reader.readAsDataURL(f);
		});
	}
	// 이미지 미리보기 끝 -
})
</script>
</head>
<body>
<h1>책 등록</h1>
<!-- 폼페이지 -->
<!-- 
요청URI : /cretae?title=롬이야기&category=소설&price=10000
요청파라미터 : tilte=롬이야기&category=소설&price=10000
요청방식 : post

스프링 시큐리티를 사용할 경우 action 경로 뒤에 csrf 토큰을 입력해야함.(multipart/form-data로 전송할 경우에만)
 -->
<form action="/create?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
	<!-- 폼데이터 -->
	<div class="imgs_wrap"></div>
	<p>제목 : <input type="text" name="title" required /></p>
	<p>카테고리 : <input type="text" name="category" required /></p>
	<p>가격: <input type="number" name="price" required /></p>
	<p>내용: <textarea name="cont" rows="5" cols="30"></textarea></p>
	<p>
		책표지 : <input type="file" id="input_imgs" name="uploadfile" multiple />
	</p>
	<p>
		<input type="submit" value="저장" />
		<input type="button" id="list" value="목록" />
	</p>
	<sec:csrfInput/>
</form>
<script type="text/javascript">
	CKEDITOR.replace('cont');
	
	$("#list").on("click",function(){
		location.href="/list";
	});
	
</script>
</body>
</html>

 

 

 

**********************

form  action 태그에서 쓰는 경우와 ajax에서 쓰는 경우

 

 

book의 <detail.jsp>에서 첨부파일 이미지 경로에 upload뒤에 /가 있는 경우 지워야한다.

 

 


<emp_SQL.xml>

  • 회원로그인 쿼리문 수정
  • pkg_crypto.decrypt(A.USER_PW)  
<!-- 회원 로그인 -->
	<select id="memLogin" parameterType="memVO" resultMap="memMap">
		SELECT A.USER_NO, A.USER_ID
			 , pkg_crypto.decrypt(A.USER_PW) USER_PW
			 , A.USER_NAME, A.COIN, A.REG_DATE, A.UPD_DATE, A.ENABLED
		     , B.USER_NO, B.AUTH
		FROM   MEM A LEFT OUTER JOIN MEM_AUTH B
		ON(A.USER_NO = B.USER_NO)
		WHERE  A.USER_ID = #{userId}
	</select>

 

끄읏

Comments