ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,78 @@
+package baseball.controller;
+
+import java.util.List;
+
+import baseball.model.ComputerInputData;
+import baseball.view.PlayerInputView;
+import baseball.model.CompareBallsModel;
+import baseball.service.BaseballGameService;
+import baseball.service.InvalidInputHandler;
+import baseball.service.MoreGameService;
+
+public class GameController {
+    
+    private PlayerInputView playerInputView;
+    private BaseballGameService baseballGameService;
+    private ComputerInputData computerInputData;
+    private CompareBallsModel compareBallsModel;
+    private InvalidInputHandler invalidInputHandler;
+    private MoreGameService moreGameService;
+
+    public GameController() {
+        this.playerInputView = new PlayerInputView();
+        this.baseballGameService = new BaseballGameService();
+        this.computerInputData = new ComputerInputData();
+        this.compareBallsModel = new CompareBallsModel();
+        this.invalidInputHandler = new InvalidInputHandler();
+        this.moreGameService = new MoreGameService();
+    }
+
+
+    public void gameFlow() {
+        boolean gameWon = false;
+
+        while (!gameWon) {
+            String input = playerInputView.getPlayerInput();
+
+            if (!baseballGameService.isValidInput(input)) {
+                invalidInputHandler.invalidInputPrint();
+            }
+
+            List<Integer> computerList = computerInputData.getComputerList();
+            int[] result = compareBallsModel.compareNumbers(input, computerList);
+
+            if (result[0] == 3) {
+                System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
+                gameWon = true;
+                continue;
+            }
+
+            printGameResult(result);
+            }
+        if(moreGameService.more() == true) {
+            computerInputData = new ComputerInputData();
+            gameFlow();
+        }
+    }
+
+
+    private void printGameResult(int[] result) {
+        StringBuilder output = new StringBuilder();
+    
+        if (result[0] == 0 && result[1] == 0) {
+            System.out.println("낫싱");
+        }
+        
+        if (result[0] > 0) {
+            output.append(result[0]).append("스트라이크 ");
+        }
+        
+        if (result[1] > 0) {
+            output.append(result[1]).append("볼");
+        }
+        
+        if (output.length() > 0) {
+            System.out.println(output);
+        }
+    }
+}
\ No newline at end of file

리뷰: 간단한 거라도 `view`의 관심사면 `view`에서 처리하는 게 맞는 것 같아. `Controller -> View`가 되는 거지

일단 간단하고 복잡한거의 기준이 없잖아 얼마나 간단한거는 `Controller`에서 처리하고 복잡한 건 `View`에서 처리한다 벌써 말이 안되는게 느껴지지
그리고 만약 출력부분에서 문제가 생기면 당연히 `View` 계층을 확인 할텐데 간단하다고 해서 다른곳에서 처리하면 유지보수 비용이 훨씬 올라가겠지
@@ -0,0 +1,75 @@
+package nextstep.security.authorization;
+
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+public class RoleHierarchyImpl implements RoleHierarchy {
+
+    private final Map<String, Set<String>> rolesReachableInOneOrMoreStepsMap;
+
+    public RoleHierarchyImpl(Map<String, Set<String>> rolesReachableInOneOrMoreStepsMap) {
+        this.rolesReachableInOneOrMoreStepsMap = rolesReachableInOneOrMoreStepsMap;
+    }
+
+    public static Builder with() {
+        return new Builder();
+    }
+
+    @Override
+    public Collection<String> getReachableGrantedAuthorities(Collection<String> authorities) {
+
+        Set<String> reachableRoles = new HashSet<>();
+
+        if (CollectionUtils.isEmpty(authorities)) {
+            return Collections.emptyList();
+        }
+
+        for (String authority : authorities) {
+            addReachableRoles(authority, reachableRoles);
+        }
+
+        return reachableRoles;
+    }
+
+    private void addReachableRoles(String authority, Set<String> reachableRoles) {
+        reachableRoles.add(authority);
+        Set<String> lowerRoles = rolesReachableInOneOrMoreStepsMap.computeIfAbsent(authority, k -> new HashSet<>());
+        reachableRoles.addAll(lowerRoles);
+    }
+
+    public static class Builder {
+        private final Map<String, Set<String>> hierarchy;
+
+        private Builder() {
+            this.hierarchy = new HashMap<>();
+        }
+
+        public RoleHierarchyImpl build() {
+            return new RoleHierarchyImpl(hierarchy);
+        }
+
+        public ImpliedRoles role(String role) {
+            return new ImpliedRoles(role);
+        }
+
+        private Builder addHierarchy(String role, String... impliedRoles) {
+            Set<String> impliedRoleSet = hierarchy.computeIfAbsent(role, k -> new HashSet<>());
+            impliedRoleSet.addAll(Arrays.asList(impliedRoles));
+            return this;
+        }
+
+        public final class ImpliedRoles {
+            private final String role;
+
+            public ImpliedRoles(String role) {
+                this.role = role;
+            }
+
+            public Builder implies(String... impliedRoles) {
+                return Builder.this.addHierarchy(this.role, impliedRoles);
+            }
+        }
+    }
+}
+

리뷰: 만약 다음과 같이 권한의 계층 구조를 설정할 경우에는 어떻게 처리되어야 할까요?
``` java
RoleHierarchyImpl.with()
  .role("ADMIN").implies("USER")
  .role("USER").implies("GUEST")
  .build();
```

현재 구조에서는 직접적인 하위 역할만 추가하고 있어서, ADMIN이 USER만 포함하고 GUEST까지 확장되지 않는 문제가 발생할 수 있습니다.

Spring Security의 RoleHierarchyImpl은 buildRolesReachableInOneOrMoreStepsMap()을 활용하여 모든 계층 관계를 반영하는데, 현재 코드에서는 해당 로직이 없어 다단계 계층 탐색이 누락될 가능성이 있습니다 😢 

개선해보면 어떨까요? 😄
@@ -0,0 +1,51 @@
+package nextstep.app;
+
+import nextstep.security.authorization.NullRoleHierarchy;
+import nextstep.security.authorization.RoleHierarchyImpl;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class RoleHierachyTest {
+
+    @DisplayName("상위 권한의 reachable authorities는 사용자의 권한과 하위 권한을 같이 반환한다.")
+    @Test
+    public void roleHierarchyTest() {
+
+        // given
+        RoleHierarchyImpl roleHierarchy = RoleHierarchyImpl.with()
+                .role("ADMIN").implies("USER")
+                .build();
+
+        Set<String> authorites = new HashSet<>();
+        authorites.add("ADMIN");
+
+        // when
+        Collection<String> reachableGrantedAuthorities = roleHierarchy.getReachableGrantedAuthorities(authorites);
+
+        // then
+        Assertions.assertThat(reachableGrantedAuthorities).containsOnly("ADMIN", "USER");
+    }
+
+    @DisplayName("NullRoleHierarchy의 reachable authorities는 사용자의 권한 리스트를 그대로 반환한다.")
+    @Test
+    public void nullRoleHierarchyTest() {
+
+        // given
+        NullRoleHierarchy nullRoleHierarchy = new NullRoleHierarchy();
+
+        Set<String> authorites = new HashSet<>();
+        authorites.add("ADMIN");
+        authorites.add("USER");
+
+        // when
+        Collection<String> result = nullRoleHierarchy.getReachableGrantedAuthorities(authorites);
+
+        // then
+        Assertions.assertThat(result).isSameAs(authorites);
+    }
+}

리뷰: 테스트 코드 👍 

코드 개선해 보시면서 아래 케이스에 대한 테스트 코드도 작성해 보면 좋을 것 같네요 😄 
``` java
RoleHierarchyImpl.with()
  .role("ADMIN").implies("USER")
  .role("USER").implies("GUEST")
  .build();
```
@@ -0,0 +1,45 @@
+package nextstep.security.config;
+
+import nextstep.security.ProviderNotFoundException;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ProviderManager implements AuthenticationManager {
+
+    private List<AuthenticationProvider> providers = Collections.emptyList();
+
+    public ProviderManager(List<AuthenticationProvider> providers) {
+        this.providers = providers;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) {
+        Class<? extends Authentication> authenticationClassToTest = authentication.getClass();
+        Authentication result = null;
+        for (AuthenticationProvider provider : this.providers) {
+            if (!provider.supports(authenticationClassToTest)) {
+                continue;
+            }
+            result = provider.authenticate(authentication);
+            if (result != null) { // 인증된 객제가 있다면
+                copyDetails(authentication, result);
+                result.setAuthenticated(true); // todo 책임 확인 한번 필요
+                break;
+            }
+        }
+        if (result == null) {
+            throw new ProviderNotFoundException("지원하는 provider가 없습니다!");
+        }
+        return result;
+    }
+
+    // 인증된 토큰에 detail 값이 없다면 detail 넣어주기
+    private void copyDetails(Authentication source, Authentication dest) {
+        if ((dest instanceof AbstractAuthenticationToken token) && (dest.getDetails() == null)) {
+            token.setDetails(source.getDetails());
+        }
+    }
+
+}
+

리뷰: provider를 찾지 못한 케이스와 provider를 찾았지만, 인증을 하지 못한 경우의 케이스는 예외 메시지를 다르게 가는게 좋을 것 같은데 어떻게 생각하시나요?
@@ -0,0 +1,45 @@
+package nextstep.security.config;
+
+import nextstep.security.ProviderNotFoundException;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ProviderManager implements AuthenticationManager {
+
+    private List<AuthenticationProvider> providers = Collections.emptyList();
+
+    public ProviderManager(List<AuthenticationProvider> providers) {
+        this.providers = providers;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) {
+        Class<? extends Authentication> authenticationClassToTest = authentication.getClass();
+        Authentication result = null;
+        for (AuthenticationProvider provider : this.providers) {
+            if (!provider.supports(authenticationClassToTest)) {
+                continue;
+            }
+            result = provider.authenticate(authentication);
+            if (result != null) { // 인증된 객제가 있다면
+                copyDetails(authentication, result);
+                result.setAuthenticated(true); // todo 책임 확인 한번 필요
+                break;
+            }
+        }
+        if (result == null) {
+            throw new ProviderNotFoundException("지원하는 provider가 없습니다!");
+        }
+        return result;
+    }
+
+    // 인증된 토큰에 detail 값이 없다면 detail 넣어주기
+    private void copyDetails(Authentication source, Authentication dest) {
+        if ((dest instanceof AbstractAuthenticationToken token) && (dest.getDetails() == null)) {
+            token.setDetails(source.getDetails());
+        }
+    }
+
+}
+

리뷰: parent를 가지고 있는 이유는 현재 ProviderManager에서 처리할 수 없을 경우, parent에 위임하는 계층형 구조를 지원하기 위해 저러한 설계가 된 것 같아요 ㅎㅎ
@@ -0,0 +1,45 @@
+package nextstep.security.config;
+
+import nextstep.security.ProviderNotFoundException;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ProviderManager implements AuthenticationManager {
+
+    private List<AuthenticationProvider> providers = Collections.emptyList();
+
+    public ProviderManager(List<AuthenticationProvider> providers) {
+        this.providers = providers;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) {
+        Class<? extends Authentication> authenticationClassToTest = authentication.getClass();
+        Authentication result = null;
+        for (AuthenticationProvider provider : this.providers) {
+            if (!provider.supports(authenticationClassToTest)) {
+                continue;
+            }
+            result = provider.authenticate(authentication);
+            if (result != null) { // 인증된 객제가 있다면
+                copyDetails(authentication, result);
+                result.setAuthenticated(true); // todo 책임 확인 한번 필요
+                break;
+            }
+        }
+        if (result == null) {
+            throw new ProviderNotFoundException("지원하는 provider가 없습니다!");
+        }
+        return result;
+    }
+
+    // 인증된 토큰에 detail 값이 없다면 detail 넣어주기
+    private void copyDetails(Authentication source, Authentication dest) {
+        if ((dest instanceof AbstractAuthenticationToken token) && (dest.getDetails() == null)) {
+            token.setDetails(source.getDetails());
+        }
+    }
+
+}
+

리뷰: copyDetails 내부에서 AbstractAuthenticationToken 타입을 체크하는 것은 객체지향 원칙(SOLID) 중 개방폐쇄 원칙와 리스코프 치환 원칙에 위배될 가능성이 있어 남긴 코멘트였는데요.

ProviderManager는 인증 프로세스를 관리하는 역할이라면, 특정 구현체에 의존하지 않는 것이 더 좋은 설계일 것 같은데 어떻게 생각하시나요?
내부에서 타입을 체크하지 않고 인증 프로세스를 관리하는 역할까지 가져갈 수 있도록 해결할 방법이 있을지 고민해보면 좋겠습니다!
@@ -0,0 +1,29 @@
+package nextstep.security.config;
+
+import nextstep.security.AuthenticationException;
+
+public interface AuthenticationProvider {
+
+    /**
+     * AuthenticationManager의 authenticate 메서드와 같은 contract으로 인증 수행
+
+     * @param authentication : 인증 요청 객체
+     * @return credential을 포함한 완전히 authenticated 객체를 반환.
+     * AuthenticationProvider가 받은 Authentication 객체에 대한 인증을 지원하지 않으면 지원null 을 반환 할 수 있음.
+     * 그러면 Authentication을 지원하는 그 다음 AuthenticationProvider가 시도된다.
+     */
+    Authentication authenticate(Authentication authentication) throws AuthenticationException;
+
+    /**
+     *
+     * AuthenticationProvider가 Authentication를 support 하면 true 반환
+     * true를 반환하는것이 AuthenticationProvider가 인증할 수 있을것이라고 보장하는 것이 아님. 그냥 더 자세히 평가를 지원한다는 의미.
+     * 다른 AuthenticationProvider를 시도해봐야한다는 의미로 AuthenticationProvider는 authenticate()결과를 null로 반환
+     * 인증 가능한 AuthenticationProvider 선택은 런타임에 ProviderManager에 의해서 이루어짐
+     *
+     * @param authentication
+     * @return <code>true</code> if the implementation can more closely evaluate the Authentication class presented
+     */
+    boolean supports(Class<?> authentication);
+
+}

리뷰: > provider manager에서 왜 support 할때 리플렉션을 통한 클래스 정보를 넘길까요?

instanceof를 사용할 경우 상속 관계에서 확인이 어렵고, 타입을 체크하기 위해선 무조건 객체가 생성되어야 하기 때문에 클래스 정보로 확인하는 것이 더 유연한 설계로 보여지네요!
@@ -0,0 +1,32 @@
+package nextstep.security.config;
+
+import nextstep.security.UserDetails;
+
+import java.security.Principal;
+
+//  Implementations which use this class should be immutable.
+public abstract class AbstractAuthenticationToken implements Authentication{
+
+    private Object details;
+
+    @Override
+    public String getName() {
+        if (this.getPrincipal() instanceof UserDetails userDetails) {
+            return userDetails.getUsername();
+        }
+        if (this.getPrincipal() instanceof Principal principal) {
+            return principal.getName();
+        }
+        return (this.getPrincipal() == null) ? "" : this.getPrincipal().toString();
+    }
+
+    public void setDetails(Object details) {
+        this.details = details;
+    }
+
+    @Override
+    public Object getDetails() {
+        return this.details;
+    }
+
+}

리뷰: 지금은 config 패키지에 너무 많은 정보가 모여있는데, 의미있는 컨텍스트를 묶어보면 어떨까요?
@@ -0,0 +1,46 @@
+package nextstep.security.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.filter.GenericFilterBean;
+
+import java.io.IOException;
+
+/**
+ * SecurityContextHolderFilter는 요청 시작 시 SecurityContextRepository로부터 인증 정보를 SecurityContextHolder로 이동시킴
+ * 요청 종료 후 SecurityContextHolder를 정리.
+ * 요청 시작 시:
+ * SecurityContextRepository.loadContext() 호출.
+ * 로드한 인증 정보를 SecurityContextHolder에 설정.
+ * 요청 종료 시:
+ * SecurityContextHolder.getContext()를 가져와 SecurityContextRepository.saveContext() 호출.
+ * SecurityContextHolder.clearContext()로 정리.
+ */
+public class SecurityContextHolderFilter extends GenericFilterBean {
+
+    private final SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
+        boolean isHttpServlet = servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse;
+
+        if (isHttpServlet) {
+            HttpServletRequest request = (HttpServletRequest) servletRequest;
+            SecurityContext securityContext = securityContextRepository.loadContext(request);
+            try {
+                SecurityContextHolder.setContext(securityContext);
+                chain.doFilter(servletRequest, servletResponse);
+            } finally {
+                SecurityContextHolder.clearContext();
+            }
+            return;
+        }
+        throw new ServletException("SecurityContextHolderFilter only supports HTTP requests");
+
+    }
+
+}

리뷰: OncePerRequestFilter가 아닌 GenericFilterBean로 구현해주신 이유가 있을까요?
@@ -0,0 +1,46 @@
+package nextstep.security.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.filter.GenericFilterBean;
+
+import java.io.IOException;
+
+/**
+ * SecurityContextHolderFilter는 요청 시작 시 SecurityContextRepository로부터 인증 정보를 SecurityContextHolder로 이동시킴
+ * 요청 종료 후 SecurityContextHolder를 정리.
+ * 요청 시작 시:
+ * SecurityContextRepository.loadContext() 호출.
+ * 로드한 인증 정보를 SecurityContextHolder에 설정.
+ * 요청 종료 시:
+ * SecurityContextHolder.getContext()를 가져와 SecurityContextRepository.saveContext() 호출.
+ * SecurityContextHolder.clearContext()로 정리.
+ */
+public class SecurityContextHolderFilter extends GenericFilterBean {
+
+    private final SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
+        boolean isHttpServlet = servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse;
+
+        if (isHttpServlet) {
+            HttpServletRequest request = (HttpServletRequest) servletRequest;
+            SecurityContext securityContext = securityContextRepository.loadContext(request);
+            try {
+                SecurityContextHolder.setContext(securityContext);
+                chain.doFilter(servletRequest, servletResponse);
+            } finally {
+                SecurityContextHolder.clearContext();
+            }
+            return;
+        }
+        throw new ServletException("SecurityContextHolderFilter only supports HTTP requests");
+
+    }
+
+}

리뷰: 지금은 SecurityContextRepository가 인터페이스로 분리되어 있지만, 내부에서 초기화하고 있기 때문에 확장성이 떨어지는 구조네요.
어떻게 확장이 가능한 구조로 만들 수 있을까요?
@@ -2,8 +2,12 @@
 
 import jakarta.servlet.http.HttpServletRequest;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
 import nextstep.app.domain.Member;
 import nextstep.app.domain.MemberRepository;
+import nextstep.security.access.RoleHierarchy;
+import nextstep.security.access.RoleHierarchyImpl;
 import nextstep.security.authentication.AuthenticationException;
 import nextstep.security.authentication.BasicAuthenticationFilter;
 import nextstep.security.authentication.UsernamePasswordAuthenticationFilter;
@@ -20,6 +24,8 @@
 import nextstep.security.config.FilterChainProxy;
 import nextstep.security.config.SecurityFilterChain;
 import nextstep.security.context.SecurityContextHolderFilter;
+import nextstep.security.core.GrantedAuthority;
+import nextstep.security.core.SimpleGrantedAuthority;
 import nextstep.security.userdetails.UserDetails;
 import nextstep.security.userdetails.UserDetailsService;
 import nextstep.security.util.AnyRequestMatcher;
@@ -57,19 +63,22 @@ public FilterChainProxy filterChainProxy(List<SecurityFilterChain> securityFilte
 
     @Bean
     public SecuredMethodInterceptor securedMethodInterceptor() {
-        return new SecuredMethodInterceptor(securedAuthorizationManager());
+        return new SecuredMethodInterceptor(roleHierarchy());
     }
 
     @Bean
-    public AuthorizationManager<MethodInvocation> securedAuthorizationManager() {
-         return new SecuredAuthorizationManager();
+    public RoleHierarchy roleHierarchy() {
+        return RoleHierarchyImpl.with()
+                .role("ADMIN").implies("USER")
+                .role("USER").implies("GUEST")
+                .build();
     }
 
     @Bean
     public AuthorizationManager<HttpServletRequest> requestAuthorizationManager() {
         List<RequestMatcherEntry<AuthorizationManager>> mappings = new ArrayList<>();
         mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members"),
-                new AuthorityAuthorizationManager<HttpServletRequest>(Set.of("ADMIN"))));
+                new AuthorityAuthorizationManager(roleHierarchy(), Set.of("ADMIN"))));
         mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members/me"),
                 new AuthenticatedAuthorizationManager()));
         mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/search"),
@@ -108,8 +117,8 @@ public String getPassword() {
                 }
 
                 @Override
-                public Set<String> getAuthorities() {
-                    return member.getRoles();
+                public Set<GrantedAuthority> getAuthorities() {
+                    return member.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
                 }
             };
         };

리뷰: 잘 추가해주셨네요 👍
@@ -0,0 +1,13 @@
+package nextstep.security.access;
+
+import java.util.Collection;
+import nextstep.security.core.GrantedAuthority;
+
+public final class NullRoleHierarchy implements RoleHierarchy {
+
+    @Override
+    public Collection<? extends GrantedAuthority> getReachableGrantedAuthorities(
+            Collection<? extends GrantedAuthority> authorities) {
+        return authorities;
+    }
+}

리뷰: `<? extends GrantedAuthority>`로 지정되어있을 이유가 어떻게 될까요?
@@ -4,19 +4,26 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
+import nextstep.security.access.RoleHierarchy;
 import nextstep.security.authentication.Authentication;
 import nextstep.security.authorization.AuthorizationDecision;
 import nextstep.security.authorization.AuthorizationManager;
 import nextstep.security.authorization.Secured;
 import nextstep.security.authorization.web.AuthorityAuthorizationManager;
+import nextstep.security.core.GrantedAuthority;
 import org.aopalliance.intercept.MethodInvocation;
 
 public class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
 
     private AuthorityAuthorizationManager<Collection<String>> authorityAuthorizationManager;
+    private final RoleHierarchy roleHierarchy;
 
-    public void setAuthorityAuthorizationManager(Collection<String> authorities) {
-        authorityAuthorizationManager = new AuthorityAuthorizationManager<>(authorities);
+    public SecuredAuthorizationManager(RoleHierarchy roleHierarchy) {
+        this.roleHierarchy = roleHierarchy;
+    }
+
+    public void setAuthorityAuthorizationManager(RoleHierarchy roleHierarchy, Collection<String> authorities) {
+        authorityAuthorizationManager = new AuthorityAuthorizationManager<>(roleHierarchy, authorities);
     }
 
     @Override
@@ -26,7 +33,7 @@ public AuthorizationDecision check(Authentication authentication, MethodInvocati
         if (authorities.isEmpty()) {
             return null;
         }
-        setAuthorityAuthorizationManager(authorities);
+        setAuthorityAuthorizationManager(roleHierarchy, authorities);
         return authorities.isEmpty() ? null : authorityAuthorizationManager.check(authentication, authorities);
     }
 

리뷰: `RoleHierarchy`를 따로 둘 필요가 없을 것 같아요.

`authorityAuthorizationManager`에서 처리하게 두면 될텐데 `SecuredAuthorizationManager`가 가질 필요가 있을까요?