개요
Spring Security는 기본적으로 Servlet Filter를 활용해 요청 권한을 확인하는 기능을 제공한다. 하지만, 이 방식 외에도 실행되는 메서드 단위로 권한을 확인할 수 있는 기능을 제공하는 것을 알게 되었고, 이 글을 통해 해당 기능을 설명하고자 한다.
Method Security 란?
Spring Security는 요청 레벨에서의 권한 확인 뿐만 아니라, 메서드 레벨에서도 권한을 확인할 수 있는 기능을 지원한다. 해당 기능을 특정 메서드에 적용하면 Spring Security에서 설정한 권한에 맞는 사용자만 해당 메서드에 접근할 수 있게 된다.
설정
메서드 레벨에서 권한을 확인하기 위해서는 먼저 아무 @Configuration이 붙은 클래스에 @EnableMethodSecurity 어노테이션을 추가해 method security 사용을 활성화시켜야 한다.
@EnableMethodSecurity // 추가
@Configuration
public class SecurityConfig {
...
}
이렇게 하면 Method Security를 위한 어노테이션들(@PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter)을 사용할 수 있게 된다.
참고로, Spring Boot Starter Security는 해당 기능을 자동으로 설정해주지 않으니, 위와 같이 직접 설정해주어야 한다.
적용 방법
설정이 끝난 후, 아래와 같이 메서드에 어노테이션을 붙여주면 바로 해당 메서드에 대한 권한 확인이 진행된다.
@PreAuthorize("hasRole('ADMIN')")
public void adminMethod() {
...
}
Method Security 어노테이션
Spring Security는 Method Security를 적용을 위해 여러 어노테이션들을 제공한다.
@PreAuthorize
이 어노테이션은 타겟 메서드에 접근하기 전에 권한을 확인 후, 해당 메서드 접근 가능 여부를 판단한다는 것을 나타낸다.
@PreAuthorize("hasRole('ADMIN')")
public void adminMethod() {
...
}
위와 같이 적용하면, adminMethod는 ROLE_ADMIN 권한을 가진 사용자만 접근이 가능하게 된다. 어노테이션 안에 Spring Expression Language (SpEL)을 사용하여 권한을 확인하는 방법을 지정해야 한다.
참고로, @PreAuthorize 뿐만 아니라, Method Security의 모든 어노테이션들도 이와 같이 SpEL을 사용해야 한다.
@PostAuthorize
이 어노테이션은 @PreAuthoize와 적용 시점이 다르다. @PreAuthorize는 메서드 호출 전에 적용이 된다면, @PostAuthorize는 메서드가 호출된 후에 권한 확인이 적용된다. 따라서, 메서드가 값을 반환하기 전에, 해당 값이 사용자의 정보와 관련되어 있는지 확인하는 기능과 같이, 더 확장된 권한 확인 방식을 제공한다.
@Component
public class BankService {
@PostAuthorize("returnObject.owner == authentication.name")
public Account readAccount(Long id) {
// ... 사용자의 `Account`일 경우에만 메서드가 값을 반환
}
}
위와 같이 @PostAuthorize 안에 returnObject(타겟 메서드의 반환 값)의 owner와 authentication의 name을 비교하는 표현식을 적어 true일 경우에만 메서드가 값을 반환할 수 있도록 권한 확인을 진행하는 것을 볼 수 있다.
@PreFilter
이 어노테이션은 타겟 메서드의 파라미터로 들어오는 다중 값을 미리 확인한다는 것을 나타낸다.
@Component
public class BankService {
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Account... accounts) {
// ... `accounts` 파라미터로는 사용자의 `Account`만 넘어올 수 있다.
return updated;
}
}
위와 같이 @PreFilter에 filterObject(타겟 메서드의 파라미터 값)의 owner와 authentication의 name을 비교하는 표현식을 적어 true일 경우에만 파라미터로 값이 전달될 수 있도록 설정할 수 있다.
또한, @PreFilter는 아래와 같이 Array, Collection, Map, Stream(열려있는 경우에만) 등 파라미터로 사용되는 여러 타입을 지원한다.
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Account[] accounts)
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Collection<Account> accounts)
@PreFilter("filterObject.value.owner == authentication.name")
public Collection<Account> updateAccounts(Map<String, Account> accounts)
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Stream<Account> accounts)
@PostFilter
타겟 메서드의 반환 값을 필터링한다는 것을 나타내는 어노테이션이다.
@Component
public class BankService {
@PostFilter("filterObject.owner == authentication.name")
public Collection<Account> readAccounts(String... ids) {
// ... 사용자의 account만 accounts에 담겨 반환
return accounts;
}
}
위의 코드와 같이 @PostFilter를 붙여 메서드의 반환 값인 accounts를 사용자의 Account만 담아 반환하게 만들 수 있다.
@PostFilter 또한 @PreFilter와 같이 여러 타입을 지원한다.
@PostFilter("filterObject.owner == authentication.name")
public Account[] readAccounts(String... ids)
@PostFilter("filterObject.value.owner == authentication.name")
public Map<String, Account> readAccounts(String... ids)
@PostFilter("filterObject.owner == authentication.name")
public Stream<Account> readAccounts(String... ids)
그 외의 어노테이션
위에서 설명한 4개의 어노테이션 외에도 권한 검증을 위한 다른 어노테이션들도 존재한다.
@Secured
Spring Security의 레거시 어노테이션이다. @PreAuthorize가 이를 대신하며, Spring Security 공식 문서에서도 @PreAuthorize를 사용하는 것을 권장한다.
@Secured 어노테이션을 사용하려면 아래와 같이 @EnableMethodSecurity에 추가 설정이 필요하다.
@EnableMethodSecurity(securedEnabled = true)
JSR-250 Annotations
Spring Security는 Java의 JSR-250 어노테이션도 지원한다. 이 역시, @PreAuthorize가 더 뛰어난 기능을 제공하기 때문에 Spring Security 공식 문서에서도 @PreAuthorize를 사용하는 것을 권장한다.
JSR-250 어노테이션을 사용하려면 아래와 같이 @EnableMethodSecurity에 추가 설정이 필요하다.
@EnableMethodSecurity(jsr250Enabled = true)
마무리
Spring Security의 Method Security 기능을 통해 메서드 레벨에서 권한을 확인할 수 있는 방법에 대해 알아보았다. 해당 기능을 잘 활용하면 admin만 호출이 가능한 API를 구현할 수 있을 것이다.
하지만, 개인적으로는 해당 기능을 사용할 경우 admin 전용 API를 확인하기 위해 해당 기능이 적용된 controller 클래스를 일일이 확인해야 한다는 불편함이 생길 수 있다. 따라서, 이런 경우에는 admin 전용 API만 포함되어 있는 controller 클래스를 별도로 생성하는 것이 더 좋은 선택이라고 생각한다.
참고
Method Security :: Spring Security
There are some scenarios where you may not wish to throw an AuthorizationDeniedException when a method is invoked without the required permissions. Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases
docs.spring.io
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] AuthenticationFailureHandler에 대하여 (0) | 2024.03.19 |
---|---|
[Spring Security] AuthenticationSuccessHandler에 대하여 (0) | 2024.03.15 |