[B -2-57] 스프링 시큐리티 4

2019. 10. 11. 19:02Project B (SPMS)/Project B 파트7

반응형

게시글의 수정 및 삭제

 

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

 

게시글의 수정과 삭제에서 신경쓰이는 부분은 게시글의 수정과 삭제현재 로그인한 사용자와 게시글의 작성자가 동일한 경우에만 할 수 있다는 것이다. 

 

이 처리를 과거에는 인터셉터로 처리했지만, @PreAuthorize의 경우에는 표현식으로 처리 가능하다.

 


views/board

modify.jsp

 

...더보기
<%@ 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"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>

<%@ include file="../includes/header.jsp"%>
<div class="row">
	<div class="col-lg-12">
		<h1 class="page-header">게시판 수정</h1>
	</div>
	<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
	<div class="col-lg-12">
		<div class="panel panel-default">
			<div class="panel-heading">게시글 수정 페이지</div>
			
			<!-- /.panel-heading -->
			<div class="panel-body">
				<form role="form" action="/board/modify" method="post">
					<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token }">
					
					<!-- 히든 파라미터 기능 시작 -->
					<input type="hidden" name="pageNum" value='<c:out value="${cri.pageNum}"/>'>
					<input type="hidden" name="amount" value='<c:out value="${cri.amount}"/>'>
					<!-- 히든 파라미터 기능 종료 -->
					<!-- Criteria의 키워드와 타입에 대한 처리 시작 -->
					<input type='hidden' name='keyword' value='<c:out value="${cri.keyword}"/>'>
					<input type='hidden' name='type' value='<c:out value="${cri.type}"/>'> 
					<!-- Criteria의 키워드와 타입에 대한 처리 종료 -->

					<div class="form-group">
						<label>글 번호</label>
						<input class="form-control" name="bno" value='<c:out value="${board.bno }"/>' readonly="readonly">
					</div>
					
					<div class="form-group">
					  <label>등록일자</label>
					  <input class="form-control" name='regDate'
					    value='<fmt:formatDate pattern = "yyyy/MM/dd" value = "${board.regdate}" />'  readonly="readonly">            
					</div>
					
					<div class="form-group">
					  <label>수정일자</label> 
					  <input class="form-control" name='updateDate'
					    value='<fmt:formatDate pattern = "yyyy/MM/dd" value = "${board.updateDate}" />'  readonly="readonly">            
					</div>
					
					<div class="form-group">
						<label>작성자</label>
						<input class="form-control" name="writer" value='<c:out value="${board.writer }"/>' readonly="readonly">
					</div>
					<div class="form-group">
						<label>제목</label>
						<input class="form-control" name="title" value='<c:out value="${board.title }"/>'>
					</div>
					<div class="form-group">
						<label>내용</label>
						<textarea class="form-control" rows="10" name="content"><c:out value="${board.content}"/></textarea>
					</div>
						<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>
						<button type="submit" data-oper='list' class="btn btn-info">목록</button>
				</form>
			</div>
			<!-- /.panel-body -->
		</div>
		<!-- /.panel -->
	</div>
	<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
</div>

<script type="text/javascript">
$().ready(function() {

	  var formObj = $("form");

	  $('button').on("click", function(e){
	    
	    e.preventDefault(); 
	    
	    var operation = $(this).data("oper");
	    
	    console.log(operation);
	    
	    if(operation === 'remove'){
	      formObj.attr("action", "/board/remove");
	      
	    }else if(operation === 'list'){
	      //move to list
	      formObj.attr("action", "/board/list").attr("method","get");
	      
	      var pageNumTag = $("input[name='pageNum']").clone();
	      var amountTag = $("input[name='amount']").clone();    
	      
	      // 키워드와 타입 추가
	      var keywordTag = $("input[name='keyword']").clone();   
	      var typeTag = $("input[name='type']").clone();   
	      
	      formObj.empty();
	      
	      formObj.append(pageNumTag);
	      formObj.append(amountTag);
	      formObj.append(keywordTag);
	      formObj.append(typeTag);
	    }
	    
	    formObj.submit();
	  });
});
</script>


<!-- /#page-wrapper -->
<%@ include file="../includes/footer.jsp"%>

우선 상단에 스프링 시큐리티 태그 라이브러리를 쓸 수 있게 설정하고, POST 방식으로 처리되는 부분이므로 CSRF 토큰을 추가한다.

 

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

 

 


src/main/java

com.spms.controller

BoardController.java

 

...더보기
package com.spms.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.spms.domain.BoardVO;
import com.spms.domain.Criteria;
import com.spms.domain.PageDTO;
import com.spms.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {

	private BoardService boardService;

	@GetMapping("/list")
	public void list(Criteria cri, Model model) {
		log.info("list" + cri);
		boardService.getList(cri).forEach(board -> log.info(board));
		model.addAttribute("list", boardService.getList(cri));
		
		int total = boardService.getTotal(cri); 
		log.info("total : " + total);
		model.addAttribute("pageMaker", new PageDTO(cri, total));
	}
	
	@GetMapping("/register")
	@PreAuthorize("isAuthenticated()")
	public void register() {

	}

	@PostMapping("/register")
	@PreAuthorize("isAuthenticated()")
	public String register(BoardVO board, RedirectAttributes rttr) {
		
		log.info("register : " + board);
		boardService.register(board);
		rttr.addFlashAttribute("result", board.getBno());
		return "redirect:/board/list";
	}

	@GetMapping({ "/get", "/modify" })
	public void get(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, Model model) {
		log.info("/get or modify");
		model.addAttribute("board", boardService.get(bno));
	}

	@PostMapping("/modify")
	@PreAuthorize("principal.username == #board.writer")
	public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
		log.info("modify:" + board);

		if (boardService.modify(board)) {
			rttr.addFlashAttribute("result", "success");
		}
		
		return "redirect:/board/list" + cri.getListLink();
	}
	
	@PostMapping("/remove")
	@PreAuthorize("principal.username == #writer")
	public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr, String writer) {
		log.info("remove..." + bno);
		if (boardService.remove(bno)) {
			rttr.addFlashAttribute("result", "success");
		}
		
		return "redirect:/board/list" + cri.getListLink();
	}

}

 

BoardController에서는 메서드를 실행하기 전에 로그인한 사용자와 현재 파라미터로 전달되는 작성자가 일치하는지 체크한다. 

@PreAuthorize의 경우 문자열로 표현식을 지정할 수 있는데, 이 때 컨트롤러에 전달되는 파라미터를 같이 쓸 수 있으므로 유용하다.

 

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

 

기존과 달라진 부분은 파라미터로 writer가 추가된 부분과 해당 파라미터를 @PreAuthorize에서 #writer를 이용해서 체크한 부분이다.

 

게시글의 수정은 파라미터로 Board 타입의 객체를 받도록 설계되어있다.

반응형