개발/유지보수

[디자인 패턴] Adapter 패턴 구성 요소 및 Spring Security 사례

선우. 2024. 5. 30. 22:00

개요

 스프링 시큐리티 6.x 버전 출시 이후로 더이상 사용되지 않는 deprecated된 클래스들이 생겨났습니다. 그에 따라 기존 프로젝트에 적용했던 코드들은 직접적으로 활용하지 않을 것이 권장되었습니다. Spring Security 측이 제공한 해결책 코드에 어댑터 패턴이 적용됨을 발견했고, 해당 코드로 변경하며 공부한 내용을 정리하겠습니다.

 

1. 어댑터 패턴

 어댑터 패턴이란 클라이언트가 호출하는 인터페이스는 정해져 있지만, 실제로 활용할 코드의 인터페이스가 일치하지 않을 때 활용하는 패턴을 말합니다.

 

클라이언트가 호출할 API와 무관하게 기존의 클래스를 변경하지 않고도 재활용할 수 있도록 하기 때문입니다.

 

이미 구현이 완료된 클래스를 재사용하고 싶은데 클라이언트가 호출할 API가 클래스의 API와 다를 때, 어댑터 패턴을 도입할 수 있습니다.

 

2. 어댑터 패턴 구성 요소

 어댑터 패턴의 기본적인 아이디어는 기존의 Adaptee api를 전혀 수정하지 않고 클라이언트가 호출할 Target api에 맞추는 것입니다.

 

이러한 아이디어를 코드 레벨로 구현할 방법은 두 가지가 있습니다. 활용할 기존의 클래스(Adaptee)를 상속하는 Adapter 패턴과 Adaptee 클래스를 필드로 갖는 Adapter 패턴입니다.

 

아래 클래스 다이어그램을 통해 두 가지 어댑터 패턴의 구조를 알아보겠습니다.

Adaptee 클래스를 상속한 Adapter

Java는 다중 상속이 불가능하기 때문에 클라이언트가 사용하는 Target 인터페이스를 implements 하고, Adaptee 클래스를 extends 합니다. targetMethod에서는 extends한 Adaptee의 api를 호출하게 됩니다.

 

Adaptee 클래스에 위임하는 Adapter

위 Adapter의 경우 Adaptee를 필드로 갖고 있습니다. Target 클래스를 extends하고(Target 인터페이스를 implements해도 무방) Adaptee의 api를 호출해 처리를 위임합니다.

 

3. 활용 사례

이제 Spring Security의 어댑터 패턴 활용 사례를 살펴보겠습니다.

 

6.x 버전 이전에는 인가처리를 할 때 AccessDecisionManager 인터페이스를 사용했습니다. 그러나 이제는 AuthorizationManager 인터페이스를 사용합니다.

 

이러한 상황을 어댑터 패턴의 구성요소에 대입하면 기존에 활용하던 AccessDecisionManager는 Adaptee, Spring Security가 새롭게 사용하는 AuthorizationManager는 Target 입니다.

 

클래스 다이어그램

 

아래 코드를 통해 기존의 AccessDecisionManager를 변경 없이 활용하면서 새로운 AuthorizationManager를 도입하는 해결 방법을 살펴보겠습니다.

@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionManager accessDecisionManager;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        try {
            Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
            this.accessDecisionManager.decide(authentication.get(), object, attributes);
            return new AuthorizationDecision(true);
        } catch (AccessDeniedException ex) {
            return new AuthorizationDecision(false);
        }
    }

    @Override
    public void verify(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        this.accessDecisionManager.decide(authentication.get(), object, attributes);
    }
}

🔗https://docs.spring.io/spring-security/reference/servlet/authorization/architecture.html#authz-voter-adaptation

 

 스프링 시큐리티는 AuthorizationManager를 implements하고 AccessDecisionManager를 필드로 갖는 어댑터 패턴을 적용했습니다.

 

메서드 오버라이딩한 내부 구현에서 AccessDecisionManager의 api를 호출해 인가처리를 위임하는 것을 확인할 수 있습니다.

 

4. 주의사항

 일반적으로 클래스 상속 Adapter 패턴보다 위임을 사용한 패턴이 문제가 적다고 합니다. 상위 클래스(Adaptee)의 내부 동작을 자세히 모르면, 상속을 효과적으로 사용하기 어렵기 때문입니다.

 

Reference