ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,14 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authorization.web.AuthorizationResult;
+
+@FunctionalInterface
+public interface AuthorizationManager<T> {
+    @Deprecated
+    AuthorizationDecision check(Authentication authentication, T object);
+
+    default AuthorizationResult authorize(Authentication authentication, T object) {
+        return check(authentication, object);
+    }
+}

리뷰: 음.. 결국엔 **인가실패시 예외를 발생시킨다.** 가 핵심이라고 생각해요.
check로는 null이 올 수도 있고, 권한이 없다 할 지어도 에러코드 등에서 개발자 마음대로 핸들링 할 수 있는 반면에,
verify는 인가에 실패한 경우 동일한 에러가 발생된다. 라는 점에서 추적에 용이할 것 같고, 인적오류를 최소화 할 수 있을 것 같다고 생각이 드네요!
따라서 관리자 전용 API등 권한이 없으면 추가적인 로직 없이 바로 예외를 던질때 사용하면 좋을 것 같습니다~
@@ -0,0 +1,14 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authorization.web.AuthorizationResult;
+
+@FunctionalInterface
+public interface AuthorizationManager<T> {
+    @Deprecated
+    AuthorizationDecision check(Authentication authentication, T object);
+
+    default AuthorizationResult authorize(Authentication authentication, T object) {
+        return check(authentication, object);
+    }
+}

리뷰: 네 사실 `verify`를 단일로 쓰면 저희가 흔히 알고 있듯 예외를 발생시킬 수 있는 곳에서 예외를 발생시키는 것이 맞기는 합니다.

다만 `AuthorizationManager`의 원래 책임은 인가가 된 유저인지를 확인하는 것이고 이건 인가가 실패되었다는 것이 에러 상황이 아닌 정상적인 비즈니스의 흐름이라고 볼 수 있어요. 인가가 실패한 것에 대한 결과가 예외가 나는 것은 `AuthorizationFilter`가 인가 실패의 결과가 예외이다라는 것을 실행하는 것일뿐인 것이구요. 

실제 `AuthorizationManager`들의 구현체를 보시면 이런 이유들도 겹쳐서 `check`안에서 거의 예외가 발생하지 않고 있는 것을 확인할 수 있어요.
@@ -0,0 +1,28 @@
+package nextstep.security.authorization.web;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.List;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authorization.AuthorizationDecision;
+import nextstep.security.authorization.AuthorizationManager;
+import nextstep.security.util.RequestMatcher;
+import nextstep.security.util.RequestMatcherEntry;
+
+public class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
+
+    private final List<RequestMatcherEntry<AuthorizationManager>> mappings;
+
+    public RequestMatcherDelegatingAuthorizationManager(List<RequestMatcherEntry<AuthorizationManager>> mappings) {
+        this.mappings = mappings;
+    }
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, HttpServletRequest request) {
+        for (RequestMatcherEntry<AuthorizationManager> mapping : mappings) {
+            if (mapping.getRequestMatcher().matches(request)) {
+                return mapping.getEntry().check(authentication, request);
+            }
+        }
+        return AuthorizationDecision.DENY;
+    }
+}

리뷰: ```suggestion
            if (mapping.matches(request)) {
```
제가 의도 드렸던 내용은 getter를 꺼내와서 다시 호출하는 것이 아닌 해당 객체 내에서 처리할 수 있게 메시지를 던지는 형태를 말씀드린 것이었어요. getter를 호출해서 메소드 체이닝이 되는 것은 객체의 역할이 있다기보단 단순히 래핑클래스가 되어버려 책임과 역할이 불분명해질 수 있습니다.

```java
    public boolean matches(HttpServletRequest request) {
        return requestMatcher.matches(request);
    }
```
@@ -0,0 +1,13 @@
+package nextstep.security.authorization.web;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authorization.AuthorizationDecision;
+import nextstep.security.authorization.AuthorizationManager;
+
+public class DenyAllAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
+    @Override
+    public AuthorizationDecision check(Authentication authentication, HttpServletRequest object) {
+        return AuthorizationDecision.DENY;
+    }
+}

리뷰: ```suggestion
    public AuthorizationResult check(Authentication authentication, HttpServletRequest object) {
```
반환 타입들도 잘 추상화해주신 클래스로 만들어주면 좋겠네요 :)
@@ -0,0 +1,22 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authorization.web.AuthorizationResult;
+
+public class AuthorizationDecision implements AuthorizationResult {
+    public static final AuthorizationDecision ALLOW = new AuthorizationDecision(true);
+    public static final AuthorizationDecision DENY = new AuthorizationDecision(false);
+
+    private final boolean granted;
+
+    public AuthorizationDecision(boolean granted) {
+        this.granted = granted;
+    }
+
+    public boolean isGranted() {
+        return granted;
+    }
+
+    public static AuthorizationDecision of(boolean granted) {
+        return granted ? ALLOW : DENY;
+    }
+}

리뷰: ```suggestion
    public static AuthorizationDecision from(boolean granted) {
```
관습적으로 정적 팩토리 메소드는 파라미터가 하나일때 from, 여러개일때 of를 활용합니다.
@@ -0,0 +1,29 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.AuthenticationException;
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
+    private final AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
+
+    @Override
+    public AuthorizationDecision check(final Authentication authentication, final MethodInvocation invocation) {
+        Method method = invocation.getMethod();
+
+        if (method.isAnnotationPresent(Secured.class)) {
+            Secured secured = method.getAnnotation(Secured.class);
+
+            if (authentication == null) {
+                throw new AuthenticationException();
+            }
+
+            return authoritiesAuthorizationManager.check(authentication, List.of(secured.value()));
+        }
+
+        return new AuthorizationDecision(true);
+    }
+}

리뷰: method.getAnnotation(Secured.class)를 사용하여 `@Secured` 어노테이션을 조회하고 있네요.
Spring AOP 환경에서는 메서드가 프록시 객체로 감싸질 수 있기 때문에, 프록시된 메서드를 조회할 경우 실제 구현체의 메서드에서 선언된 `@Secured` 어노테이션을 찾지 못할 가능성이 있습니다.
method.getAnnotation 와 AopUtils.getMostSpecificMethod() 어떤 차이가 있을까요? 😄
@@ -23,7 +23,7 @@
 @AutoConfigureMockMvc
 class BasicAuthTest {
     private final Member TEST_ADMIN_MEMBER = new Member("a@a.com", "password", "a", "", Set.of("ADMIN"));
-    private final Member TEST_USER_MEMBER = new Member("b@b.com", "password", "b", "", Set.of());
+    private final Member TEST_USER_MEMBER = new Member("b@b.com", "password", "b", "", Set.of(""));
 
     @Autowired
     private MockMvc mockMvc;
@@ -37,6 +37,33 @@ void setUp() {
         memberRepository.save(TEST_USER_MEMBER);
     }
 
+    @DisplayName("인증된 사용자는 자신의 정보를 조회할 수 있다.")
+    @Test
+    void request_success_members_me() throws Exception {
+        String token = Base64.getEncoder().encodeToString((TEST_USER_MEMBER.getEmail() + ":" + TEST_USER_MEMBER.getPassword()).getBytes());
+
+        ResultActions response = mockMvc.perform(get("/members/me")
+                .header("Authorization", "Basic " + token)
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+        );
+
+        response.andExpect(status().isOk())
+                .andExpect(MockMvcResultMatchers.jsonPath("$.email").value(TEST_USER_MEMBER.getEmail()));
+    }
+
+    @DisplayName("인증되지 않은 사용자는 자신의 정보를 조회할 수 없다.")
+    @Test
+    void request_fail_members_me() throws Exception {
+        String token = Base64.getEncoder().encodeToString(("none" + ":" + "none").getBytes());
+
+        ResultActions response = mockMvc.perform(get("/members/me")
+                .header("Authorization", "Basic " + token)
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+        );
+
+        response.andExpect(status().isUnauthorized());
+    }
+
     @DisplayName("ADMIN 권한을 가진 사용자가 요청할 경우 모든 회원 정보를 조회할 수 있다.")
     @Test
     void request_success_with_admin_user() throws Exception {
@@ -64,6 +91,20 @@ void request_fail_with_general_user() throws Exception {
         response.andExpect(status().isForbidden());
     }
 
+    @DisplayName("허용된 URI이 아닌경우 요청이 실패 한다.")
+    @Test
+    void request_fail_invalid_uri() throws Exception {
+        String token = Base64.getEncoder().encodeToString((TEST_USER_MEMBER.getEmail() + ":" + TEST_USER_MEMBER.getPassword()).getBytes());
+
+        ResultActions response = mockMvc.perform(get("/invalid-uri")
+                .header("Authorization", "Basic " + token)
+                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+        );
+
+        response.andExpect(status().isForbidden());
+    }
+
+
     @DisplayName("사용자 정보가 없는 경우 요청이 실패해야 한다.")
     @Test
     void request_fail_with_no_user() throws Exception {

리뷰: 테스트 코드 👍
@@ -0,0 +1,29 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.AuthenticationException;
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
+    private final AuthoritiesAuthorizationManager authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();
+
+    @Override
+    public AuthorizationDecision check(final Authentication authentication, final MethodInvocation invocation) {
+        Method method = invocation.getMethod();
+
+        if (method.isAnnotationPresent(Secured.class)) {
+            Secured secured = method.getAnnotation(Secured.class);
+
+            if (authentication == null) {
+                throw new AuthenticationException();
+            }
+
+            return authoritiesAuthorizationManager.check(authentication, List.of(secured.value()));
+        }
+
+        return new AuthorizationDecision(true);
+    }
+}

리뷰: AopUtils.getMostSpecificMethod()를 사용하면 프록시 객체든 아니든 실제 객체의 메서드를 조회하여 @Secured 어노테이션을 못찾는 경우가 발생하지 않겠네요! 
 감사합니다 :)
@@ -0,0 +1,41 @@
+package nextstep.security.authorization.manager;
+
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authorization.AuthorizationDecision;
+import nextstep.security.authorization.Secured;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Set;
+
+public class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
+
+    private final AuthorizationManager<Collection<String>> authorizationManager;
+
+    public SecuredAuthorizationManager() {
+        this.authorizationManager = new AuthoritiesAuthorizationManager();
+    }
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, MethodInvocation invocation) {
+        Method method = invocation.getMethod();
+        boolean isGranted = isGranted(authentication, method);
+
+        return new AuthorizationDecision(isGranted);
+    }
+
+    private boolean isGranted(Authentication authentication, Method method) {
+        return (authentication != null && checkAuthorization(authentication, method));
+    }
+
+    private boolean checkAuthorization(Authentication authentication, Method method) {
+        if (!method.isAnnotationPresent(Secured.class)) {
+            return false;
+        }
+
+        Secured secured = AnnotationUtils.findAnnotation(method, Secured.class);
+        return this.authorizationManager.check(authentication, Set.of(secured.value())).isGranted();
+    }
+}

리뷰: ```suggestion
        if (authentication == null) {
            return AuthorizationDecision.ACCESS_DENIED;
        }
```

권한에 따른 예외 처리는 Interceptor의 역할로 넘겨주면 어떨까요?
@@ -0,0 +1,41 @@
+package nextstep.security.authorization.manager;
+
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authorization.AuthorizationDecision;
+import nextstep.security.authorization.Secured;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Set;
+
+public class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
+
+    private final AuthorizationManager<Collection<String>> authorizationManager;
+
+    public SecuredAuthorizationManager() {
+        this.authorizationManager = new AuthoritiesAuthorizationManager();
+    }
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, MethodInvocation invocation) {
+        Method method = invocation.getMethod();
+        boolean isGranted = isGranted(authentication, method);
+
+        return new AuthorizationDecision(isGranted);
+    }
+
+    private boolean isGranted(Authentication authentication, Method method) {
+        return (authentication != null && checkAuthorization(authentication, method));
+    }
+
+    private boolean checkAuthorization(Authentication authentication, Method method) {
+        if (!method.isAnnotationPresent(Secured.class)) {
+            return false;
+        }
+
+        Secured secured = AnnotationUtils.findAnnotation(method, Secured.class);
+        return this.authorizationManager.check(authentication, Set.of(secured.value())).isGranted();
+    }
+}

리뷰: 예외 책임은 Interceptor에서 하는 것이 맞는 것 같아서 수정 하였습니다 !
@@ -0,0 +1,20 @@
+package nextstep.security.authorization;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+
+public class AuthenticatedAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, HttpServletRequest object) {
+        if (authentication == null) {
+            return AuthorizationDecision.unAuthorizationDecision();
+        }
+
+        if (authentication.isAuthenticated()) {
+            return AuthorizationDecision.authorizationDecision();
+        }
+
+        return AuthorizationDecision.unAuthorizationDecision();
+    }
+}

리뷰: 해당 Manager에서 예외를 발생시키는 것은 기본 인가 흐름과 조금 맞지 않는 방향으로 보여지네요!
AuthorizationDecision(false)를 응답하는 방향으로 개선해보면 어떨까요?
@@ -9,30 +9,25 @@
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.util.Set;
 
 public class CheckAuthenticationFilter extends OncePerRequestFilter {
-    private static final String DEFAULT_REQUEST_URI = "/members";
+    private final AuthorizationManager<HttpServletRequest> authorizationManager;
+
+    public CheckAuthenticationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
+        this.authorizationManager = authorizationManager;
+    }
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
-        if (!DEFAULT_REQUEST_URI.equals(request.getRequestURI())) {
-            filterChain.doFilter(request, response);
-            return;
-        }
-
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-        if (authentication == null || !authentication.isAuthenticated()) {
-            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-            return;
-        }
 
-        Set<String> authorities = authentication.getAuthorities();
-        if (!authorities.contains("ADMIN")) {
-            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        AuthorizationDecision decision = authorizationManager.check(authentication, request);
+
+        if (decision.isAuthorization()) {
+            filterChain.doFilter(request, response);
             return;
         }
 
-        filterChain.doFilter(request, response);
+        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
     }
 }

리뷰: decision.result의 접근제어자를 private로 설정하지 않은 이유가 있으실까요?
@@ -0,0 +1,23 @@
+package nextstep.security.authorization;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+
+public class RequestMatcherEntry<T extends AuthorizationManager> {
+
+    private final RequestMatcher requestMatcher;
+    private final T entry;
+
+    public RequestMatcherEntry(RequestMatcher requestMatcher, T entry) {
+        this.requestMatcher = requestMatcher;
+        this.entry = entry;
+    }
+
+    public boolean isMatches(HttpServletRequest request) {
+        return requestMatcher.matches(request);
+    }
+
+    public AuthorizationDecision check(Authentication authentication, HttpServletRequest request) {
+        return entry.check(authentication, request);
+    }
+}

리뷰: 해당 코드에 AuthorizationManager로 강제 캐스팅하는 코드가 존재하고 있네요! 이렇게 구현할 경우 제네릭을 사용한 장점이 없어질 것 같은데요.
제네릭을 활용하여 강제 캐스팅 없이 구현해보면 어떨까요?
@@ -0,0 +1,23 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+
+public class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, MethodInvocation invocation) {
+        Method method = invocation.getMethod();
+        Secured secured = method.getAnnotation(Secured.class);
+        if (authentication == null) {
+            return AuthorizationDecision.unAuthorizationDecision();
+        }
+        if (!authentication.getAuthorities().contains(secured.value())) {
+            return AuthorizationDecision.unAuthorizationDecision();
+        }
+
+        return AuthorizationDecision.authorizationDecision();
+    }
+}

리뷰: 해당 위치에서도 예외를 발생시키는 것이 자연스러운 흐름일지 고민해보면 좋겠습니다 :)
@@ -0,0 +1,23 @@
+package nextstep.security.authorization;
+
+import nextstep.security.authentication.Authentication;
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+
+public class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, MethodInvocation invocation) {
+        Method method = invocation.getMethod();
+        Secured secured = method.getAnnotation(Secured.class);
+        if (authentication == null) {
+            return AuthorizationDecision.unAuthorizationDecision();
+        }
+        if (!authentication.getAuthorities().contains(secured.value())) {
+            return AuthorizationDecision.unAuthorizationDecision();
+        }
+
+        return AuthorizationDecision.authorizationDecision();
+    }
+}

리뷰: 정적 팩터리 메서드를 통해 의미를 나타내는 것이 너무 좋네요 👍 
다만 인증이 성공했을 경우는 생성자를 통해 생성하고, 실패의 경우 정적 팩터리 메서드를 사용하는 것 보다 방식을 통일하면 코드의 일관성이 생길 것 같은데 어떻게 생각하시나요?
@@ -1,7 +1,6 @@
 package nextstep.security.authorization;
 
 import nextstep.security.authentication.Authentication;
-import nextstep.security.authentication.AuthenticationException;
 import nextstep.security.context.SecurityContextHolder;
 import org.aopalliance.aop.Advice;
 import org.aopalliance.intercept.MethodInterceptor;
@@ -16,21 +15,21 @@
 public class SecuredMethodInterceptor implements MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
 
     private final Pointcut pointcut;
+    private final AuthorizationManager<MethodInvocation> authorizationManager;
 
-    public SecuredMethodInterceptor() {
+    public SecuredMethodInterceptor(AuthorizationManager<MethodInvocation> authorizationManager) {
         this.pointcut = new AnnotationMatchingPointcut(null, Secured.class);
+        this.authorizationManager = authorizationManager;
     }
 
     @Override
     public Object invoke(MethodInvocation invocation) throws Throwable {
         Method method = invocation.getMethod();
         if (method.isAnnotationPresent(Secured.class)) {
-            Secured secured = method.getAnnotation(Secured.class);
             Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-            if (authentication == null) {
-                throw new AuthenticationException();
-            }
-            if (!authentication.getAuthorities().contains(secured.value())) {
+            AuthorizationDecision authorizationDecision = authorizationManager.check(authentication,invocation);
+
+            if (!authorizationDecision.isAuthorization()) {
                 throw new ForbiddenException();
             }
         }

리뷰: 인증 과정으로 봤을 때 해당 코드는 남아있어야 하지 않을까요?
@@ -0,0 +1,59 @@
+package nextstep.security.authorization;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.UsernamePasswordAuthenticationToken;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AuthenticatedAuthorizationManagerTest {
+
+    private final AuthorizationManager<HttpServletRequest> manager = new AuthenticatedAuthorizationManager();
+
+    @Test
+    @DisplayName("authentication가 없으면, false를 반환한다.")
+    void isNull() {
+        //given
+        final Authentication authentication = null;
+        final MockHttpServletRequest request = new MockHttpServletRequest();
+
+        //when
+        final AuthorizationDecision result = manager.check(authentication, request);
+
+        //then
+        assertThat(result.isAuthorization()).isFalse();
+    }
+
+    @Test
+    @DisplayName("인증에 성공했다면, true를 반환한다.")
+    void success() {
+        //given
+        final Authentication authentication = UsernamePasswordAuthenticationToken.authenticated("id", "password", Set.of("USER"));
+        final MockHttpServletRequest request = new MockHttpServletRequest();
+
+        //when
+        final AuthorizationDecision result = manager.check(authentication, request);
+
+        //then
+        assertThat(result.isAuthorization()).isTrue();
+    }
+
+    @Test
+    @DisplayName("인증되지 않은 요청이 들어오면, false를 반환한다.")
+    void failed() {
+        //given
+        final Authentication authentication = UsernamePasswordAuthenticationToken.unauthenticated("id", "password");
+        final MockHttpServletRequest request = new MockHttpServletRequest();
+
+        //when
+        final AuthorizationDecision result = manager.check(authentication, request);
+
+        //then
+        assertThat(result.isAuthorization()).isFalse();
+    }
+}

리뷰: 추가 테스트들 너무 좋네요 💯
@@ -0,0 +1,26 @@
+package nextstep.security.authorization;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+
+import java.util.List;
+
+public class RequestAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
+
+    private List<RequestMatcherEntry<AuthorizationManager>> matcherEntries;
+
+    public RequestAuthorizationManager(List<RequestMatcherEntry<AuthorizationManager>> mappings) {
+        this.matcherEntries = mappings;
+    }
+
+    @Override
+    public AuthorizationDecision check(Authentication authentication, HttpServletRequest request) {
+        for (RequestMatcherEntry<AuthorizationManager> matcherEntry : matcherEntries) {
+            if (matcherEntry.isMatches(request)) {
+                return matcherEntry.check(authentication, request);
+            }
+        }
+
+        return AuthorizationDecision.unAuthorizationDecision();
+    }
+}

리뷰: > 이미 Reuqest URI에 해당하는 Matcher를 한 번 만나게 되면 바로 반환을 하는게 맞을까요?

넵 한 번 매칭이 되면 추가적으로 확인할 필요는 없어보입니다! 일반적으로 요청 URI 하나엔 하나의 정책만 적용되는 것이 자연스러울 것 같아요 ㅎㅎ