spring security method 권한(Ahuthorization) 설정

김준수·2023년 8월 5일
0
post-custom-banner

1. 소개

spring security에서 기본적으로 제공하는 권한 설정 방법은 2 가지가 있다.

  • Authorize HTTP Request
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
        .anyRequest().authenticated()
    )
  • Method Security
@Service
public class MyCustomerService {
    @PreAuthorize("hasAuthority('permission:read')")
    @PostAuthorize("returnObject.owner == authentication.name")
    public Customer readCustomer(String id) { ... }
}

기본적으로 Authorize HTTP Request 방법으로 대부분의 권한 설정을 할 수 있지만 Method Security 방법이 더 유용할 때가 있다.

  1. 세분화한 권한 부여; 예를 들자면 메서드 매개변수반환 값이 권한부여 결정을 할 때 사용될 경우
  2. 서비스 계층에서 보안 적용
  3. Annotation을 통한 설정을 선호 할 때.

2. Method Security

2.1. 설정

Method Security는 기본적으로 Spring AOP기반으로 구축 되어 있다.
Method Security를 시작하려면 @Configuration이 있는 곳에 @EnableMethodSecurity를 추가하면 된다.

@EnableMethodSecurity
@Configuration
public class SecurityConfig {
  public SecurityFilterChain config(HttpSecurity http) throws Exception{
  	return http.build();
  }
}

2.2 구조


1. #readCustomer함수를 호출하면 Spring AOP가 @PreAuthorize의 pointcut과 일치하는 advisor인 AuthorizationManagerBeforeMethodInterceptor를 호출합니다.
2. AuthorizationManagerBeforeMethodInterceptorPreAuthorizeAuthorizationManager#check를 호출 합니다.
3. PreAuthorizeAuthorizationManager#checkMethodSecurityExpressionHandler를 호출하여 SpEL 구문식을 분석합니다. SpEL 구문식을 처리하는 EvaluationContext를 생성하면서 분석한 구문을 처리하는 것과 일치하는 MethodSecurityExpressionRoot를 생성하여 넘겨줍니다.
ex) hasRole에 경우 SecurityExpressionRoot를 생성해 EvaluationContext에 넘겨준다.
4. EvaluationContext를 이용하여 SpEL 구문식이 올바른지 평가한다.
5. 평가를 통과하면 Sprin AOP는 target method를 호출한다.
6. 그렇지 않는 경우, 인터셉터는 AuthorizationDeniedEvent를 발생시키고 AccessDeniedException예외를 던진다. ExceptionTranslationFilter가 catch하여 403 status code를 response에 리턴한다.
7. method가 리턴되면 Spring AOP가 @PostAuthorize의 pointcut과 일치하는 advisor인 AuthorizationManagerBeforeMethodInterceptor를 호출한고 이 인터셉터는PostAuthorizeAuthorizationManager#check 호출한다.
8. 통과시 일반적인 과정으로 진행된다.
9. 그렇지 않는 경우, 인터셉터는 AuthorizationDeniedEvent를 발생시키고 AccessDeniedException예외를 던진다.ExceptionTranslationFilter가 catch하여 403 status code를 response에 리턴한다.

2.3. 메소드 권한 부여

2.3.1. @PreAuthroize

method 실행전 권한을 평가한다.

public class BankService {
	@PreAuthorize("hasRole('ADMIN')")
	public Account readAccount(Long id) {
        // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
	}
}

2.3.1.1. method parameter 사용

method의 parameter를 평가하는데 사용할 수 있다.

@PreAuthorize("hasPermission(#c, 'write')")
public void updateContact(@P("c") Contact contact);

2.3.2. @PostAuthorize

method 실행후 권한을 평가한다.
@PostAhuthorize는 returnObject를 통해 메소드의 반환값을 평가 할 수 있다.

@Component
public class BankService {
	@PostAuthorize("returnObject.owner == authentication.name")
	public Account readAccount(Long id) {
        // ... is only returned if the `Account` belongs to the logged in user
	}
}

2.3.2.1 @Transcaton Rollback 설정

@PreAuthorize의 인터셉터는 100번, @PostAuthorize의 인터셉트는 200번이다. 이게 중요한 이유는 @Transaction이 기본 설정인 @EnableTransactionManagement(Integer.MAX_VALUE) 설정으로 인해 제일 마지막에 실행되는 AOP이기 때문이다. 따라서 @PostAuthorize의 경우 권한통과 실패해도 이미 Transaction이 종료된 후라 Rollback 되지 않는다. @PostAuthorize가 @Transaction안에서 실행 되게 할려면 @Configure annotaion이 있는 곳에 다음과 같이 설정하면 된다.

@EnableTransactionManagement(order = 0)

2.3.3. @PreFilter

여러개의 값을 @PreFilter를 통해 검증할 수 있다.

@Component
public class BankService {
	@PreFilter("filterObject.owner == authentication.name")
	public Collection<Account> updateAccounts(Account... accounts) {
        // ... `accounts` will only contain the accounts owned by the logged-in user
        return updated;
	}
}

@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)

2.3.4. @PostFilter

method 호출 종료 후 반환 값들을 여러개 검증할 수 있다.

@Component
public class BankService {
	@PostFilter("filterObject.owner == authentication.name")
	public Collection<Account> readAccounts(String... ids) {
        // ... the return value will be filtered to only contain the accounts owned by the logged-in user
        return accounts;
	}
}

2.4. Class 또는 interface수준에 Annotaion 선언

@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() { ... }
}

class 수준과 method 수준 모두에서 선언 된 경우 method 수준에서 선언된 annotaion이 class 수준에 정의된 annotaion을 재정의 합니다.

@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
public class MyController {
    @GetMapping("/endpoint")
    @PreAuthorize("hasAuthority('ROLE_ADMIN')") //재정의됨
    public String endpoint() { ... }
}

2.5. Authorization Expression Fields and Methods

SpEL 표현식에서 기본적으로 제공하는 다양한 표현식들 존재합니다.

  • permitAll - 인증이 필요하지 않습니다.
  • denyAll - 인증을 항상 거부 합니다.
  • hasAuthority - 이 메소드는 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 목록에서 해당 Authority가 있는지 평가합니다.
  • hasRole - hasAuthority의 숏컷으로 authority의 prefix로 ROLE_ 이나 설정한 기본 prefix가 붙습니다.
  • hasAnyAuthority - 이 메소드로 주어진 authority들 중 Authentication의 GrantAuthority가 하나라도 포함되는지 평가 합니다.
  • asAnyRole - hasAnyAuthority의 숏컷으로 prefixe로 ROLE_ 이나 설정한 기본 prefix가 붙습니다.
  • hasPermission - PermissionEvaluator가 이 권한이 올바른지 평가합니다.

그리고 설정된 변수

  • authentication - Authentication instance입니다.
  • principal - Authentication#getPrincipal instance 입니다.

사용예시

@Component
public class MyService {
    @PreAuthorize("denyAll")
    MyResource myDeprecatedMethod(...);

    @PreAuthorize("hasRole('ADMIN')")
    MyResource writeResource(...)

    @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')")
    MyResource deleteResource(...)

    @PreAuthorize("principal.claims['aud'] == 'my-audience'")
    MyResource readResource(...);
}

2.6. 프로그래밍 방식으로 평가

2.6.1. SpEL에서 사용자 정의 Bean 사용

먼저 MethodSecurityExpressionOperations의 인스턴스를 매개변수로 하는 method를 가지는 bean을 만드세요

@Component("authz")
public class AuthorizationLogic {
    public boolean decide(MethodSecurityExpressionOperations operations) {
        // ... authorization logic
    }
}

그런 다음 해당 bean을 사용하세요

@Controller
public class MyController {
    @PreAuthorize("@authz.decide(#root)")
    @GetMapping("/endpoint")
    public String endpoint() {
        // ...
    }
}
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 8월 5일

잘 읽었습니다. 좋은 정보 감사드립니다.

답글 달기