- CSRF에 대하여 설명하시오.
- 스프링 시큐리티에서 CSRF를 방어하기 위해 적용 시키는 방법은?
- 아래를 적용해 보세요.
- csrf 를 enable 시
- 게시판 delete 를 ajax통신으로 구현하시오.
- 트랙잭션에 대하여 설명하시오.
- 아래가 rollback 하지 않는 이유는?
@Transactional //롤백 안함 public void transetionTest5() throws SQLException { log.info("transetionTest5() 테스트"); BoardVO boardVO = new BoardVO(); boardVO.setBcontent("트랜잭션5"); boardVO.setBname("트랜잭션5"); boardVO.setBtitle("트랜잭션5"); mapper.insertBoard(boardVO); throw new SQLException("RuntimeException for rollback"); }
- 게시판 구현에서 트랜잭션 처리 하시오.
- checked Exception 과 unchecked Exception 차이는?
Spring Security CSRF 토큰 사용법
- POST, UPDATE, DELETE에서만 적용됨.(GET방식 미적용)
-> CSRF를 막기 위해서 넣는 것
- 요청 때마다 즉시 랜덤 토큰을 뿌려서, 이를 확인하는 방식
-> 매번 value 값이 변하고, hidden으로 포함되어 들어감.
-> 공격자 입장에서는, 고정된 쿼리문만 전송하면 더 이상 명령이 작동되지 않음
해당 부분은, 기본적으로 CSRF 설정이 되어 있기 때문에 따로 해제되어 있지 않는 이상 자동으로 적용되고 있음.
-> 해제한다면, WebSecurityConfigurerAdapter를 구현하는 클래스 내 configure(HttpSecurity http) 함수에서 해제함.
@Override
protected void configure(HttpSecurity http) throws Exception {
// 우선 CSRF설정을 해제한다.
// 초기 개발시만 해주는게 좋다.
// http.csrf().disable();
}
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
HomeController 일부 : delete 처리를 하는 페이지에 연결시키기
@GetMapping("/csrf")
public String csrf(Model model) {
log.info("csrf()..");
return "/board/csrf_delete";
}
RestController
- ajax 처리 시 필요한 restcontroller
package edu.sejong.ex.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import edu.sejong.ex.service.BoardService;
import edu.sejong.ex.vo.BoardVO;
import lombok.extern.slf4j.Slf4j;
/*
//REST : Representational State Transfer
//하나의 URI가 하나의 고유한 리소스를 대표하도록 설계된 개념
*/
//http://localhost/spring02/list?bno=1 ==> url+파라미터
//http://localhost/spring02/list/1 ==> url
//RestController은 스프링 4.0부터 지원
//@Controller, @RestController 차이점은 메서드가 종료되면 화면전환의 유무
//@RestController 기존의 @Controller 와는 완전다른 기능임
@Slf4j
@RestController
@RequestMapping("/boards")
public class RestBoardController {
@Autowired
private BoardService boardService;
@GetMapping("/list")
public List<BoardVO> list(){
log.info("list()..");
return boardService.showList();
}
@GetMapping("/{bid}")
public BoardVO restContentView(BoardVO boardVO){
log.info("restContentView()..");
return boardService.showContent(boardVO.getBid());
}
// @DeleteMapping("/{bid}")
// public String restDelete(BoardVO boardVO){
// log.info("restDelete()..");
//
// boardService.remove(boardVO.getBid());
//
// return "Success";
// }
@DeleteMapping("/{bid}")
public ResponseEntity<String> rest_delete(BoardVO boardVO) {
ResponseEntity<String> entity = null;
log.info("rest_delete..");
try {
boardService.removeBoard(boardVO.getBid(), boardVO);
// 삭제가 성공하면 성공 상태메시지 저장
entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
// 삭제가 실패하면 실패 상태메시지 저장
entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
// 삭제 처리 HTTP 상태 메시지 리턴
return entity;
}
//게시글 등록
@PostMapping("/")
public ResponseEntity<String> restWrite(@RequestBody BoardVO boardVO) {
ResponseEntity<String> entity = null;
log.info("restWrite()");
try {
boardService.addBoard(boardVO);
// 삭제가 성공하면 성공 상태메시지 저장
entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
// 삭제가 실패하면 실패 상태메시지 저장
entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
// 삭제 처리 HTTP 상태 메시지 리턴
return entity;
}
//게시글 수정
@PutMapping("/{bid}")
public ResponseEntity<String> restModify(@RequestBody BoardVO boardVO) {
ResponseEntity<String> entity = null;
log.info("restModify()");
try {
boardService.modifyBoard(boardVO);
// 삭제가 성공하면 성공 상태메시지 저장
entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
// 삭제가 실패하면 실패 상태메시지 저장
entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
// 삭제 처리 HTTP 상태 메시지 리턴
return entity;
}
@GetMapping("/start")
public ModelAndView start(ModelAndView mv){
log.info("start()..");
mv.setViewName("/board/rest_list");
return mv;
}
}
delete 처리하는 부분 : csrf_delete.jsp
- 해당 코드는 CSRF ajax 처리를 연습하기 위한 파일이다.
view에서는 CSRF 토큰을 meta 태그로 받아오는 과정이 필요.
meta 태그는 문서의 메타 데이터(metadata)를 제공하며, 이는 브라우저나 검색 엔진 등이 문서를 해석하는 데 도움이 됨.
- meta 태그의 역할 중, 사용자가 정의한 메타 데이터를 설정하는 기능을 활용.
ajax 내 beforeSend 함수 활용
- beforeSend는 AJAX 요청이 전송되기 전에 호출되는 콜백 함수.
-> 이 콜백 함수를 사용하면 AJAX 요청을 보내기 전에 필요한 작업을 수행할 수 있음.
- AJAX 요청을 보내기 전에 HTTP 요청 헤더에 CSRF 토큰을 설정
-> 서버는 CSRF 토큰을 받아서 요청을 처리할 수 있게 됨.
<%@ 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="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<script type="text/javascript">
// ajax 요청시 사용할 csrf 글로벌 변수설정
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
</script>
<script type="text/javascript">
$(document).ready(function (){
function deleteBoard(id){
$.ajax({
type:"DELETE",
url: "/boards/" + id,
success: function(result){
console.log(result);
},
beforeSend : function(xhr){
//ajax호출 중 처리
//글로벌 변수로 설정한 csrf token 셋팅
xhr.setRequestHeader(header,token);
},
error: function(e){
console.log(e);
}
});
}
deleteBoard(1082);
});
</script>
</head>
<body>
</body>
</html>
@Transactional을 적용시킨 함수
@Transactional을 적용시키지 않으면, 함수 내 에러가 발생되지 않는 시점까지는 데이터 삽입, 수정, 삭제가 발생할 수 있음.
핵심 포인트 : @Transactional은 unchecked exception이 발생하면 롤백이 되지만, checked exception이 발생할 땐 롤백이 되지 않는다.
- checked exception : 메서드에서 에러가 발생할 수 있음을 명시적으로 선언하고, 호출하는 쪽에서 해당 예외를 처리.
- unchecked exception : 명시적인 예외 처리가 필요하지 않음.
-> checked exception은 '개발자가 직접 에러 처리를 해야 하기 때문'에, @Transactional은 해당 exception에 대해서는 롤백처리를 하지 않도록 설정한 것.
그렇기 때문에, checked exception의 경우에는 @Transactional만 사용하면 안된다.
rollback이 되는 클래스를 지정해주는 작업이 필요하다.
@Transactional(rollbackFor = Exception.class)
트랜잭션 활용방식에 관하여
게시판에서는, 대표적으로 댓글 작성 함수를 @Transactinal 처리해주면 될 것이다.
댓글 작성 함수 내에는 댓글에 들어가는 값을 변형시키는 함수와 댓글 내용 및 작성자 등이 들어가는 함수를 같이 쓰니까..
