AJax와 스프링 시큐리티 처리
<form>태그를 이용하는 방식 외에 많이 사용되는 Ajax를 이용하는 경우 약간의 추가적인 설정이 필요하다.
해당 Ajax부분을 로그인한 사용자만이 해당 기능들을 사용할 수 있도록 수정해주어야 한다.
스프링 시큐리티가 적용되면 POST, PUT, PATCH, DELETE와 같은 방식으로 데이터를 전송하는 경우
추가적으로 X-CSRF-TOKEN와 같은 헤더 정보를 추가해서 CSRF토큰값을 전달하도록 수정해야한다.
Ajax는 JS를 이용하기 때문에 브라우저에서는 CSRF토큰과 관련된 값을 변수로 선언하고, 전송 시 포함 시켜주는
방식으로 수정하자.
게시물등록 시 첨부파일의 처리
스프링 시큐리티가 적용된 후에는 게시물에 파일첨부가 정상적으로 동작하지 않는다.
게시물의 등록이 POST로 전송되기 때문이다. 이에따라 modify.jsp에서 JS를 수정해주어야 한다.
// $("input[type='file']").change(function(e) {
// var formData = new FormData();
// var inputFile = $("input[name='uploadFile']");
// var files = inputFile[0].files;
// for (var i = 0; i < files.length; i++) {
// if (!checkExtension(files[i].name, files[i].size)) {
// return false;
// }
// formData.append("uploadFile", files[i]);
// }
// $.ajax({
// url : '/uploadAjaxAction',
// processData : false,
// contentType : false,
// data : formData,
// type : 'POST',
// dataType : 'json',
// success : function(result) {
// console.log(result);
// showUploadResult(result); //업로드 결과 처리 함수
// }
// }); //$.ajax
// });
var csrfHeaderName ="${_csrf.headerName}";
var csrfTokenValue="${_csrf.token}";
$("input[type='file']").change(function(e){
var formData = new FormData();
var inputFile = $("input[name='uploadFile']");
var files = inputFile[0].files;
for(var i = 0; i < files.length; i++){
if(!checkExtension(files[i].name, files[i].size) ){
return false;
}
formData.append("uploadFile", files[i]);
}
$.ajax({
url: '/uploadAjaxAction',
processData: false,
contentType: false,
beforeSend: function(xhr) {
xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
},
data:formData,
type: 'POST',
dataType:'json',
success: function(result){
console.log(result);
showUploadResult(result); //업로드 결과 처리 함수
}
}); //$.ajax
});
기존것을 주석처리하고 , var 변수를 생성하고, beforeSend를 이용해서 추가적인 헤더를 지정해서
전송한다. 브라우저 내 개발자 도구를 통해서 살펴보면 전송 시 특별한 헤더가 같이 전송되는 것을 볼 수 있다.
첨부파일의 제거
첨부파일의 등록과 마찬가지로 첨부된 파일을 삭제하는 경우에도 post방식으로 동작하기 때문에 마찬가지로 CSRF토큰의 처리가 필요하다.
$(".uploadResult").on("click", "button", function(e){
console.log("delete file");
var targetFile = $(this).data("file");
var type = $(this).data("type");
var targetLi = $(this).closest("li");
$.ajax({
url: '/deleteFile',
data: {fileName: targetFile, type:type},
beforeSend: function(xhr) {
xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
},
dataType:'text',
type: 'POST',
success: function(result){
alert(result);
targetLi.remove();
}
}); //$.ajax
});
그리고 브라우저에서 로그인한 사용자만이 업로드가 가능하지만 필요하다면 서버쪽에서도 어노테이션등을 이용해서 업로드시 보안을 확인 할 수 있다.
//업로드 하는 기능에 어노테이션만 추가해준다.
@PreAuthorize("isAuthenticated()")
@PostMapping(value = "/uploadAjaxAction", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public ResponseEntity<List<AttachFileDTO>> uploadAjaxPost(MultipartFile[] uploadFile) {
/* 이후 다른것도 */
// @PostMapping("/deleteFile")
// @ResponseBody
// public ResponseEntity<String> deleteFile(String fileName, String type) {
@PreAuthorize("isAuthenticated()")
@PostMapping("/deleteFile")
@ResponseBody
해당 어노테이션만 달아주었다.
게시물 수정/삭제에서 첨부파일의 처리
게시물의 수정화면에서도 첨부파일은 추가/삭제가능하니 여기도 코드를 수정해야한다.!!!
// $("input[type='file']").change(function(e){
// var formData = new FormData();
// var inputFile = $("input[name='uploadFile']");
// var files = inputFile[0].files;
// for(var i = 0; i < files.length; i++){
// if(!checkExtension(files[i].name, files[i].size) ){
// return false;
// }
// formData.append("uploadFile", files[i]);
// }
// $.ajax({
// url: '/uploadAjaxAction',
// processData: false,
// contentType: false,data:
// formData,type: 'POST',
// dataType:'json',
// success: function(result){
// console.log(result);
// showUploadResult(result); //업로드 결과 처리 함수
// }
// }); //$.ajax
// });
var csrfHeaderName ="${_csrf.headerName}";
var csrfTokenValue="${_csrf.token}";
$("input[type='file']").change(function(e){
var formData = new FormData();
var inputFile = $("input[name='uploadFile']");
var files = inputFile[0].files;
for(var i = 0; i < files.length; i++){
if(!checkExtension(files[i].name, files[i].size) ){
return false;
}
formData.append("uploadFile", files[i]);
}
$.ajax({
url: '/uploadAjaxAction',
processData: false,
contentType: false,data:
formData,type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
},
dataType:'json',
success: function(result){
console.log(result);
showUploadResult(result); //업로드 결과 처리 함수
}
}); //$.ajax
});
댓글 기능에서의 Ajax
댓글의 경우 모든 동작이 Ajax를 통해서 이루어지기 때문에 화면에서도 수정되어야 하는 부분이 있고, 서버쪽에서도 변경될 부분이 꽤 있다.
우선 서버 쪽에서는 ReplyController가 댓글에 대한 보안 원칙을 다음과 같이 설계할 수 있다.
- 댓글의 등록 : 로그인한 사용자만 댓글을 추가할 수 있다.
- 댓글의 수정과 삭제 : 로그인한 사용자와 댓글 작성자의 아이디를 비교해서 같은 경우에만 댓글을 수정/ 삭제할 수 있다.
브라우저 쪽에서는 기존과 달라지는 부분은
- 댓글의 등록 : CSRF 토큰을 같이 전송한다.
- 댓글의 수정/ 삭제 : 기존의 댓글삭제에는 댓글 번호만으로 처리했다면, 사버 쪽에서 사용할 것이므로, 작성자도 같이 전송하자.
댓글 등록
댓글의 처리는 get.jsp 파일을 수정해야한다. 여기도 시큐리티 태그라이브러리를 넣어주자.
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
댓글등록은 만일 사용자가 로그인 해있다면, 현재 로그인한 사용자가 댓글 작성자가 되어야 하므로, 코드를 수정해준다.
var replyer = null;
<sec:authorize access="isAuthenticated()">
replyer = '<sec:authentication property="principal.username"/';
</sec:autherize>
var csrfHeaderName="${_csrf.headerName}";
var csrfTokenValue="${_csrf.token}";
가장 중요한 CSRF토큰에 대한 처리는 csrfHeaderName변수와 csrfTokenValue변수를 선언ㅇ해서 처리한다.
댓글을 보여주는 모달창에는 현재 로그인한 사용자의 이름이으로 replyer항목 고정되도록 수정한다.
$("#addReplyBtn").on("click", function(e){
modal.find("input").val("");
modal.find("input[name='replyer']").val(replyer);
modalInputReplyDate.closest("div").hide();
modal.find("button[id !='modalCloseBtn']").hide();
modalRegisterBtn.show();
$(".modal").modal("show");
});
제이쿼리를 이용해서 Ajax로 CSRF토큰을 전송하는 방식은 첨부파일의 경우 beforeSend를 이용해서 처리했지만,
기본 설정으로 지정해서 사용하는 것이 더 편하기 때문에 아래 코드를 사용한다.
ajaxSend()를 이용한 코드는 모든 Ajax전송시 CSRF토큰을 같이 전송하도록 세팅되기 때문에 매번 Ajax사용 시 beforeSend를 호출해야 하는 번거로움을 줄일 수 있다.
ReplyController에서는 댓글 등록이 로그인한 사용자인지를 확인한다.
@PreAuthorize("isAuthenticated()")
@PostMapping(value = "/new", consumes = "application/json", produces = { MediaType.TEXT_PLAIN_VALUE })
public ResponseEntity<String> create(@RequestBody ReplyVO vo) {
log.info("ReplyVO: " + vo);
int insertCount = service.register(vo);
log.info("Reply INSERT COUNT: " + insertCount);
return insertCount == 1 ? new ResponseEntity<>("success", HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
마찬가지로 어노테이션을 추가해준다.
브라우저에 새로운 댓글을 추가하려고 하면 댓글 작성자는 고정된 형태로 보이게 되고, 전송시 CSRF토큰이 같이 전송된다.
이때 수정과, 삭제에 대해서도 수정을 해주자.
기존코드에 해당 어노테이션만 추가해주면된다.
댓글삭제, 수정
댓글 삭제는 자신이 작성ㅇ한 댓글만이 삭제가 가능하도록 해야한다.
화면에서는 JS를 이용해서 모달창의 댓글 작성자 정보와 현재 로그인한 사용자가 같은지를 비교해서
같은 경우에만 Ajax로 댓글을 삭제 할 수 있도록 한다.
만일 자신이 작성한 댓글이 아닌 경우나 로그인하지 않은 경우에는 삭제할 수 없도록 제한해야한다.
댓글삭제- 수정시 기존에는 rno 댓글 남바만 전송했지만 작성자를 같이 전송하도록 수정한다.
modalRemoveBtn.on("click", function (e){
var rno = modal.data("rno");
console.log("RNO: " + rno);
console.log("REPLYER: " + replyer);
if(!replyer){
alert("로그인후 삭제가 가능합니다.");
modal.modal("hide");
return;
}
var originalReplyer = modalInputReplyer.val();
console.log("Original Replyer: " + originalReplyer);
if(replyer != originalReplyer){
alert("자신이 작성한 댓글만 삭제가 가능합니다.");
modal.modal("hide");
return;
}
replyService.remove(rno, originalReplyer, function(result){
alert(result);
modal.modal("hide");
showList(pageNum);
});
});
<!-- 수정기능부분-->
modalModBtn.on("click", function(e){
var reply = {rno:modal.data("rno"), reply: modalInputReply.val()};
if(!replyer){
alert("로그인후 수정이 가능합니다.");
modal.modal("hide");
return;
}
var originalReplyer = modalInputReplyer.val();
console.log("Original Replyer: " + originalReplyer);
if(replyer != originalReplyer){
alert("자신이 작성한 댓글만 수정이 가능합니다.");
modal.modal("hide");
return;
}
replyService.update(reply, function(result){
alert(result);
modal.modal("hide");
showList(pageNum);
});
});
이렇게 originalReplyer가 추가된 후에는 resources폴더 내의 js/reply.js에서 rno와 replyer를 같이 전송하도록 수정하자
function remove(rno, replyer, callback, error) {
console.log("--------------------------------------");
console.log(JSON.stringify({rno:rno, replyer:replyer}));
$.ajax({
type : 'delete',
url : '/replies/' + rno,
data: JSON.stringify({rno:rno, replyer:replyer}),
contentType: "application/json; charset=utf-8",
success : function(deleteResult, status, xhr) {
if (callback) {
callback(deleteResult);
}
},
error : function(xhr, status, er) {
if (error) {
error(er);
}
}
});
}
로그아웃 처리
header.jsp를 수정해서 스프링 시큐리티를 이용하도록 수정하고, 로그인한 상태에서는 로그아웃 페이지로 이동하도록 하자.
<sec:authorize access="isAuthenticated()">
<li><a href="/customLogout"><i class="fa fa-sign-out fa-fw"></i>
Logout</a></li>
</sec:authorize>
<sec:authorize access="isAnonymous()">
<li><a href="/customLogin"><i class="fa fa-sign-out fa-fw"></i>
Login</a></li>
</sec:authorize>
시큐리티 라이브러리를 넣어주고, 해당코드를 넣어 로그인, 비로그인상태에따른 버튼활성화를 생성한다.
그리고 로그아웃이 되었을때 이동되는 화면을 구현해주기위해
customLogout.jsp로 제작하고 /customLogout으로 이동하도록 하게하자.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Bootstrap Admin Theme</title>
<!-- Bootstrap Core CSS -->
<link href="/resources/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- MetisMenu CSS -->
<link href="/resources/vendor/metisMenu/metisMenu.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/resources/dist/css/sb-admin-2.css" rel="stylesheet">
<!-- Custom Fonts -->
<link href="/resources/vendor/font-awesome/css/font-awesome.min.css"
rel="stylesheet" type="text/css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="login-panel panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Logout Page</h3>
</div>
<div class="panel-body">
<form role="form" method='post' action="/customLogout">
<fieldset>
<!-- Change this to a button or input when using this as a form -->
<a href="index.html" class="btn btn-lg btn-success btn-block">Logout</a>
</fieldset>
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
</form>
</div>
</div>
</div>
</div>
</div>
<!-- jQuery -->
<script src="/resources/vendor/jquery/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="/resources/vendor/bootstrap/js/bootstrap.min.js"></script>
<!-- Metis Menu Plugin JavaScript -->
<script src="/resources/vendor/metisMenu/metisMenu.min.js"></script>
<!-- Custom Theme JavaScript -->
<script src="/resources/dist/js/sb-admin-2.js"></script>
<script>
$(".btn-success").on("click", function(e){
e.preventDefault();
$("form").submit();
});
</script>
</body>
</html>
작성했다면 로그아웃이 정상적으로 작동하는지 확인하자
정상적으로 로그아웃하는 순서로 진행되었다.
로그인이후 처리
여기서 로그인을 시도하면
해당 홈화면으로 이동하게된다.
이것을 수정하기위해 home.jsp 파일을 수정해주자.
<script>
self.location ="/board/list";
</script>
를 넣어주어 자동으로 이동하게하였다. 확인하자
'Spring공부 > 4-스프링시큐리티' 카테고리의 다른 글
프로젝트(1) (0) | 2021.10.21 |
---|---|
자동 로그인 (0) | 2021.10.21 |
스프링 시큐리티와 JSP (0) | 2021.10.19 |
커스텀 UserDetailsService (0) | 2021.10.19 |
JDBC를 이용하는 간편 인증/ 권한처리 (0) | 2021.10.18 |
댓글