Authorization Events

김상욱·2024년 12월 11일

Spring Security에서 AuthorizationDeniedEventAuthorizationGrantedEvent는 권한 확인(Authorization) 과정에서 발생하는 이벤트를 처리하는 기능을 제공합니다. 이를 통해 권한 승인 여부에 따라 특정 로직을 실행하거나 추가 작업을 수행할 수 있습니다. 이를 구현하는 방법에 대해 단계별로 자세히 설명드리겠습니다.

1. Authorization Event란?

Spring Security에서는 권한 확인 시, 다음과 같은 두 가지 주요 이벤트를 발생시킬 수 있습니다.

  1. AuthorizationDeniedEvent

    • 권한이 거부된 경우 발생합니다.
    • 예를 들어, 사용자가 특정 리소스에 접근하려고 했으나 필요한 권한을 가지지 못한 경우 이 이벤트가 발생합니다.
  2. AuthorizationGrantedEvent

    • 권한이 승인된 경우 발생합니다.
    • 사용자가 적절한 권한을 가지고 있어 리소스에 접근이 허용되었을 때 이 이벤트가 발생합니다.

2. Authorization Event Publisher 등록

Spring Security의 AuthorizationEventPublisher는 이러한 이벤트를 발행하는 역할을 합니다. 이를 활성화하기 위해 Spring의 기본 구현체인 SpringAuthorizationEventPublisher를 등록합니다.

구현 코드

@Bean
public AuthorizationEventPublisher authorizationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    return new SpringAuthorizationEventPublisher(applicationEventPublisher);
}

동작 설명

  1. ApplicationEventPublisher 주입

    • Spring의 이벤트 발행 메커니즘을 활용하기 위해 ApplicationEventPublisher를 사용합니다.
  2. SpringAuthorizationEventPublisher 생성

    • 이 구현체는 권한 관련 이벤트를 Spring의 ApplicationEventPublisher를 통해 발행하도록 설계되어 있습니다.
  3. @Bean으로 등록

    • AuthorizationEventPublisher는 Spring 컨텍스트에 Bean으로 등록되어야 합니다. 이를 통해 Spring Security가 권한 확인 중 이벤트를 자동으로 발행합니다.

3. 이벤트 리스너 등록

이벤트를 수신하려면 Spring의 @EventListener를 활용해야 합니다. 이를 통해 권한 관련 이벤트가 발생했을 때 사용자 정의 로직을 실행할 수 있습니다.

구현 코드

@Component
public class AuthenticationEvents {

    @EventListener
    public void onFailure(AuthorizationDeniedEvent failure) {
        // 권한 거부 시 수행할 로직
        System.out.println("Authorization Denied: " + failure);
    }

    @EventListener
    public void onSuccess(AuthorizationGrantedEvent success) {
        // 권한 승인 시 수행할 로직
        System.out.println("Authorization Granted: " + success);
    }
}

동작 설명

  1. @Component 등록

    • 해당 클래스가 Spring의 Bean으로 관리되도록 등록합니다.
  2. @EventListener 어노테이션

    • 이 어노테이션은 특정 이벤트를 수신하고 처리하는 메서드임을 나타냅니다.
    • 여기서는 AuthorizationDeniedEventAuthorizationGrantedEvent를 각각 처리하는 두 메서드를 정의했습니다.
  3. 이벤트 처리 로직

    • onFailure 메서드: 권한 거부 시 동작할 로직을 구현합니다.
    • onSuccess 메서드: 권한 승인 시 동작할 로직을 구현합니다.

4. 활용 예제

권한 확인 시 이벤트 발생

Spring Security가 권한을 확인하는 과정에서 특정 리소스 접근이 거부되거나 승인되면, 위에서 정의한 이벤트 리스너가 호출됩니다.

예를 들어:
1. 사용자가 /admin 리소스에 접근.
2. @PreAuthorize("hasRole('ADMIN')")로 권한 제한 설정.
3. 권한 거부 시:

  • AuthorizationDeniedEvent 발생 → onFailure 메서드 실행.
  1. 권한 승인 시:
    • AuthorizationGrantedEvent 발생 → onSuccess 메서드 실행.

5. 추가 고려 사항

  1. 커스텀 로직 추가

    • 이벤트 리스너에서 로깅, 알림 발송, 감사(Audit) 기록 등의 작업을 수행할 수 있습니다.
  2. 테스트 환경 설정

    • Spring Security 테스트 모듈을 활용하여 권한 확인 이벤트가 제대로 발생하는지 검증합니다.
  3. 성능 최적화

    • 이벤트 핸들링이 복잡해질 경우, 비동기 처리를 고려하여 메인 스레드의 부하를 줄일 수 있습니다.

전체 코드 예시

@Configuration
public class SecurityConfig {

    @Bean
    public AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        return new SpringAuthorizationEventPublisher(applicationEventPublisher);
    }
}

@Component
public class AuthenticationEvents {

    @EventListener
    public void onFailure(AuthorizationDeniedEvent failure) {
        System.out.println("Authorization Denied: " + failure);
        // 추가 로직 작성
    }

    @EventListener
    public void onSuccess(AuthorizationGrantedEvent success) {
        System.out.println("Authorization Granted: " + success);
        // 추가 로직 작성
    }
}

이렇게 설정하면 권한 관련 이벤트를 처리할 수 있는 유연한 시스템을 구축할 수 있습니다.


이 코드는 Spring Security에서 권한 확인이 성공했을 때 발생하는 AuthorizationGrantedEvent를 제어하여 필요하지 않은 이벤트를 필터링하고, 필요한 경우에만 이벤트를 발행하도록 사용자 정의 이벤트 퍼블리셔를 구현한 예제입니다.

AuthorizationGrantedEvent는 기본적으로 비활성화되어 있는데, 이를 활성화하면 이벤트가 너무 자주 발생할 수 있으므로 필터링이 중요합니다. 아래에서 코드의 동작 원리를 자세히 설명하겠습니다.

1. AuthorizationEventPublisher란?

  • Spring Security의 인터페이스로, 권한 관련 이벤트(AuthorizationGrantedEvent, AuthorizationDeniedEvent)를 발행하는 역할을 합니다.
  • Spring Security에서 기본 구현체는 SpringAuthorizationEventPublisher이며, 이는 모든 권한 이벤트를 발행합니다.
  • 이 예제에서는 AuthorizationGrantedEventROLE_ADMIN 권한과 관련된 이벤트만 발행하도록 필터링하는 커스텀 이벤트 퍼블리셔를 구현합니다.

2. 코드 설명

(1) 클래스 정의 및 필드 선언

@Component
public class MyAuthorizationEventPublisher implements AuthorizationEventPublisher {
    private final ApplicationEventPublisher publisher;
    private final AuthorizationEventPublisher delegate;

    public MyAuthorizationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
        this.delegate = new SpringAuthorizationEventPublisher(publisher);
    }
}
  • ApplicationEventPublisher:

    • Spring의 이벤트 발행 메커니즘을 사용하기 위한 객체.
    • 이벤트를 발행하는 역할을 수행합니다.
  • AuthorizationEventPublisher 인터페이스 구현:

    • MyAuthorizationEventPublisherAuthorizationEventPublisher를 구현하여 Spring Security에서 사용할 수 있도록 합니다.
  • delegate:

    • 기본 이벤트 퍼블리셔(SpringAuthorizationEventPublisher)를 포함.
    • 권한 거부 이벤트(AuthorizationDeniedEvent)와 같은 기본 이벤트는 여전히 처리하도록 위임합니다.

(2) publishAuthorizationEvent 메서드 구현

@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication,
        T object, AuthorizationDecision decision) {
    if (decision == null) {
        return;
    }
    if (!decision.isGranted()) {
        this.delegate.publishAuthorizationEvent(authentication, object, decision);
        return;
    }
    if (shouldThisEventBePublished(decision)) {
        AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent(
                authentication, object, decision);
        this.publisher.publishEvent(granted);
    }
}
  • decision == null 처리:

    • 권한 결정(AuthorizationDecision)이 없으면 아무 작업도 하지 않고 반환.
  • 권한 거부 처리:

    if (!decision.isGranted()) {
        this.delegate.publishAuthorizationEvent(authentication, object, decision);
        return;
    }
    • 권한이 거부된 경우(decision.isGranted() == false), 기본 퍼블리셔를 통해 AuthorizationDeniedEvent를 발행합니다.
  • 필터링된 권한 승인 처리:

    if (shouldThisEventBePublished(decision)) {
        AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent(
                authentication, object, decision);
        this.publisher.publishEvent(granted);
    }
    • shouldThisEventBePublished 메서드를 사용해 특정 조건에 부합하는 경우만 AuthorizationGrantedEvent를 생성하고 발행.

(3) shouldThisEventBePublished 메서드 구현

private boolean shouldThisEventBePublished(AuthorizationDecision decision) {
    if (!(decision instanceof AuthorityAuthorizationDecision)) {
        return false;
    }
    Collection<GrantedAuthority> authorities = ((AuthorityAuthorizationDecision) decision).getAuthorities();
    for (GrantedAuthority authority : authorities) {
        if ("ROLE_ADMIN".equals(authority.getAuthority())) {
            return true;
        }
    }
    return false;
}
  • 결정 타입 확인:

    if (!(decision instanceof AuthorityAuthorizationDecision)) {
        return false;
    }
    • AuthorizationDecisionAuthorityAuthorizationDecision 타입인지 확인.
    • AuthorityAuthorizationDecision만 권한 리스트를 확인할 수 있으므로, 다른 타입일 경우 이벤트를 발행하지 않음.
  • 권한 필터링:

    for (GrantedAuthority authority : authorities) {
        if ("ROLE_ADMIN".equals(authority.getAuthority())) {
            return true;
        }
    }
    • AuthorityAuthorizationDecision 객체에서 권한 리스트(authorities)를 가져옴.
    • 권한 리스트에 ROLE_ADMIN이 포함되어 있는 경우에만 true 반환.

3. 전체 흐름 요약

  1. Spring Security는 리소스에 접근할 때 권한을 확인하고, 권한 결정(AuthorizationDecision)을 생성.
  2. MyAuthorizationEventPublisher가 호출되어 권한 결정에 따라 이벤트를 발행.
  3. AuthorizationGrantedEventshouldThisEventBePublished 메서드를 통해 ROLE_ADMIN 권한에 한해서만 발행.
  4. 권한 거부(AuthorizationDeniedEvent)는 기본 퍼블리셔를 통해 그대로 발행.

4. 사용 방법

  1. 위 코드를 Spring Bean으로 등록 (@Component 사용).

  2. 이벤트 리스너에서 필요한 AuthorizationGrantedEvent를 처리:

    @Component
    public class AuthorizationEventListener {
    
        @EventListener
        public void handleAuthorizationGranted(AuthorizationGrantedEvent event) {
            System.out.println("Granted Event: " + event);
        }
    
        @EventListener
        public void handleAuthorizationDenied(AuthorizationDeniedEvent event) {
            System.out.println("Denied Event: " + event);
        }
    }
  3. AuthorizationGrantedEvent는 이제 ROLE_ADMIN과 관련된 이벤트에만 발행됩니다.

5. 활용 시 주의사항

  1. 필터 조건

    • shouldThisEventBePublished 메서드의 필터 조건을 명확히 설정해야 합니다. 필요에 따라 조건을 추가하거나 수정하여 더 세밀한 필터링을 구현하세요.
  2. 이벤트 과다 발행 방지

    • 권한 확인이 자주 이루어지는 대규모 애플리케이션에서는 이벤트가 너무 많아질 수 있습니다. 필요한 경우 이벤트 비동기 처리 또는 로깅 방식 조정을 고려하세요.
  3. 테스트

    • 권한 설정 및 이벤트 발행 동작이 의도대로 작동하는지 철저히 테스트하세요.

이 구현을 통해 권한 승인 이벤트를 필요에 따라 필터링하고 효율적으로 관리할 수 있습니다.

0개의 댓글