Spring Security - CSRF / transaction

S.Sun·2024년 4월 25일

스프링

목록 보기
15/17

질문 내용

  1. CSRF에 대하여 설명하시오.
  2. 스프링 시큐리티에서 CSRF를 방어하기 위해 적용 시키는 방법은?
  3. 아래를 적용해 보세요.
  • csrf 를 enable 시
  • 게시판 delete 를 ajax통신으로 구현하시오.
  1. 트랙잭션에 대하여 설명하시오.
  2. 아래가 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");
	}
  1. 게시판 구현에서 트랜잭션 처리 하시오.
  2. checked Exception 과 unchecked Exception 차이는?

개인 작성

  • CSRF
    - Cross-site request forgery
    - 사이트 간 위조 요청
    - 서버에서 받아들이는 정보가 특별히 사전 조건을 검증하지 않는다는 단점을 이용하는 공격
    - 사용자가 로그인되어 있는 상태여야 함(관리자로 인증 및 권한이 부여된 사용자)
    -> 해커가 태그가 들어간 코드가 담긴 메일을 보냄.
    -> 사용자가 메일을 열 때, 이미지 파일을 받아오기 위해 URL이 열림
    -> 사용자의 브라우저는 사용자의 인증 정보를 포함한 요청을 자동으로 생성하고, 해커가 제공한 요청을 실행
    -> 사용자가 로그인한 상태이므로, 웹 애플리케이션에서 유효한 것처럼 처리됨
    - 사례로, 2008년 옥션에서 발생한 개인정보 해킹 사건(1800만명 가량)이 있다.
  • 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();
    }
  • url를 통해 이동되는 페이지 중, CSRF 설정이 필요한 페이지라면 다음 문구를 form 태그에 추가해줘야 한다.
    -> 이 문구를 일일히 적어줘야 하기 때문에, form 태그 라이브러리를 추가하여 해당 폼 태그의 폼을 사용하게 되면 해당 문구를 적어주지 않아도 자동으로 처리해준다.
<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>
  • 트랜잭션
    - DB에 대한 개념
    - 어떤 일련의 작업(한번에 이루어져야 하는 작업 단위)
    - 모두 에러없이 끝나야 하며, 중간에 에러가 발생한다면 에러 발생 이전 시점까지 작업되었던 내용은 모두 원상복구되어야 함.
    -> 500 및 404 등의 에러 처리가 아니면, 에러 발생 시 DB로 하여금 원상복구하도록 DB에 요청하는 것
    - DB에서는 commit, rollback과 같은 원상복구하는 방법이 존재.
    - 대상 SQL 명령은 INSERT, UPDATE, DELETE가 해당됨

@Transactional을 적용시킨 함수

  • 소프트웨어적으로 함수에서 에러가 있으면, 롤백시키라는 의미를 담는 에너테이션
    -> 함수 내 로직에 에러가 발생하는 경우, 해당 함수의 로직을 모두 rollback시킨다.

@Transactional을 적용시키지 않으면, 함수 내 에러가 발생되지 않는 시점까지는 데이터 삽입, 수정, 삭제가 발생할 수 있음.

  • 이를 막기 위해, @Transactional를 적용시키는 것.

핵심 포인트 : @Transactional은 unchecked exception이 발생하면 롤백이 되지만, checked exception이 발생할 땐 롤백이 되지 않는다.
- checked exception : 메서드에서 에러가 발생할 수 있음을 명시적으로 선언하고, 호출하는 쪽에서 해당 예외를 처리.
- unchecked exception : 명시적인 예외 처리가 필요하지 않음.
-> checked exception은 '개발자가 직접 에러 처리를 해야 하기 때문'에, @Transactional은 해당 exception에 대해서는 롤백처리를 하지 않도록 설정한 것.

그렇기 때문에, checked exception의 경우에는 @Transactional만 사용하면 안된다.
rollback이 되는 클래스를 지정해주는 작업이 필요하다.

@Transactional(rollbackFor = Exception.class)

트랜잭션 활용방식에 관하여

  • 최대한 Spring Transaction Annotation 형식으로 트랜잭션을 구현해야 함.
    - 개발자들은 다른 방식을 잘 활용하려고 하지 않는다.
    - 그러므로, 될 수 있으면 annotation을 활용하여 transaction을 진행하는 게 좋다.
  • INSERT, UPDATE, DELETE 기본적으로 두 개 이상 들어가는 로직은 반드시 트랜잭션 처리해야 함.
  • 최대한 비즈니스 로직을 서비스단에서 만들기 때문에, @Transactional은 서비스단 함수에 붙일 수 있도록 프로그램을 짜는 것이 최선이다.

게시판에서는, 대표적으로 댓글 작성 함수를 @Transactinal 처리해주면 될 것이다.
댓글 작성 함수 내에는 댓글에 들어가는 값을 변형시키는 함수와 댓글 내용 및 작성자 등이 들어가는 함수를 같이 쓰니까..

  • checked exception : 메서드에서 에러가 발생할 수 있음을 명시적으로 선언하고, 호출하는 쪽에서 해당 예외를 처리.
  • unchecked exception : 명시적인 예외 처리가 필요하지 않음.

profile
두리둥둥

0개의 댓글