일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- crud
- servlet
- 컬렉션프레임워크
- 반복문
- ibatis
- ddit
- JDBC
- Homebrew
- Android
- Mac
- 단축키
- python
- 자바문제
- 맥
- spring
- API
- 대덕인재개발원
- 생활코딩
- 자바
- nodejs
- 이클립스
- html
- Oracle
- FastAPI
- jsp
- Error
- pyqt
- 배열
- 객체지향
- Java
Archives
- Today
- Total
romworld
Spring 26 - security (애너테이션, PL/SQL&패키지를 통한 비밀번호 암호화/복호화 , 상관관계 서브쿼리) 본문
Spring
Spring 26 - security (애너테이션, PL/SQL&패키지를 통한 비밀번호 암호화/복호화 , 상관관계 서브쿼리)
inderrom 2023. 2. 15. 14:071. 스프링 시큐리티 애너테이션을 구현
오라클의 함수/패키지를 통해 비밀번호 암호화(Encrypt)/벽호화(Decrypt)
PL/SQL
- Package
- User function
- Stored procesure
- Trigger
- Annoymous Block
<servlet-context.xml>
- cecurity-context에서
- xmlns:security="http://www.springframework.org/schema/security"
- http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
<!-- 스프링 시큐리티 애너테이션을 활성화
- 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+"¤tPage=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>
끄읏
'Spring' 카테고리의 다른 글
Spring 28 - adminlte3을 사용한 CRUD [setInterval(), datatables 사용법, ID 자동완성] (0) | 2023.02.16 |
---|---|
Spring 27 - 전자정부프레임워크와 부트스트랩 사용해보자(adminlte3) (0) | 2023.02.15 |
Spring 24 - security (테이블을 생성해 실습해보자 DB사용), LEFT OUTER JOIN, 구글 카멜변환, 로그인, 로그아웃 (0) | 2023.02.14 |
Spring 23 - security (+ ajax에 csrf값 설정) (0) | 2023.02.13 |
Spring 22 - 트랜잭션, 예외처리 (0) | 2023.02.10 |
Comments