스프링 프로젝트 Part7 - 스프링 시큐리티 접목

꼼꼼발·2020년 12월 3일
0
post-thumbnail

기존 프로젝트에 스프링 시큐리티를 적용한다.

  • 로그인과 회원 가입 페이지 작성

  • 기존 화면과 컨트롤러에 시큐리티 관련 내용 추가

  • Ajax부분 변경

기존 프로젝트에 시큐리티 관련 설정을 수가한다.

  • security-context.xml 추가 / security 패키지 / domain 패키지의 MemberVO AuthVO 클래스 추가

  • web.xml에서 security-context.xml설정과 필터 추가

  • MemberMapper 인터페이스와 MemberMapper.xml의 추가

  • controller패키지에 CommonController추가


로그인 페이지 처리

로그인에 대한 처리는 /customLogin으로 처기한다. CSS나 자바스크립트 링크가 ../vendor../dist로 되어있는 링크를 /resources/vendor/resources/dist로 수정한다.

customLogin.jsp를 작성할 때 신경 쓸 부분

  • JSTL, 스프링 시큐리티의 태그를 사용할 수 있게 선언

  • CSS 파일이나 JS 파일 링크를 절대경로로 수정

  • <form>태그내의 <input>태그의 name속성을 스프링 시큐리티에 맞게 수정

  • CSRF 토큰 항목 추가

  • 자바스크립트를 통한 로그인 전송

로그인 테스트

/customLogin을 호출해서 admin91/pw91으로 로그인했다면 아래와 같은 에러가 뜬다.

이는 로그인 성공 후 CustomLoginSuccessHandler를 이용해서 사용자의 권한에 따라 이동하도록 했기 때문이다. 스프링 시큐리티는 기본적으로 로그인 후 처리를 SavedRequestAwareAuthenticationSuccessHandler라는 클래스를 이용한다. 해당 클래스는 사용자가 원래 보려던 페이지의 정보를 유지해서 로그인 후 다시 원했던 페이지로 이동하는 방식이다.

SavedRequestAwareAuthenticationSuccessHandler를 이용하는 설정은 기존 XML이나 Java설정에서 authentication-success-handler-ref속성이나 successHandler()메서드를 삭제하고 관련 스프링 빈의 설정도 사용하지 않도록 한다. 프로젝트는 게시물의 작성(/board/register)시 로그인 페이지로 이동하고 로그인 후에 다시 게시물의 작성 페이지로 이동하는 방식을 적용할 것이다.

security-context.xml

CustomLoginSuccesshandler빈과 <security:form-login>의 참조한 것들을 주석 처리하고 아래의 것만 적용한다.

<!-- 711p -->
<security:form-login login-page="/customLogin"/>

게시물 작성 시 스프링 시큐리티 처리

게시물 리스트의 경우 사용자들의 관심을 끌기 위해 아무 제약 없이 보여주지만 게시물 작성시에는 로그인한 상ㅇ자에 한해 처리해야 한다. servlet-context.xml에는 스프링 시큐리티 관련 설정을 추가하고 BoardController에 어노테이션을 통해 제어한다.

  • 게시물 작성으로 이동한다
    • 로그인 하지 않은 경우 로그인 페이지를 띄운다.
    • 로그인한 경우 게시물 작성 페이지로 간다.

BoardController의 메서드일부에 어노테이션을 추가한다.

BoardController

@PreAuthorize("isAuthenticated()") : 3버전부터 지원하며, ( ) 안에 표현식을 사용할 수 있다. 이는 어떠한 사용자든 로그인에 성공하면 해당 기능을 사용할 수 있다는 것이다.


게시물 작성시 로그인한 사용자의 아이디 출력

게시물의 작성은 로그인한 사용자만 허용되고 작성자(writer)항목에는 현재 사용자의 아이디가 출력될 수 있게 수정한다.

register.jsp

<div class="form-group">
  <label>작성자</label>
  <input type="text" class="form-control" name="writer" value='<sec:authentication property="principal.username"/>' readonly="readonly">
</div>

register.jsp와 같이 스프링 시큐리티의 영향을 받는 JSP파일에는 반드시 시큐리티 관련 태그 라이브러리를 설정해야한다. 작성자에 현재 사용자는 현재 로그인한 사용자의 아이디를 출력한다.(스프링 시큐리티에서는 username이 사용자의 아이디이다.)

브라우저에 현재 사용자의 아이디가 추가된 상태인지 확인한다.

CSRF 토큰 설정

스프링 시큐리티를 사용할 때 POST방식의 전송은 반드시 CSRF토큰을 사용하도록 추가해야한다. <form>태그 내에 CSRF 토큰의 값을 <input type='hidden'>으로 추가한다.

register.jsp

<form role="form" action="/board/register" method="post">
  ...생략...
  <!-- CSRF 토큰 설정 -->
  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>

브라우저에서 게시물의 등록이 정상적으로 이루어지는지 확인한다.

스프링 시큐리티 한글처리

게시물의 등록에서 스프링 시큐리티 적용 이후에 한글이 깨질 수도 있따. 한글처리는 web.xml을 이용해서 스프링의 CharacterEncodingFilter를 이용해서 처리하지만 시큐리티 필터로 적용할 때에는 필터의 순서를 주의해서 설정해야 한다. web.xml에서 필터의 순서가 바뀌는 경우에는 게시물의 작성 시에 한글이 깨져서 BoardController에 전달된다.

web.xml(인코딩 설정을 선 적용 후 스프링 시큐리티 적용)

<!-- 한글설정 -->
<filter>
	<filter-name>encodingFilter</filter-name>
	<filter-class>
		org.springframework.web.filter.CharacterEncodingFilter
	</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>UTF-8</param-value>
	</init-param>
	<init-param>
		<param-name>forceEncoding</param-name>
		<param-value>true</param-value>
	</init-param>
</filter>

<filter-mapping>
	<filter-name>encodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 한글설정 END -->

<!-- 스프링 시큐리티를 스프링 MVC에서 사용할 수 있게함 -->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

게시물 조회와 로그인 처리

게시물의 조회는 그 자체는 로그인 여부와 관계없이 처리되지만 게시물의 조회 화면에서 현재 로그인한 사용자만 수정/삭제 작업을 할 수 있는 기능을 활성화해야된다. 다시말해서 로그인 하지 않은 사용자가 게시물을 보면 수정 버튼이 없어야된다는 것이다.

게시물 조회를 담당하는 화면에서 현재 게시물의 작성자와 현재 로그인한 사용자 정보를 비교해서 이를 처리하도록 수정한다.

get.jsp 태그라이브러리

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

위의 그림과 같이 현재 로그인하고, 게시글의 작성자만 수정/삭제가 가능한 버튼이 보이게 하는 부분을 구현한다.

get.jsp

<!-- 로그인한 사용자만 Modify버튼 보이게 하기 -->
<sec:authentication property="principal" var="pinfo"/>
    <sec:authorize access="isAuthenticated()">
        <c:if test="${pinfo.username eq board.writer}">
            <button data-oper='modify' class="btn btn-default"
            onclick="location.href='/board/modify?bno=${board.bno}'">Modify</button>
        </c:if>
    </sec:authorize>
<!-- /.authentication -->

<sec:authentication>태그를 매번 이용하는 것은 불편하기 때문에 로그인과 관련된 정보인 principal은 아예 JSP내에서 pinfo라는 이름의 변수로 사용하도록 했다. <sec:authorize>는 인증받은 사용자만 영향을 받기 위해서 지정하고, 내부에서는 username과 게시물의 writer가 일치하는지를 확인해서 Modify버튼을 추가한다.

조회 화면에서 댓글 추가 버튼

로그인한 사용자만 조회 화면에서 댓글을 추가할 수 있기에, <sec:authorize>태그를 이용해서 댓글 버튼의 활성/비활성화도 처리한다.

get.jsp

<!-- 로그인한 사용자만 댓글 활성화 -->
<sec:authorize access="isAuthenticated()">
    <button id="addReplyBtn" class='btn btn-primary btn-xs pull-right'>댓글 작성하기</button>
</sec:authorize>
<!-- /.authorize -->


게시물의 수정/삭제

게시물의 수정과 삭제는 브라우저에서는 로그인한 사용자만 접근할 수 있지만 사용자가 URL을 조작해서 접근이 가능하기 때문에 화면과 POST방식으로 처리되는 부분에서 CSRF토큰과 스프링 시큐리티를 적용한다.

게시물의 수정과 삭제는 현재 로그인한 사용자와 게시물의 작성자가 동일한 경우에만 할 수 있다. @PreAuthorize어노테이션을 이용해서 표현식으로 처리할 수 있다.

브라우저 화면에서 설정

modify.jsp를 통해 처리한다.

상단에는 스프링 시큐리티의 태그 라이브러리를 설정하고 POST방식으로 처리하기때문에 CSRF토큰도 추가한다.

modify.jsp

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<!-- POST로 처리되므로 CSRF토큰 추가 -->
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">

조회와 마찬가지로 현재 로그인한 사용자가 게시물의 작성자인 경우에만 수정과 삭제가 가능하도록 제어한다.

modify.jsp

<!-- 로그인한 사용자만 수정/삭제 버튼 보이게 하기 -->
<sec:authentication property="principal" var="pinfo"/>
<sec:authorize access="isAuthenticated()">
	<c:if test="${pinfo.username eq board.writer}">
		<button type="submit" data-oper='modify' class="btn btn-default">수정</button>
		<button type="submit" data-oper='remove' class="btn btn-danger">삭제</button>
	</c:if>
</sec:authorize>
<!-- /.authentication -->

BoardController에서 제어

BoardController에서는 메서드를 실행하기 전에 로그인한 사용자와 현재 파라미터로 전달되는 작성자가 일치하는지 체크한다. @PreAuthorize의 경우 문자열로 표현식을 지정할 수 있는데 이 때 컨트롤러에 전달되는 파라미터를 같이 사용할 수 있으므로 유용하다.

삭제의 경우 기존에는 파라미터로 게시물의 번호 bno만 받았지만 작성자를 의미하는 writer를 같이 추가해서 @PreAuthorize로 검사하자

BoardController수정 처리

@PreAuthorize("principal.username == #board.writer") //720p
@PostMapping("/modify")
public String modify(BoardVO board, Criteria cri, RedirectAttributes rttr) {
	log.info("modify:" + board);
	
	if(service.modify(board)) {
		rttr.addFlashAttribute("result", "success");
	}
	return "redirect:/board/list" + cri.getListLink();
}

게시물 수정은 파라미터로 BoardVO를 받기때문에 작성자를 검사할때 #board.writer로 한다.

BoardController삭제 처리

@PreAuthorize("principal.username == #writer") //720p
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, 
  RedirectAttributes rttr, String writer) {
	System.out.println("여기로 들어오나요?");
	log.info("remove...." + bno);
	
	//581p
	List<BoardAttachVO> attachList = service.getAttachList(bno);
	for(BoardAttachVO result : attachList) {
		System.out.println(result.getFileName());
		System.out.println(result.getUploadPath());
		System.out.println(result.getUuid());
		System.out.println(result.getBno());
	}
	
	replayService.removeAll(bno);
	
	if(service.remove(bno)) {
		//delete Attach Files
		deleteFiles(attachList);
		
		rttr.addFlashAttribute("result","success");
	}
	return "redirect:/board/list" + cri.getListLink();
}

파라미터로 writer를 추가하고 해당 파라미터를 @PreAuthorize에서 #writer를 이용해서 검사한다.


Ajax와 스프링 시큐리티 처리

<form>태그를 이용하는 방식 외에 많이 사용되는 Ajax를 이용하는 경우 추가적인 설정이 필요하다. 현재 프로젝트는 파일 업로드와 댓글 부분이 Ajax를 이용하므로 로그인한 사용자만이 해당 기능들을 사용할 수 있도록 수정한다.

스프링 시큐리티가 적용되면 POST, PUT PATCh, DELETE와 같은 방식으로 데이터를 전송하면 반드시 추가적으로 X-CSRF-TOKEN와 같은 헤더 정보를 추가해서 CSRF토큰 값을 전달하도록 수정해야된다. Ajax는 자바스크립트를 이용하기에 브라우저에서는 CSRF토큰과 관련된 값을 변수로 선언하고, 전송시 포함시켜주면 된다.

게시물 등록시 첨부파일 처리

스프링 시큐리티가 적용된 후 게시물에 파일 첨부가 정상적으로 동작하지 않는 것을 알 수 있다. 게시물의 등록이 POST방식으로 전송되기 때문에 발생하는 문제다. 게시물의 등록 페이지에서 자바스크립트 부분을 수정해야된다.

register.jsp

//CSRF토큰값 변수로 선언하기 721p
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,
        //X-CSRF-TOKEN 헤더 정보 추가하기 721p
        beforeSend: function(xhr) {
            xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
        },
        data: formData,
        type: "POST",
        dataType: "json",
        success: function(result) {
            console.log(result);
            showUploadResult(result); //업로드 결과 처리 함수
        }
    });//$.ajax
});

기존 코드에서 csrfHeaderNamecsrfTokenValue변수를 추가한다. CSRF 토큰의 값은 세션이 달라질 때마다 변한다. Ajax로 데이터를 전송할 때 beforeSend를 이용해서 추가적인 헤더를 지정해서 전송한다. 브라우저 내 개발자 도구를 통해서 살펴보면 전송 시 특별한 헤더가 같이 전송된다.

첨부파일 제거

첨부파일의 등록과 마찬가지로 첨부된 파일을 삭제하는 경우에도 POST방식으로 동작하기 때문에 CSRF토큰의 처리가 필요하다.

register.jsp

//560p 첨부파일의 변경 처리
$(".uploadResult").on("click", "button", function(e) {
    
    console.log("첨부파일 삭제!");

    //561p 추가
    var targetFile = $(this).data("file");
    var type = $(this).data("type");

    var targetLi = $(this).closest("li");

    $.ajax({
        url: "/deleteFile",
        data: {fileName: targetFile, type:type},
        //X-CSRF-TOKEN 헤더 정보 추가하기 721p
        beforeSend: function(xhr) {
            xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
        },
        dataType:'text',
        type:'POST',
        success:function(result) {
            alert(result);

            targetLi.remove();
        }
    });//$.ajax
});

UploadController 수정

브라우저에서 로그인한 사용자만 업로드가 가능하지만 필요하면 서버 쪽에서 어노테이션을 이용해 업로드 시 보안을 확인한다.

UploadController

//517p AttachFileDTO 클래스 활용
//724p UploadController 보안확인
@PreAuthorize("isAuthenticated()")//첨부파일 등록은 외부에서 로그인한 사용자만 할 수 있도록 제한
@PostMapping(value = "/uploadAjaxAction", produces = MediaType.
		APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public ResponseEntity<List<AttachFileDTO>> uploadAjaxPost(MultipartFile[] uploadFile) {

//548p 서버에서 첨부파일의 삭제
//724p 로그인한 사용자만 첨부파일 삭제할 수 있게 한다.
@PreAuthorize("isAuthenticated()")
@PostMapping("/deleteFile")
@ResponseBody
public ResponseEntity<String> deleteFile(String fileName, String type) {

첨부파일 등록과 삭제는 외부에서 로그인한 사용자만 할 수 있도록 제한한다.

게시물 수정/삭제에서 첨부파일 처리

게시물의 수정 화면에서도 첨부파일은 추가되거나 삭제가 가능하므로 코드를 수정할 필요가 있다.

modify.jsp

// CSRF 토큰 값 변수로 선언하기 725p
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",
		//X-CSRF-TOKEN 헤더 정보 추가하기 721p
		beforeSend: function(xhr) {
			xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
		},
		dataType: "json",
		success: function(result) {
			console.log(result);
			showUploadResult(result); //업로드 결과 처리 함수
		}
	});//$.ajax
});

댓글 기능에서 Ajax

댓글의 경우 모든 동작이 Ajax를 통해서 이루어지기 때문에 화면에서도 수정되어야 하는 부분이 있고, 서버쪽에서도 변경될 부분이 있다. 우선 서버 쪽에서 ReplyController가 댓글에 대한 보안 원칙을 다음과 같이 설계한다.

  • 댓글 등록 : 로그인한 사용자만 댓글을 추가할 수 있다.

  • 댓글 수정 삭제 : 로그인한 사용자와 댓글 작성자의 아이디를 비교해서 같은 경우만 댓글을 수정/삭제 한다.

브라우저 쪽에서는 기존과 달라지는 부분은 다음과 같다.

  • 댓글 등록 : CSRF 토큰을 같이 전송하도록 수정한다.

  • 댓글 수정 삭제 : 기존의 댓글 삭제에는 댓글 번호만 처리했는데 서버쪽에서 사용할 것이기 때문에 댓글 작성자를 같이 전송한다.

댓글 등록

댓글 등록 처리는 get.jsp파일을 수정한다.

get.jsp

var modal = $('.modal');
var modalInputReply = modal.find("input[name='reply']");
var modalInputReplyer = modal.find("input[name='replyer']");
var modalInputReplyDate = modal.find("input[name='replyDate']");

var modalModBtn = $("#modalModBtn");
var modalRemoveBtn = $("#modalRemoveBtn");
var modalRegisterBtn = $("#modalRegisterBtn");

//댓글 등록 스프링 시큐리티 처리(727p)
var replyer = null;

<sec:authorize access="isAuthenticated()">
replyer = '<sec:authentication property="principal.username"/>';
</sec:authorize>

var csrfHeaderName = "${_csrf.headerName}";
var csrfTokenValue = "${_csrf.token}";

자바스크립트 <sec:authorize>태그를 이용해 스프링 시큐리티의 usernmaereplyer라는 변수로 처리할 수 있게 한다. 에러를 뱉어내지만 실제로는 정상적으로 동작한다.

가장 중요한 CSRF토큰에 대한 처리는 csrfHeaderName변수와 csrfTokenValue변수를 선언해서 처리한다. 댓글을 보여주는 모달창에는 현재 로그인한 사용자의 이름으로 replyer항목이 고정되도록 수정한다.

$("#addReplyBtn").click(function(e) {
    log('addReplyBtn 클릭');
    modal.find("input").val('');
    //댓글작성자 로그인한 사용자로 고정하기 728p
    //클래스가modal인 태그의 하위요소를 찾아서 값을 principal.username으로 설정한다.
    modal.find("input[name='replyer']").val(replyer);
    modalInputReplyDate.closest("div").hide();//가장 가까운(closest) div태그를 찾아서 그것을 숨겨라
    modal.find("button[id != 'modalCloseBtn']").hide();

    modalRegisterBtn.show();

    $(".modal").modal("show");
})

제이쿼리를 이용해서 AjaxCSRF토큰을 전송하는 방식은 첨부파일의 경우 beforeSend를 이용해서 처리했지만, 기본 설정으로 지정해서 사용하는 것이 더 편하기 때문에 아래 코드를 사용한다.

get.jsp

//beforeSend 기본설정 지정
$(document).ajaxSend(function(e, xhr, options) {
    xhr.setRequestHeader(csrfHeaderName, csrfTokenValue);
});

modalRegisterBtn.on("click",function(e){
  ...생략...
});

modalModBtn.on("click",function(e){
  ...생략...
});
  
modalRemoveBtn.on("click",function(e){
  ...생략...
});

ajaxSend()를 이용한 코드는 모든 Ajax전송 시 CSRF토큰을 같이 전송하도록 세팅되기 때문에 매번 Ajax사용 시 beforeSend를 호출해야 하는 번거로움을 줄일 수 있다. ReplyController에서는 댓글 등록이 로그인한 사용자인지를 확인하도록 한다.

ReplyController

/*393p 등록작업 및 테스트*/
//728p 댓글등록이 로그인한 사용자인지 확인
@PreAuthorize("isAuthenticated()")
@PostMapping(value = "/new",
		consumes = "application/json",
		produces = { MediaType.TEXT_PLAIN_VALUE })
public ResponseEntity<String> create(@RequestBody ReplyVO vo) {
	
	System.out.println("ReplyVO : " + vo);
	
	int insertCount = service.register(vo);
	System.out.println("Reply INSERT COUNT : " + insertCount);
	
	return insertCount == 1
			? new ResponseEntity<>("success",HttpStatus.OK)
			: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}

브라우저에 새로운 댓글을 추가하려고 하면 아래와 같이 댓글 작성자(replyer)는 고정된 형태로 보이게 되고, 전송 시 CSRF 토큰이 같이 전송됩니다.

댓글 삭제

댓글 삭제는 자신이 작성한 댓글만 삭제 가능해야한다. 화면에서 자바스크립트를 이용해서 모달창의 댓글 작성자 정보와 현재 로그인한 사용자가 같은지 비교해서 같은 경우만 Ajax로 댓글을 삭제하도록 한다. 만약 자신이 작성자 댓글이 아닌 경우나 로그인하지 않는 경우 삭제할 수 없도록 제한한다.

댓글의 수정과 삭제는 처이해야 하는 작업이 많다 우선 기존과 다르게 댓글 작성자 항목을 같이 전송해야 하기 때문에 코드 수정이 필요하다. 댓글 삭제시 기존 rno와 같이 댓글 번호만 전송했지만, 원래 댓글의 작성자를 같이 전송하도록 수정한다.

get.jsp

//댓글 삭제
modalRemoveBtn.click(function(e) {
    log("댓글삭제 버튼클릭");
    
    var rno = modal.data("rno");

    //730p 로그인한 사용자만 댓글 삭제 처리
    log("rno값: " + rno);
    log("Replyer: " + replyer);

    if(!replyer) {
        alert("로그인 후 삭제가 가능하다.");
        modal.modal("hide");
        return;
    }

    var originalReplyer = modalInputReply.val();

    log("오리지널 작성자 : " + originalReplyer); //댓글의 원래 작성자

    if(replyer != originalReplyer) {
        alert("본인이 작성한 댓글만 삭제가 가능해요.");
        modal.modal("hide");
        return;
    }

    replyService.remove(rno, originalReplyer, function(result){
        alert(result);
        modal.modal("hide");
        showList(pageNum);
    });
    // /.730p
});

originalReplayer가 추가된 후 resources폴더 내의 js/reply2.js에서 rnoreplyer를 같이 전송하도록 수정한다.

reply2.js

//댓글 삭제
//731p
function remove(rno, replyer, callback, error) {
	$.ajax({
		type : 'delete',
		url : '/replies/' + rno,
		//731p
		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(callback) {
				error(er);
			}
		}
	});
}

reply2.jsremove는 기존과 달리 replyer를 추가로 파라미터로 지정했고, 데이터 전송 시 JSON타입으로 전송하도록 변경한다. ReplyControllerJSON으로 전송되는 데이터를 처리하도록 수정한다.

ReplyController

//732p JSON타입으로 받는 데이터 처리
@PreAuthorize("principal.username == #vo.replyer")
@DeleteMapping(value = "/{rno}")
public ResponseEntity<String> remove(@RequestBody ReplyVO vo, 
	@PathVariable("rno") Long rno) {
	
	System.out.println("remove : " + rno);
	
	System.out.println("replyer : " + vo.getReplyer());
	
	return service.remove(rno) == 1
			? new ResponseEntity<>("success",HttpStatus.OK)
			: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}

기존 코드와 비교하면 어노테이션이 추가됐고 파라미터가 @RequestBody가 적용되어서 JSON으로된 데이터를 받도록 수정했다.

브라우저를 통해 댓글이 정상적으로 삭제되는지 확인한다. 브라우저에는 JSON데이터를 전송하고 ReplyController에서는 로그를 통해서 정상적으로 동작하는지 확인한다.

콘솔창

댓글 수정

댓글 수정은 기존에는 댓글의 내용만 전송했지만, 댓글의 작성자 또한 같이 전송되도록 수정한다.

get.jsp

//댓글 수정
modalModBtn.click(function(e) {
    log("댓글수정 버튼클릭");
    
    //로그인한 사용자만 댓글 수정 처리
    var originalReplyer = modalInputReplyer.val();

    var reply = {
        rno:modal.data("rno"), 
        reply:modalInputReply.val(), 
        replyer:modalInputReplyer.val()};

    if(!replyer){
        alert("로그인 후 수정이 가능합니다.");
        modal.modal("hide");
        return;
    }

    log("오리지널 작성자:  " + originalReplyer);

    if(replyer != originalReplyer) {
        alert("본인이 작성한 댓글만 수정 가능해요");
        modal.modal("hide");
        return;
    }

    replyService.update(reply, function(result) {

        alert(result);
        modal.modal("hide");
        //showList(1);
        //댓글 수정 442p
        showList(pageNum);
    });
});

ReplyController에서는 어노테이션 처리를 추가한다.

ReplyController

/*397p 댓글 수정*/
@PreAuthorize("principal.username == #vo.replyer")
@RequestMapping(method = { RequestMethod.PUT, RequestMethod.PATCH },
		value = "/{rno}",
		consumes = "application/json")
public ResponseEntity<String> modify(
		@RequestBody ReplyVO vo,
		@PathVariable("rno") Long rno) {
	vo.setRno(rno);
	System.out.println("파라미터rno값 : " + rno);
	System.out.println("vo객체의 rno : " + vo.getRno());
	System.out.println("vo객체의 댓글 : " + vo.getReply());
	
	return service.modify(vo) == 1
			? new ResponseEntity<>("success", HttpStatus.OK)
			: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); 
}

브라우저를 통해 실행할 때 JSON 데이터가 전송되는지 확인하고 서버에서 로그를 확인한다.


로그아웃 처리

header.jsp를 수정해서 로그인한 상태에서는 로그아웃 페이지로 이동할 수 있게 한다.

header.jsp

<li class="dropdown">
    <a class="dropdown-toggle" data-toggle="dropdown" href="#">
        <i class="fa fa-user fa-fw"></i> <i class="fa fa-caret-down"></i>
    </a>
    <ul class="dropdown-menu dropdown-user">
        <li><a href="#"><i class="fa fa-user fa-fw"></i>회원 정보</a>
        </li>
        <li><a href="#"><i class="fa fa-gear fa-fw"></i>설정</a>
        </li>
        <!-- 로그인 상태에서 로그아웃 하기 추가 735p -->
        <li class="divider"></li>
        <sec:authorize access="isAuthenticated()">
            <li>
                <a href="/customLogout"><i class="fa fa-sign-out fa-fw"></i>로그아웃</a>
            </li>
        </sec:authorize>
        <sec:authorize access="isAnonymous()">
            <li>
                <a href="/customLogin"><i class="fa fa-sign-out fa-fw"></i>로그인</a>
            </li>
        </sec:authorize>
        <!-- /.735p -->
    </ul>
    <!-- /.dropdown-user -->
</li>
<!-- /.dropdown -->


로그아웃 페이지

로그아웃 페이지는 customLogout.jsp로 생성하고, 로그인 페이지를 수정해서 /customLogout으로 이용한다.

customLogout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>로그아웃 페이지</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">
</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">로그아웃 페이지</h3>
	                </div>
	                <div class="panel-body">
	                    <form role="form" method="post" action="/customLogout">
	                        <fieldset>
	                            
	                            <a href="index.html" class="btn btn-lg btn-success btn-block">로그아웃</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>
</body>
<script>
$(".btn-success").on("click", function(e){
	console.log("로그아웃 버튼 클릭");
	e.preventDefault();
	$("form").submit();
});
</script>
<c:if test="${param.logout != null}">
    <script>
        $(document).ready(function() {
            alert("로그아웃하셨습니다.");
        });
    </script>
</c:if>
</html>

브라우저에서 로그아웃 시 자동으로 로그인 페이지로 이동하면서 경고창을 보여준다.

0개의 댓글

관련 채용 정보