스프링 시큐리티에서의 메소드 레벨 보안

박민주·2024년 3월 5일
0

스프링시큐리티

목록 보기
1/6

메소드레벨 보안 소개

Spring Security는 웹 애플리케이션에서 API 경로나 URL에 대한 권한을 부여하는 것 외에도 메소드 레벨의 보안을 제공합니다. 이를 통해 Java 메소드가 controller layer, service layer, repository layer 중 어디에 있더라도 강제로 권한을 부여할 수 있습니다.

메소드 레벨의 보안은 주로 두 가지 상황에서 사용됩니다. 첫째로는 호출 권한 부여로, 특정 메소드의 호출을 위해 권한을 설정하는데 사용됩니다. 두번째는 필터링 권한 부여로, 메소드가 처리한 데이터를 규칙에 따라 필터링하고 반환하는데 사용됩니다.

메소드 레벨 보안을 활용하기 위해선 @PreAuthorize, @PostAuthorize와 같은 주석을 사용합니다. 또한, @Secured, @RoleAllowed와 같은 주석들도 있지만 덜 강력합니다.

@PreAuthorize와 @PostAuthorize를 추천하는 이유는 강력하고 SpEL을 활용할 수 있기 때문입니다. 이러한 주석들을 사용하면 보다 유연한 권한 부여 규칙을 설정할 수 있습니다.

메소드 호출 권한 설명

@PreAuthorize 주석을 사용하여 메소드에 권한 관련 규칙이나 보안 요구 사항을 정의할 때, Spring Security 프레임워크는 유저 정보에 따른 보안 요구 사항이 충족되어야 해당 메소드가 호출될 수 있도록 합니다. 이를 위해 다양한 접근 방법을 제공하는데, 예를 들어 hasAuthority, hasRole 메소드를 사용할 수 있습니다. 또한, SpEL을 활용하여 보안 요구 사항을 정의할 수도 있습니다.

메소드 레벨의 보안 주석인 @PreAuthorize는 호출하기 전에 권한을 검증하고, @PostAuthorize는 메소드의 실행을 멈추지 않지만 실행이 완료된 후에 권한을 검증합니다. @PostAuthorize를 사용할 때는 메소드의 반환 객체가 특정 유저와 연관이 있는지 확인할 수 있습니다.

Spring Security 프레임워크는 Spring AOP를 사용하여 메소드 호출을 인터셉트하여 권한 관련 규칙을 실행합니다. 이를 통해 메소드 레벨의 보안이 효과적으로 적용될 수 있습니다.

요약하면, @PreAuthorize와 @PostAuthorize 주석을 사용하여 메소드 레벨의 보안을 설정하고, Spring Security와 Spring AOP를 통해 권한 검증이 실행됩니다. 이를 통해 애플리케이션의 보안을 강화할 수 있습니다.

@PreAuthorize와 @PostAuthorize의 사용법

우선 @PreAuthorize와 @PostAuthorize를 사용하기 위해서는 구성 클래스나 메인 어플리케이션 클래스에 @EnableMehotdSecurity 어노테이션을 적용해주면된다.

@SpringBootApplication
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class EazyBankBackendApplication {

	public static void main(String[] args) {
		SpringApplication.run(EazyBankBackendApplication.class, args);
	}

}
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ProjectSecurityConfig {
	내용...
}
  • prePostEnabled : PreAuthorize, PostAuthorize, PreFilter, PostFilter 어노테이션을 사용 가능하게 해준다.
  • securedEnabled : Secured 사용 가능
  • jsr250Enabled : RoleAllowed 사용 가능

예시

public interface LoanRepository extends JpaRepository<Loans, Long> {
    @PreAuthorize("hasRole('ROOT')")
    List<Loans> findByCustomerIdOrderByStartDtDesc(int customerId);

}

코드와 같이 Repossitory에 @PreAuthorize에 ROOT란 Role을 가진 유저만 접근할 수 있게 적용할 수 있다. ROOT란 권한이 없다면 메소드를 실행하지않아 값을 받아오지 못하게된다.

	@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;
        }
    }

브레이크포인트를 List<Loans> loans = ....에 두고 실행을 하면

원하는 값은 받아왔지만 ROOT권한이 없기에 view에 전달을 해주지 않는다.

권한 필터링

메소드 레벨 보안을 통해 메소드 호출을 제어하는 것 이외에 필터링 조건도 강제할 수가 있는데 이는 메소드로 받는 입력, 메소드에서 내보내는 출력에 모두 해당 됩니다.

받고 싶지 않은 특정 입력이나 규칙의 입력을 @PreFilter라는 주석을 이용해 필터링 할 수 있습니다.

saveContactInquiryDeatils 메소드를 사용할 때 contact 객체(object) 목록을 받게 될 것입니다. 여기서 contact객체의 contactName에 Test가 들어가있다면 그 객체는 받지 않는 예입니다.

하지만 놓쳐서는 안될 중요한 사실이 있습니다. @PreFilter 주석은 사용할 때마다 PreFilter하려는 메소드 입력은 반드시 컬렉션 인터페이스 유형이어야 한다는 점입니다.

예시

	@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;
    }

contactName에 Test가 들어간다면 필터링 한다는 예시입니다. view에서 Test로 작성하고 디버깅해보면


Test가 들어간 객체가 필터링되어 받지 않음을 확인 할 수 있습니다.
그렇다면 PostFilter는 어떨까요.

    @PostMapping("/contact")
    @PostFilter("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;
    }


Test가 들어갔음에도 객체를 받는다는것이 보입니다. 현재는 PostFilter 기준만 적용되어 있기 때문입니다. 이는 모든 비즈니스 로직은 실행되나
작성된 contact 속에서 UI 애플리케이션으로 돌아갈 때 해당 객체는 Test라는 이름을 갖고 있기 때문에 필터링 된다는 뜻입니다.

따라서 PostFilter 주석을 직접적으로 사용할 때는 메소드 입력이나 비즈니스 로직을 제어하는 것이 아닙니다.

그러므로 PreFilter와 PostFilter에 대해서 명확하게 알고 사용하는 것이 중요합니다.

profile
개발자 되고싶다..

0개의 댓글