import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import edu.ex.mapper.BoardMapper;
import edu.ex.vo.BoardVO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class TransactionTestService {
@Autowired
private BoardMapper mapper;
// Test1 : 트랜잭션 내에서 두 개의 삽입 작업을 시뮬레이션함. 첫 번째 삽입이 성공하면 롤백안함.
@Transactional
public void TransactionTest1() {
log.info("TransactionTest1()..");
BoardVO boardVO = new BoardVO();
boardVO.setBcontent("트랜잭션1");
boardVO.setBname("트랜잭션1");
boardVO.setBtitle("트랜잭션1");
mapper.insertBoard(boardVO);
BoardVO boardVO2 = new BoardVO();
boardVO2.setBcontent("트랜잭션2");
boardVO2.setBname("트랜잭션2");
boardVO2.setBtitle("트랜잭션2");
mapper.insertBoard(boardVO2);
}
// Test2 : 트랜잭션 내에서 두 개의 삽입 작업을 시도하지만, 두 번째 삽입 작업 직전에 일부러 예외를 발생시켜 롤백될 수 있도록 함.
public void TransactionTest2() {
log.info("TransactionTest2()..");
BoardVO boardVO = new BoardVO();
boardVO.setBcontent("트랜잭션1");
boardVO.setBname("트랜잭션1");
boardVO.setBtitle("트랜잭션1");
mapper.insertBoard(boardVO);
BoardVO boardVO2 = new BoardVO();
boardVO2.setBcontent("트랜잭션2");
boardVO2.setBname("트랜잭션2");
boardVO2.setBtitle("트랜잭션2");
// 일부러 트랜잭션을 위한 테스트로 null 값 셋팅
boardVO2 = null;
mapper.insertBoard(boardVO2);
}
// Test3 : 트랜잭션 내에서 두 개의 삽입 작업을 시도하지만,
// 두 번째 삽입 작업 직전에 일부러 예외를 발생시켜 롤백될 수 있도록 함.
// 이 메서드에서는 @Transactional 어노테이션이 메서드에 적용함.
@Transactional
public void TransactionTest3() {
log.info("TransactionTest3()..");
BoardVO boardVO = new BoardVO();
boardVO.setBcontent("트랜잭션3");
boardVO.setBname("트랜잭션3");
boardVO.setBtitle("트랜잭션3");
mapper.insertBoard(boardVO);
BoardVO boardVO2 = new BoardVO();
boardVO2.setBcontent("트랜잭션3");
boardVO2.setBname("트랜잭션3");
boardVO2.setBtitle("트랜잭션3");
boardVO2 = null;
mapper.insertBoard(boardVO2);
}
// Test4 : RuntimeException을 발생시켜 트랜잭션을 롤백하는 경우를 시뮬레이션함.
// 트랜잭션 내에서 데이터를 삽입한 후, 특정 조건에서 의도적으로 런타임 예외를 발생시킴.
// 이러한 경우 트랜잭션은 롤백되며, 이전에 삽입한 데이터도 롤백됨.
@Transactional
public void TransactionTest4() {
log.info("TransactionTest4()..");
BoardVO boardVO = new BoardVO();
boardVO.setBcontent("트랜잭션4");
boardVO.setBname("트랜잭션4");
boardVO.setBtitle("트랜잭션4");
mapper.insertBoard(boardVO);
throw new RuntimeException("RuntimeException for rollback");
}
// Test5 : SQLException을 발생시켜도 롤백되지 않는 경우를 시뮬레이션함.
// 트랜잭션 내에서 데이터를 삽입한 후, 특정 조건에서 의도적으로 SQL 예외를 발생시킴.
// 이 예외는 rollbackFor 옵션에 명시되지 않았으므로 롤백되지 않고, 데이터베이스에는 삽입된 데이터가 남게 됨.
// CheckedException 테스트(롤백안함)
@Transactional
public void TransactionTest5() throws SQLException {
log.info("TransactionTest5()..");
BoardVO boardVO = new BoardVO();
boardVO.setBcontent("트랜잭션5");
boardVO.setBname("트랜잭션5");
boardVO.setBtitle("트랜잭션5");
mapper.insertBoard(boardVO);
throw new SQLException("SQLException for rollback");
}
// Test6 : @Transactional의 rollbackFor 옵션을 이용하면 Rollback이 되는 클래스를 지정가능함.
// Exception예외로 롤백을 하려면 @Transactional(rollbackFor = Exception.class)
// 여러개의 예외를 지정할 하려면 @Transactional(rollbackFro = {RuntimeException.class, Exception.class})
// @Transactional 어노테이션의 rollbackFor 옵션을 사용하여 SQLException이 발생했을 때 트랜잭션을 롤백하는 경우를 시뮬레이션함.
// SQLException이 발생하면 해당 트랜잭션은 롤백되며, 이전에 삽입한 데이터도 롤백.
@Transactional(rollbackFor = Exception.class)
public void TransactionTest6() throws SQLException {
log.info("TransactionTest6()..");
BoardVO boardVO = new BoardVO();
boardVO.setBcontent("트랜잭션5");
boardVO.setBname("트랜잭션5");
boardVO.setBtitle("트랜잭션5");
mapper.insertBoard(boardVO);
throw new SQLException("SQLException for rollback");
}
// Test7 : @Transactional 어노테이션의 rollbackFor 옵션을 사용하여 SQLException이 발생했을 때 트랜잭션을 롤백하는 경우를 시뮬레이션함.
// SQLException이 발생하면 해당 트랜잭션은 롤백되며, 이전에 삽입한 데이터도 롤백됨.
// 이전과는 달리 SQLException에 대한 롤백이 명시적으로 지정.
@Transactional(rollbackFor = SQLException.class)
public void TransactionTest7() throws SQLException {
log.info("TransactionTest7()..");
BoardVO boardVO = new BoardVO();
boardVO.setBcontent("트랜잭션7");
boardVO.setBname("트랜잭션7");
boardVO.setBtitle("트랜잭션7");
mapper.insertBoard(boardVO);
throw new SQLException("SQLException for rollback");
}
}
스프링 시큐리티에서 POST 방식을 이용하는 경우 기본적으로 CSRF 토큰이라는 것을 이용하게 된다. 별도의 설정이 없다면 스프링 시큐리티가 적용된 사이트의 모든 POST 방식에는 CSRF 토큰이 사용되는데 '사이트간 위조 방지'를 목적으로 특정한 값의 토큰을 사용하는 방식이다.
CSRF 공격은 '사이트간 요청 위조'라고 번역될 수 있다. 서버에서 받아들이는 정보가 특별히 사전 조건을 검증하지 않는다는 단점을 이용하는 공격이다.
예를 들어 A라는 사이트가 존재한다고 가정하고, A 사이트에는 특정 사용자의 등급을 변경하는 URI가 존재하고 파라미터가 필요하다는 것을 공격자가 알았을 때, 공격자는 A 사이트의 관리자(피해자)가 자주 방문하는 B 사이트에 <img> 태그나 <form> 태그를 이용해서 위의 URI를 추가한 게시물을 작성한다. A 사이트의 관리자(피해자)는 자신이 평상시 방문하던 B 사이트를 방문하게 되고 공격자가 작성한 게시물을 보게 된다. 이때 태그 등에 사용된 URI가 호출되고 서버에서 로그인한 관리자의 요청에 의해서 공격자는 admin 등급의 사용자로 변경된다. A 사이트 관리자는 자신이 관리하던 A 사이트에 로그인이 되어 있는 상태라면 A 사이트의 서버 입장에서는 로그인한 사용자의 정상적인 요청으로 해석된다. CSRF 공격은 서버에서 받아들이는 요청을 해석하고 처리할 때 어떤 출처에서 호출이 징행되엇는지 따지지 않기 때문에 생기는 허점을 노리는 공격 방식이다.
CSRF는 현실적으로 하나의 사이트 내에서도 가능하다. 공격을 막기 위해서는 여러 방식이 존재한다.
1) CSRF 공격 자체가 사용자의 요청에 대한 출처를 검사하지 않아서 생기는 허점이기 때문에 사용자의 요청에 대한 출처를 의미하는 referer 헤더를 체크하거나 2) REST 방식에서 사용되는 PUT, DELETE와 같은 방식을 이용하는 것이 있다.
CSRF 토큰
CSRF 토큰은 사용자가 임의로 변하는 특정한 토큰값을 서버에서 체크하는 방식이다. 서버에서 브라우저에 데이터를 전송할 때 CSRF 토큰을 같이 전송한다. 사용자가 POST 방식 등으로 특정한 작업을 할 때는 브라우저에서 전송된 CSRF 토큰의 값과 서버가 보관하고 있는 토큰의 값을 비교한다. 만일 CSRF 토큰 값이 다르다면 작업을 처리하지 않는 방식이다. 서버에서 생성하는 토큰은 일반적으로 난수를 생성해서 공격자가 패턴을 찾을 수 없도록 한다. 아무튼 공격자 입장에서는 CSRF 공격을 하려면 변경되는 CSRF 토큰의 값을 알아야만 하기 때문에 고정된 내용의 태그 등을 이용할 수 없게 된다.