[C-3-5] 게시물 작성 시 스프링 시큐리티 처리
2020. 1. 22. 15:40ㆍProject C (SBMS)/Project C 파트3
반응형
스프링 시큐리티를 사용할 때 POST 방식의 전송은 반드시 CSRF 토큰을 사용하도록 추가해야한다.
따라서, 게시글의 작성 기능에는 CSRF 토큰 사용은 필수이다.
수정 / 삭제에는 현재 로그인한 사용자와 게시글 작성자가 일치해야하는 경우에만 가능하게 해야한다.
과거에는 인터셉터로 처리했지만, @PreAuthorize 의 경우에는 표현식으로 처리가능하다.
views/board
list.jsp
더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="../includes/header.jsp"%>
<div class="review-form" style="width: 200px;border: 1px solid black; background:#D9E5FF; text-align:center; position: fixed;">
<form>
<input type='hidden' id='bookNum' name='bookNum' />
<input type='hidden' id='userId' name='${user_id }' />
<h4>개인독서노트</h4>
<div class="form-group" >
<label class="col-form-label col-form-label-sm" for="exampleSelect1">책장 종류</label>
<select class="form-control form-control-sm" id="shelfName" required="required">
<option value="">책장선택</option>
<option value="IT">IT</option>
<option value="외국어">외국어</option>
<option value="소설">소설</option>
<option value="교육">교육</option>
</select>
</div>
<div class="form-group">
<label class="col-form-label col-form-label-sm" for="inputSmall">책 이름</label>
<input class="form-control form-control-sm" type="text" placeholder="책 이름을 입력해주세요." id="bookTitle" required="required">
</div>
<div class="form-group">
<label class="col-form-label col-form-label-sm" for="inputSmall">책 저자</label>
<input class="form-control form-control-sm" type="text" placeholder="책 저자 입력해주세요." id="writer" required="required">
</div>
<div class="form-group">
<label class="col-form-label col-form-label-sm" for="inputSmall">출판사</label>
<input class="form-control form-control-sm" type="text" placeholder="출판사를 입력해주세요." id="publisher" required="required">
</div>
<div class="form-group">
<label class="col-form-label col-form-label-sm" for="inputDefault">구매날짜</label>
<input type="date" class="form-control form-control-sm" placeholder="구매날짜를 선택해주세요." id="purchaseDate" required="required">
</div>
<div class="form-group">
<label class="col-form-label col-form-label-sm" for="inputSmall">책 가격</label>
<input class="form-control form-control-sm" type="number" placeholder="책 가격을 입력해주세요." id="price" required="required">
</div>
<div class="form-group">
<label class="col-form-label col-form-label-sm" for="inputSmall">독서 평</label>
<textarea class="form-control form-control-sm" id="bookReview" rows="3" required="required"></textarea>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" id="btnReg" value="독서평 등록"/>
<button type="button" class="btn btn-danger" id="btnCan" onClick="cancelBookInfo();">취소</button>
</div>
<br/>
</form>
</div>
<div style="left:201px; width:1318px; position: absolute; " >
<h4>내 독서 노트</h4>
<div align="right">
<select id="search-shelf" name="type" style="width: 100px;">
<option value="" selected="selected">전체 책장</option>
<option value="IT">IT</option>
<option value="외국어">외국어</option>
<option value="소설">소설</option>
<option value="교육">교육</option>
</select>
<button type="button" class="btn btn-primary" id="btn-search">검색</button>
</div>
<table class="table table-hover myBookList" >
<!-- <thead>
<tr>
<th scope="row">책장명</th>
<th scope="row">책이름</th>
<th scope="row">저자</th>
<th scope="row">출판사</th>
<th scope="row">구매날짜</th>
<th scope="row">책가격</th>
<th scope="row">독서평</th>
<th scope="row">삭제</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">IT</th>
<td>자바 웹 프로그래밍</td>
<td>미노쿠마</td>
<td>미노북스</td>
<td>2020-01-11</td>
<td>35000</td>
<td>실습할 수 있어서 아주 좋다.</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-primary">
<th scope="row">Primary</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-secondary">
<th scope="row">Secondary</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-success">
<th scope="row">Success</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-danger">
<th scope="row">Danger</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-warning">
<th scope="row">Warning</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-info">
<th scope="row">Info</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-light">
<th scope="row">Light</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
<tr class="table-dark">
<th scope="row">Dark</th>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>Column content</td>
<td>
<button type="button" class="btn btn-outline-success">수정</button>
<button type="button" class="btn btn-outline-info">삭제</button>
</td>
</tr>
</tbody>
-->
</table>
<script type="text/javascript">
var loginId = null;
<sec:authorize access="isAuthenticated()">
loginId = '<sec:authentication property="principal.username"/>';
</sec:authorize>
var csrfHeaderName = "${_csrf.headerName}";
var csrfTokenValue = "${_csrf.token}";
//Ajax spring security header..
$(document).ajaxSend(function(e, xhr, options){
xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
});
var searchShelf = $("#search-shelf").val();
/* 도서 목록 JSON 데이터 추출 */
var thead = "";
thead += "<thead>";
thead += "<tr>";
thead += "<th scope=\"col\" width=\"10px;\">책장명</th>";
thead += "<th scope=\"col\" width=\"10px;\">책 이름</th>";
thead += "<th scope=\"col\" width=\"10px;\">저자</th>";
thead += "<th scope=\"col\" width=\"10px;\">출판사</th>";
thead += "<th scope=\"col\" width=\"10px;\">구매날짜</th>";
thead += "<th scope=\"col\" width=\"20px\">책 가격</th>";
thead += "<th scope=\"col\" width=\"100px;\">독서평</th>";
thead += "<th scope=\"col\" width=\"50px;\">수정 및 삭제</th>";
thead += "</tr>";
thead += "</thead>";
var bookinfoUL = $(".myBookList");
bookInfoService.getList({type: searchShelf},function(list) {
var innerTable = "";
innerTable += thead;
for (var i = 0, len = list.length || 0; i < len; i++) {
console.log(list[i]);
innerTable += "<tr>";
innerTable += "<th scope=\"row\">" + list[i].shelfName + "</th>";
innerTable += "<td>" + list[i].bookTitle + "</td>";
innerTable += "<td>" + list[i].writer + "</td>";
innerTable += "<td>" + list[i].publisher + "</td>";
innerTable += "<td>" + list[i].purchaseDate.substr(0,10) + "</td>";
innerTable += "<td>" + list[i].price + "</td>";
innerTable += "<td>" + list[i].bookReview + "</td>";
if ( loginId === list[i].userId){
innerTable += "<td><button class=\"btn btn-success\" type='button' onClick=\"editBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">수정</button><button class=\"btn btn-warning\" type='button' onClick=\"removeBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">삭제</button></td>";
}else{
//list[i].userId
innerTable += "<td><button class=\"btn btn-primary disabled\" type=\"button\" onClick=\"editBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">수정/삭제 불가</button></td>";
}
innerTable += "</tr>";
}
bookinfoUL.html(innerTable);
});
/* 도서 등록 또는 수정 JSON 데이터 전송 */
$("#btnReg").click(function() {
/* 폼 유효성 검사 */
if($("#shelfName").val() === "" || $("#bookTitle").val() === "" || $("#writer").val() === "" || $("#publisher").val() === "" || $("#purchaseDate").val() === "" || $("#price").val() === "" || $("#bookReview").val() === ""){
alert("개인독서노트 폼을 빠짐없이 모두 작성해주세요.");
return;
}
if($("#btnReg").val() === "독서평 등록"){
bookInfoService.add(
{
shelfName: $("#shelfName").val(),
userId: loginId,
bookTitle:$("#bookTitle").val(),
writer:$("#writer").val(),
publisher: $("#publisher").val(),
purchaseDate:$("#purchaseDate").val(),
price:$("#price").val(),
bookReview:$("#bookReview").val()
},
function(result){
showList();
alert("새 독서평이 등록되었습니다.");
$("#btnReg").val("독서평 등록");
$("#btnReg").attr('class','btn btn-primary');
$("#bookNum").val(""),
$("#userId").val(""),
$("#bookTitle").val(""),
$("#writer").val(""),
$("#publisher").val(""),
$("#purchaseDate").val(""),
$("#price").val(""),
$("#bookReview").val("")
}
);
}else if($("#btnReg").val() === "독서평 수정"){
bookInfoService.update(
{
bookNum: $("#bookNum").val(),
shelfName: $("#shelfName").val(),
userId: $("#userId").val(),
bookTitle:$("#bookTitle").val(),
writer:$("#writer").val(),
publisher: $("#publisher").val(),
purchaseDate:$("#purchaseDate").val(),
price:$("#price").val(),
bookReview:$("#bookReview").val()
},
function(result){
showList();
alert($("#bookNum").val() + "번의 독서평이 수정되었습니다.");
$("#bookNum").val(""),
$("#userId").val(""),
$("#bookTitle").val(""),
$("#writer").val(""),
$("#publisher").val(""),
$("#purchaseDate").val(""),
$("#price").val(""),
$("#bookReview").val("")
$("#btnReg").val("독서평 등록");
$("#btnReg").attr('class','btn btn-primary');
}
);
}
});
/* 도서 항목 JSON 데이터 출력 갱신 */
function showList() {
var searchShelf = $("#search-shelf").val();
var replyUL = $(".myBookList");
var innerTable = "";
bookInfoService.getList({type: searchShelf},function(list) {
innerTable += thead;
for (var i = 0, len = list.length || 0; i < len; i++) {
console.log(list[i]);
innerTable += "<tr>";
innerTable += "<th scope=\"row\">" + list[i].shelfName + "</th>";
innerTable += "<td>" + list[i].bookTitle + "</td>";
innerTable += "<td>" + list[i].writer + "</td>";
innerTable += "<td>" + list[i].publisher + "</td>";
innerTable += "<td>" + list[i].purchaseDate.substr(0,10) + "</td>";
innerTable += "<td>" + list[i].price + "</td>";
innerTable += "<td>" + list[i].bookReview + "</td>";
if ( loginId === list[i].userId){
innerTable += "<td><button class=\"btn btn-success\" type='button' onClick=\"editBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">수정</button><button class=\"btn btn-warning\" type='button' onClick=\"removeBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">삭제</button></td>";
}else{
//list[i].userId
innerTable += "<td><button class=\"btn btn-primary disabled\" type=\"button\" onClick=\"editBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">수정/삭제 불가</button></td>";
}
innerTable += "</tr>";
}
bookinfoUL.html(innerTable);
});
}
/* 도서 등록 폼 초기화 */
function cancelBookInfo(){
$("#shelfName").val("");
$("#btnReg").val("독서평 등록");
$("#btnReg").attr('class','btn btn-primary');
$("#bookNum").val(""),
$("#userId").val(""),
$("#bookTitle").val(""),
$("#writer").val(""),
$("#publisher").val(""),
$("#purchaseDate").val(""),
$("#price").val(""),
$("#bookReview").val(""),
$(".review-form").css("background-color", '#D9E5FF');
}
/* 도서 수정 폼 (도서 등록 폼 불러오기) */
function editBookInfo(bookNum, originUserId) {
if(loginId != originUserId){
$(".review-form").css("background-color", '#D9E5FF');
alert("자신이 작성한 댓글만 수정이 가능합니다. \n이 도서평은 " + originUserId + "님이 작성하였습니다.");
cancelBookInfo();
return;
}
$(".review-form").css("background-color", '#CEFBC9');
bookInfoService.get(bookNum, function(result){
$("#shelfName").val(result.shelfName);
$("#userId").val(result.userId);
$("#bookTitle").val(result.bookTitle);
$("#writer").val(result.writer);
$("#publisher").val(result.publisher);
$("#purchaseDate").val(result.purchaseDate.substr(0,10));
$("#price").val(result.price);
$("#bookReview").val(result.bookReview);
$("#bookNum").val(result.bookNum);
$("#btnReg").val("독서평 수정");
$("#btnReg").attr('class','btn btn-success');
});
}
/* 도서평 항목 삭제 */
function removeBookInfo(bookNum, originUserId) {
if(loginId != originUserId){
alert("자신이 작성한 도서평만 삭제가 가능합니다.");
return;
}
if(confirm('정말 삭제하시겠습니까?')) {
bookInfoService.remove(bookNum, loginId, function(result){
alert(bookNum + '번 도서평이 삭제되었습니다.');
showList();
});
}
}
/* 도서평 항목 책장 분류 검색 */
$(document).on('click', '#btn-search', function(){
var searchShelf = $("#search-shelf").val();
var bookinfoUL = $(".myBookList");
var innerTable = "";
bookInfoService.getList({type: searchShelf}, function(list) {
innerTable += thead;
for (var i = 0, len = list.length || 0; i < len; i++) {
console.log(list[i]);
innerTable += "<tr>";
innerTable += "<th scope=\"row\">" + list[i].shelfName + "</th>";
innerTable += "<td>" + list[i].bookTitle + "</td>";
innerTable += "<td>" + list[i].writer + "</td>";
innerTable += "<td>" + list[i].publisher + "</td>";
innerTable += "<td>" + list[i].purchaseDate.substr(0,10) + "</td>";
innerTable += "<td>" + list[i].price + "</td>";
innerTable += "<td>" + list[i].bookReview + "</td>";
if ( loginId === list[i].userId){
innerTable += "<td><button class=\"btn btn-success\" type='button' onClick=\"editBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">수정</button><button class=\"btn btn-warning\" type='button' onClick=\"removeBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">삭제</button></td>";
}else{
//list[i].userId
innerTable += "<td><button class=\"btn btn-primary disabled\" type=\"button\" onClick=\"editBookInfo('" + list[i].bookNum + "','" + list[i].userId + "')\">수정/삭제 불가</button></td>";
}
innerTable += "</tr>";
}
bookinfoUL.html(innerTable);
});
});
</script>
</div>
</html>
resources/js
book-info.js
더보기
console.log("bookInfo Module........");
var bookInfoService = (function() {
function getList(param, callback, error) {
var searchShelf = param.type;
$.getJSON("/sbms/bookinfo/getList/" + searchShelf + ".json",
function(data) {
if (callback) {
callback(data);
}
}).fail(function(xhr, status, err) {
if (error) {
error();
}
});
}
function add(bookinfo, callback, error) {
console.log("도서 정보를 추가합니다.");
$.ajax({
type : 'post',
url : '/sbms/bookinfo/new',
data : JSON.stringify(bookinfo),
contentType : "application/json; charset=utf-8",
success : function(result, status, xhr) {
if (callback) {
callback(result);
}
},
error : function(xhr, status, er) {
if (error) {
error(er);
}
}
})
}
function get(bookNum, callback, error) {
$.get("/sbms/bookinfo/" + bookNum + ".json", function(result) {
if (callback) {
callback(result);
}
}).fail(function(xhr, status, err) {
if (error) {
error();
}
});
}
function update(bookinfo, callback, error) {
$.ajax({
type : 'put',
url : '/sbms/bookinfo/' + bookinfo.bookNum,
data : JSON.stringify(bookinfo),
contentType : "application/json; charset=utf-8",
success : function(result, status, xhr) {
//alert('수정 reply!!!! 일단 성공!!');
if (callback) {
callback(result);
}
},
error : function(xhr, status, er) {
//alert('수정 reply!!!! 에러남!! ㅠㅠㅠㅠ');
if (error) {
error(er);
}
}
});
}
function remove(bookNum, loginId, callback, error) {
$.ajax({
type : 'delete',
url : '/sbms/bookinfo/' + bookNum,
data: JSON.stringify({bookNum:bookNum, userId:loginId}),
contentType: "application/json; charset=utf-8",
success : function(deleteResult, status, xhr) {
if (callback) {
callback(deleteResult);
}
},
error : function(xhr, status, er) {
if (error) {
error(er);
}
}
});
}
return {
getList : getList,
add : add,
get : get,
update : update,
remove : remove
};
})();
src/main/java
com.sbms.controller
BookInfoController.java
더보기
package com.sbms.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.sbms.domain.BookVO;
import com.sbms.domain.Criteria;
import com.sbms.service.BookInfoService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
@RequestMapping("/bookinfo/")
@RestController
@Log4j
@AllArgsConstructor
public class BookInfoController {
private BookInfoService bookInfoService;
@GetMapping(value = "/getList/{shelfName}", produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE })
public ResponseEntity<List<BookVO>> getList(@PathVariable String shelfName) {
log.info("[http://localhost/sbms/bookinfo/getList/{shelfName}.json]");
Criteria cri = new Criteria();
cri.setShelfName(shelfName);
return new ResponseEntity<>(bookInfoService.getList(cri), HttpStatus.OK);
}
@PreAuthorize("isAuthenticated()")
@PostMapping(value = "/new", consumes = "application/json", produces = { MediaType.TEXT_PLAIN_VALUE })
public ResponseEntity<String> create(@RequestBody BookVO book) {
int insertCount = bookInfoService.register(book);
return new ResponseEntity<>("success", HttpStatus.OK);
}
@GetMapping(value = "/{bookNum}", produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE })
public ResponseEntity<BookVO> get(@PathVariable("bookNum") Long bookNum) {
log.info("bookNum : " + bookNum);
return new ResponseEntity<>(bookInfoService.get(bookNum), HttpStatus.OK);
}
@PreAuthorize("principal.username == #book.userId")
@RequestMapping(method = { RequestMethod.PUT, RequestMethod.PATCH }, value = "/{bookNum}", consumes = "application/json", produces = {
MediaType.TEXT_PLAIN_VALUE })
public ResponseEntity<String> modify(@RequestBody BookVO book, @PathVariable("bookNum") Long bookNum) {
book.setBookNum(bookNum);
return bookInfoService.modify(book) == 1 ? new ResponseEntity<>("success", HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
@PreAuthorize("isAuthenticated()")
@DeleteMapping(value = "/{bookNum}", produces = { MediaType.TEXT_PLAIN_VALUE })
public ResponseEntity<String> remove(@RequestBody BookVO book, @PathVariable("bookNum") Long bookNum) {
return bookInfoService.remove(bookNum) == 1 ? new ResponseEntity<>("success", HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
수정사항
헤더의 로그인 영역에 로그인된 아이디 표기
수정 및 삭제 버튼 디자인 수정
- 수정 버튼 클릭 시 개인 독서노트 등록 폼 배경색상 변경
수정 및 삭제 버튼 기능 추가
- 로그인한 유저 아이디와 독서평을 등록했을 당시의 유저아이디와 비교해서 일치
수정/삭제 불가 버튼 추가
- 로그인한 유저 아이디와 독서평을 등록했을 당시의 유저아이디와 비교해서 불일치
버튼 클릭 시 사유 표시
반응형
'Project C (SBMS) > Project C 파트3' 카테고리의 다른 글
[C-3-4] 로그아웃 (0) | 2020.01.21 |
---|---|
[C-3-3] 회원가입 (0) | 2020.01.21 |
[C-3-2] 스프링 웹 시큐리티를 이용한 로그인 처리 2 (0) | 2020.01.21 |
[C-3-1] 스프링 웹 시큐리티를 이용한 로그인 처리 (0) | 2020.01.21 |