spring/spring security

스프링 시큐리티 기본 개념공부 9. (메소드 레벨 보안이란?)

대기업 가고 싶은 공돌이 2024. 7. 18. 21:29

Section 10.

 

메소드 레벨 보안

  • Spring Security에서 메소드 레벨 보안은 기본적으로 비활성화 되어있다.
  • 메소드 레벨 보안의 활성화를 위해선 @EnableMethodSecurity라는 어노테이션을 사용해야한다.
  • 해당 어노테이션은 클래스 위에 선언하여 메소드레벨 보안을 활성화시킬 수 있다.
  • 해당 어노테이션 이외에도 다른 어노테이션들이 필요하다. 첫 번재 세트는 @preAuthorize, @postAuthorize이다.
    • 위의 어노테이션들을 사용하기 위해선 @EnableMethodSecurity 내부에서 (prePostEnabled =true)로 설정해야한다.
  • @Secured를 사용하기 위해선 securedEnabled =ture, @RoleAllowed를 사용하기 위해선 jsr250Enabled = true로 설정해야한다.
  • 메소드 레벨 보안은 리포지토리, 컨트롤러, 서비스 모두에 적용이 가능하다

preAuthoirze 예시

  • preAuthorize는 메소드가 호출되는 순간 조건을 비교하여, 조건에 부합하지 않는다면 메소드 호출 자체를 무효화 시킨다.
@preAuthorize("hasAuthority('VIEWLOANS')")
@preAuthorize("hasRole('ADMIN')")
@preAuthorize("hasRole('USER')")
@preAuthorize("# username == authentication.principal.username") //입력받은 유저네임과 인증객체에 저장 유저네임이 동일한지 확인하는 로직
public Loan getLoanDetails(String username){
    return loansRepository.loadLoanDetailsByUserName(username);
}
  • 위와같이 여러가지 조건을 명시했을 경우 위의 조건들 중 하나라도 충족하면 밑의 클래스를 실행시킬 수 있다 즉 or의 개념으로 동작한다.

postAuthorize 예시

  • postAuthorize는 메소드의 호출을 막지않고 실행시킨다 다만, 메소드의 리턴값을 검사하여 조건에 부합하지 않는다면 리턴을 취소시킨다.
@postAuthorize("returnObject.username == authentication.principal.username")
@postAuthorize("hasPermission(returnObject,'ADMIN')")
public Loan getLoanDetails(String username){
    return loansRepository.loadLoanDetailsByUserName(username);
}
  • 요구사항이 너무 복잡하면 PermissionEvaluator라는 인터페이스를 implements하여 로직을 작성하도록 하자.
  • 위의 메소드 레벨 보안은 Spring AOP에 의해 실현된다. 런타임 도중 메소드 호출을 인터셉트하여 주석 내의 조건을 먼저 검사하는것이다

메소드 레벨 보안 실습

PreAuthorize

  • Authorize는 권한을 체크하는 메소드레벨 보안이다.
  • 우선 자바 메인 어플리케이션에 주석을 달아줘야한다.
   @EnableMethodSecurity(prePostEnabled = true, securedEnabled =true, jsr250Enabled= true)
  public class MainApplication{
  • 리포지토리에 preAuthorize를 적용해 보겠다
@Repository
public interface LoanRepository extends CrudRepository<Loans, Long> {

  @preAuthorize("hasRole(ROOT)")
	List<Loans> findByCustomerIdOrderByStartDtDesc(int customerId);

}
  • 다음과 같이 findByCustomerIdOrderByStartDtDesc 해당 메소드를 사용하기 위해선 ROOT 권한이 있어야 한다.

PostAuthorize

  • 이번엔 컨트롤러에 PostAuthorize를 적용해 보겠다.
    @GetMapping("/myLoans")
    @PostAuthorize("hasRole(ROOT)")
    public List<Loans> getLoanDetails(@RequestParam int id) {
        List<Loans> loans = loanRepository.findByCustomerIdOrderByStartDtDesc(id);
        if (loans != null ) {
            return loans;
        }else {
            return null;
        }
    }
  • 객체가 반환될 때 엔드유저의 권한을 체크하는 역할을 수행한다.
  • 메소드 내부의 로직은 모두 실행이 되지만 객체의 반환은 이뤄지지 않는 것을 확인할 수 있다.

preFilter

  • preFilter는 매개변수의 조건을 확인하는데 쓰인다.
  • 그러나 제약조건이 있는데, 오로지 매개변수가 컬렉션일때만 사용가능하다. (List, set, map)
  • preFilter("filterObject.contactName != 'Test'")와 같이 컬렉션 객체중 contactName이 Test인 것을 거르는 용도로 사용될 수 있다.
  • postFilter는 반환 객체를 확인하는 용도로 사용된다.
    • postFilter는 반환 객체의 유형이 컬렉션이어야 한다.
  • 만약 총 10개의 node중 2개가 조건에 부합하지 않는다면 8개의 node만 반환될 것이다.
  • 고객에게 문의가 들어왔을때 고객의 이름에 test라면 문의를 데이터베이스에 저장하지 않는 로직을 작성해보겠다.
    @PostMapping("/contact")
    @PreFilter("filterObject.contactName != 'Test'")
    public List<Contact> saveContactInquiryDetails(@RequestBody List<Contact> contacts) {
        Contact contact = contacts.get(0);
        contact.setContactId(getServiceReqNumber());
        contact.setCreateDt(new Date(System.currentTimeMillis()));
        contact = contactRepository.save(contact);
        List<Contact> returnContacts = new ArrayList<>();
        returnContacts.add(contact);
        return returnContacts;
    }
  • 다음과 같이 preFilter를 통해 Test 이름을 갖고있는 객체를 거를 수 있다.
  • 만약 Test 이름을 가진 node가 List에 포함돼있다면 해당 node만 걸러지고 나머지 node들만이 들어오게된다.