ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,33 @@
+spring:
+  security:
+    oauth2:
+      client:
+        registrations:
+          github:
+            provider: "github"
+            client-id: "Ov23lijfjg9lkGyYVDXN"
+            client-secret: "YOUR_GITHUB_CLIENT_SECRET"
+            scope:
+              - read:user
+            redirect-uri: "http://localhost:8080/login/oauth2/code/github"
+            authorization-grant-type: authorization_code
+          google:
+            provider: "google"
+            client-id: "349246449409-h7hgk8kms3k8d7tgal8nesbh24h34t0d.apps.googleusercontent.com"
+            client-secret: "YOUR_GOOGLE_CLIENT_SECRET"
+            scope:
+              - email
+              - profile
+            redirect-uri: "http://localhost:8080/login/oauth2/code/google"
+            authorization-grant-type: authorization_code
+        providers:
+          github:
+            authorization-uri: "https://github.com/login/oauth/authorize?"
+            token-uri: "https://github.com/login/oauth/access_token"
+            access-token-uri: "http://localhost:8089/login/oauth/access_token"
+            user-info-uri: "https://api.github.com/user"
+          google:
+            authorization-uri: "https://accounts.google.com/o/oauth2/v2/auth?"
+            token-uri: "https://oauth2.googleapis.com/token"
+            access-token-uri: "http://localhost:8089/login/oauth/access_token"
+            user-info-uri: "https://www.googleapis.com/oauth2/v3/userinfo"

리뷰: 헉 이런 규칙이 있는지도 몰랐네요 ,, 좋은 인사이트 감사합니다!
@@ -0,0 +1,71 @@
+package nextstep.security.authentication;
+
+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 java.io.IOException;
+import java.util.Map;
+import nextstep.security.authentication.OAuth2ClientProperties.Provider;
+import nextstep.security.authentication.OAuth2ClientProperties.Registration;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.filter.GenericFilterBean;
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class OAuth2LoginRedirectFilter extends GenericFilterBean {
+
+  private static final String REQUEST_URI = "/oauth2/authorization";
+
+  private final Map<String, Registration> registrations;
+  private final Map<String, Provider> providers;
+
+  public OAuth2LoginRedirectFilter(OAuth2ClientProperties properties) {
+    this.registrations = properties.getRegistrations();
+    this.providers = properties.getProviders();
+  }
+
+  @Override
+  public void doFilter(ServletRequest request,
+      ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+
+    String requestURI = httpServletRequest.getRequestURI();
+    String method = httpServletRequest.getMethod();
+
+    String registrationSource = null;
+    if (requestURI.startsWith(REQUEST_URI)) {
+      registrationSource = getRegistrationId(requestURI);
+    }
+
+    if (registrationSource != null && method.equals(HttpMethod.GET.name())) {
+      Registration registration = registrations.get(registrationSource);
+      Provider provider = providers.get(registrationSource);
+
+      String redirectUri = UriComponentsBuilder.fromHttpUrl(provider.authorizationUri())
+          .queryParam("client_id", registration.clientId())
+          .queryParam("scope", String.join(" ", registration.scope()))
+          .queryParam("response_type", "code")
+          .queryParam("redirect_uri", registration.redirectUri())
+          .build()
+          .toUriString();
+
+      httpServletResponse.sendRedirect(redirectUri);
+      return;
+    }
+
+    chain.doFilter(request, response);
+  }
+
+  private String getRegistrationId(String requestURI) {
+    for (String registrationId : registrations.keySet()) {
+      if (requestURI.contains(registrationId)) {
+        return registrationId;
+      }
+    }
+    return null;
+  }
+}

리뷰: [8071959](https://github.com/next-step/spring-security-oauth2/pull/7/commits/807195919a73c89b2ec6c0d52063d15b7ee1d46f)
로 반영했습니다.
@@ -0,0 +1,27 @@
+package nextstep.security.authentication;
+
+import java.util.Map;
+import nextstep.security.authentication.OAuth2ClientProperties.Provider;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+public class OAuth2UserInfoClient {
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    public OAuth2UserInfo getUserInfo(String accessToken, Provider provider) {
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add("Authorization", "Bearer " + accessToken);
+
+        HttpEntity<Void> request = new HttpEntity<>(httpHeaders);
+        ResponseEntity<Map> response = restTemplate.exchange(provider.userInfoUri(), HttpMethod.GET,
+                request, Map.class);
+
+        Map<String, Object> attributes = response.getBody();
+
+        return OAuth2UserInfoFactory.getOAuth2UserInfo(provider.name(), attributes);
+    }
+}

리뷰: [cde6bc2](https://github.com/next-step/spring-security-oauth2/pull/7/commits/cde6bc20d18375290b619a33c29a5f859e3a541e) 로 진행해보았습니다!

현 구조상 같은 값을 반환하고 있기 때문에 간과하고 있던 부분인데요.
하면 할수록 확실히 이런 프레임워크에서는 확장성 있게 구성하는게 중요하다고 느껴지네요!
@@ -0,0 +1,43 @@
+package nextstep.app;
+
+import nextstep.oauth2.authentication.provider.OAuth2LoginAuthenticationProvider;
+import nextstep.oauth2.registration.ClientRegistrationRepository;
+import nextstep.oauth2.registration.OAuth2ClientProperties;
+import nextstep.oauth2.userinfo.OAuth2UserService;
+import nextstep.oauth2.web.authorizedclient.OAuth2AuthorizedClientRepository;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.authentication.DaoAuthenticationProvider;
+import nextstep.security.authentication.ProviderManager;
+import nextstep.security.userdetails.UserDetailsService;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@EnableConfigurationProperties(OAuth2ClientProperties.class)
+@Configuration
+public class OAuth2Config {
+    @Bean
+    public ClientRegistrationRepository registrationRepository(
+            OAuth2ClientProperties oauth2ClientProperties
+    ) {
+        return oauth2ClientProperties.createClientRegistrationDao();
+    }
+
+    @Bean
+    public OAuth2AuthorizedClientRepository authorizedClientRepository() {
+        return OAuth2AuthorizedClientRepository.getInstance();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(
+            UserDetailsService userDetailsService,
+            OAuth2UserService oAuth2UserService
+    ) {
+        return new ProviderManager(List.of(
+                new DaoAuthenticationProvider(userDetailsService),
+                new OAuth2LoginAuthenticationProvider(oAuth2UserService)
+        ));
+    }
+}

리뷰: 커스텀을 하게 되겠지만 DefaultOAuth2UserService를 두어서 필요한 로직만 추가로 구현해보는 구조로 해보시면 어떨까요?
@@ -0,0 +1,68 @@
+package nextstep.oauth2.authentication.token;
+
+import nextstep.oauth2.authentication.OAuth2AccessToken;
+import nextstep.oauth2.endpoint.dto.OAuth2AuthorizationExchange;
+import nextstep.oauth2.registration.ClientRegistration;
+import nextstep.security.authentication.Authentication;
+
+import java.util.Set;
+
+public final class OAuth2AuthorizationCodeAuthenticationToken implements Authentication {
+    private final ClientRegistration clientRegistration;
+    private final OAuth2AuthorizationExchange authorizationExchange;
+    private final OAuth2AccessToken accessToken;
+
+    public OAuth2AuthorizationCodeAuthenticationToken(
+            ClientRegistration clientRegistration,
+            OAuth2AuthorizationExchange authorizationExchange,
+            OAuth2AccessToken accessToken
+    ) {
+        this.clientRegistration = clientRegistration;
+        this.authorizationExchange = authorizationExchange;
+        this.accessToken = accessToken;
+    }
+
+    public OAuth2AuthorizationCodeAuthenticationToken(
+            ClientRegistration clientRegistration,
+            OAuth2AuthorizationExchange authorizationExchange
+    ) {
+        this(clientRegistration, authorizationExchange, null);
+    }
+
+    public ClientRegistration getClientRegistration() {
+        return clientRegistration;
+    }
+
+    public OAuth2AuthorizationExchange getAuthorizationExchange() {
+        return authorizationExchange;
+    }
+
+    public OAuth2AccessToken getAccessToken() {
+        return accessToken;
+    }
+
+    public boolean isValid() {
+        return authorizationExchange.isSameState();
+    }
+
+    @Override
+    public Set<String> getAuthorities() {
+        return Set.of();
+    }
+
+    @Override
+    public Object getCredentials() {
+        return (accessToken != null) ? accessToken.token()
+                : authorizationExchange.authorizationResponse().code();
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return clientRegistration.clientId();
+    }
+
+    @Override
+    public boolean isAuthenticated() {
+        return false;
+    }
+}

리뷰: authorizationCodeAuthentication를 살펴보면 Authentication의 getCredentials나 getPrincipal를 별도로 사용하고 있지 않은데 왜 굳이 Authentication를 구현하게 하는 것일 지 생각해보셨나요? 정답이 있거나 무언가를 의도하는 질문은 아니고 생각해보시길 바라는 마음에서 남기는 코멘트 입니다!
@@ -0,0 +1,43 @@
+package nextstep.app;
+
+import nextstep.oauth2.authentication.provider.OAuth2LoginAuthenticationProvider;
+import nextstep.oauth2.registration.ClientRegistrationRepository;
+import nextstep.oauth2.registration.OAuth2ClientProperties;
+import nextstep.oauth2.userinfo.OAuth2UserService;
+import nextstep.oauth2.web.authorizedclient.OAuth2AuthorizedClientRepository;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.authentication.DaoAuthenticationProvider;
+import nextstep.security.authentication.ProviderManager;
+import nextstep.security.userdetails.UserDetailsService;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@EnableConfigurationProperties(OAuth2ClientProperties.class)
+@Configuration
+public class OAuth2Config {
+    @Bean
+    public ClientRegistrationRepository registrationRepository(
+            OAuth2ClientProperties oauth2ClientProperties
+    ) {
+        return oauth2ClientProperties.createClientRegistrationDao();
+    }
+
+    @Bean
+    public OAuth2AuthorizedClientRepository authorizedClientRepository() {
+        return OAuth2AuthorizedClientRepository.getInstance();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(
+            UserDetailsService userDetailsService,
+            OAuth2UserService oAuth2UserService
+    ) {
+        return new ProviderManager(List.of(
+                new DaoAuthenticationProvider(userDetailsService),
+                new OAuth2LoginAuthenticationProvider(oAuth2UserService)
+        ));
+    }
+}

리뷰: 미션을 진행할 때, oauth2 패키지만 해도 다루어야할 게 무척 많다보니, app 패키지에서의 변경사항을 최소화하고,
만약 추가 클래스를 구현할게 있다면 최대한 OAuth2Config 내부에서 해결하려다보니 이렇게 Bean 등록을 하게 된 것 같습니다.

DefaultOAuth2UserService 를 별도 클래스로 분리시키도록 하겠습니다.
@@ -0,0 +1,43 @@
+package nextstep.app;
+
+import nextstep.oauth2.authentication.provider.OAuth2LoginAuthenticationProvider;
+import nextstep.oauth2.registration.ClientRegistrationRepository;
+import nextstep.oauth2.registration.OAuth2ClientProperties;
+import nextstep.oauth2.userinfo.OAuth2UserService;
+import nextstep.oauth2.web.authorizedclient.OAuth2AuthorizedClientRepository;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.authentication.DaoAuthenticationProvider;
+import nextstep.security.authentication.ProviderManager;
+import nextstep.security.userdetails.UserDetailsService;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@EnableConfigurationProperties(OAuth2ClientProperties.class)
+@Configuration
+public class OAuth2Config {
+    @Bean
+    public ClientRegistrationRepository registrationRepository(
+            OAuth2ClientProperties oauth2ClientProperties
+    ) {
+        return oauth2ClientProperties.createClientRegistrationDao();
+    }
+
+    @Bean
+    public OAuth2AuthorizedClientRepository authorizedClientRepository() {
+        return OAuth2AuthorizedClientRepository.getInstance();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(
+            UserDetailsService userDetailsService,
+            OAuth2UserService oAuth2UserService
+    ) {
+        return new ProviderManager(List.of(
+                new DaoAuthenticationProvider(userDetailsService),
+                new OAuth2LoginAuthenticationProvider(oAuth2UserService)
+        ));
+    }
+}

리뷰: [5d68d0d](https://github.com/next-step/spring-security-oauth2/pull/8/commits/5d68d0dc640af2e1fd53ebca1cc7b8a5ed58d3ec) 커밋에서 반영 완료하였습니다.
@@ -0,0 +1,68 @@
+package nextstep.oauth2.authentication.token;
+
+import nextstep.oauth2.authentication.OAuth2AccessToken;
+import nextstep.oauth2.endpoint.dto.OAuth2AuthorizationExchange;
+import nextstep.oauth2.registration.ClientRegistration;
+import nextstep.security.authentication.Authentication;
+
+import java.util.Set;
+
+public final class OAuth2AuthorizationCodeAuthenticationToken implements Authentication {
+    private final ClientRegistration clientRegistration;
+    private final OAuth2AuthorizationExchange authorizationExchange;
+    private final OAuth2AccessToken accessToken;
+
+    public OAuth2AuthorizationCodeAuthenticationToken(
+            ClientRegistration clientRegistration,
+            OAuth2AuthorizationExchange authorizationExchange,
+            OAuth2AccessToken accessToken
+    ) {
+        this.clientRegistration = clientRegistration;
+        this.authorizationExchange = authorizationExchange;
+        this.accessToken = accessToken;
+    }
+
+    public OAuth2AuthorizationCodeAuthenticationToken(
+            ClientRegistration clientRegistration,
+            OAuth2AuthorizationExchange authorizationExchange
+    ) {
+        this(clientRegistration, authorizationExchange, null);
+    }
+
+    public ClientRegistration getClientRegistration() {
+        return clientRegistration;
+    }
+
+    public OAuth2AuthorizationExchange getAuthorizationExchange() {
+        return authorizationExchange;
+    }
+
+    public OAuth2AccessToken getAccessToken() {
+        return accessToken;
+    }
+
+    public boolean isValid() {
+        return authorizationExchange.isSameState();
+    }
+
+    @Override
+    public Set<String> getAuthorities() {
+        return Set.of();
+    }
+
+    @Override
+    public Object getCredentials() {
+        return (accessToken != null) ? accessToken.token()
+                : authorizationExchange.authorizationResponse().code();
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return clientRegistration.clientId();
+    }
+
+    @Override
+    public boolean isAuthenticated() {
+        return false;
+    }
+}

리뷰: 저는 해당 부분을 고민하면서 들었던 생각은 인증 과정에서 필요한 객체임을 드러내기위해 Authentication을 구현한 것이라 생각했었습니다. 정완님의 히스토리 추적을 살펴보니 일리가 있어 보이네요 👍
@@ -0,0 +1,58 @@
+package nextstep.app.service;
+
+import nextstep.app.domain.Member;
+import nextstep.app.domain.MemberRepository;
+import nextstep.oauth2.profile.OAuth2ProfileUser;
+import nextstep.oauth2.userinfo.OAuth2User;
+import nextstep.oauth2.userinfo.OAuth2UserRequest;
+import nextstep.oauth2.userinfo.OAuth2UserService;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+import java.util.Set;
+
+@Service
+public class DefaultOAuth2UserService implements OAuth2UserService {
+    private static final RestTemplate rest = new RestTemplate();
+
+    private final MemberRepository memberRepository;
+
+    public DefaultOAuth2UserService(MemberRepository memberRepository) {
+        this.memberRepository = memberRepository;
+    }
+
+    @Override
+    public OAuth2User loadUser(OAuth2UserRequest userRequest) {
+        final String userNameAttributeName = userRequest.clientRegistration()
+                .providerDetails().userInfoEndpoint().userNameAttributeName();
+        final Map<String, Object> attributes = exchangeAttributes(userRequest);
+        final OAuth2ProfileUser profileUser = OAuth2ProfileUser.of(
+                userRequest.clientRegistration().registrationId(),
+                attributes
+        );
+        final Set<String> authorities = memberRepository.findByEmail(
+                attributes.get(userNameAttributeName).toString()
+        ).orElseGet(
+                () -> memberRepository.save(new Member(
+                        profileUser.email(), "", profileUser.name(),
+                        profileUser.imageUrl(), Set.of("USER")
+                ))
+        ).getRoles();
+        return OAuth2User.of(authorities, attributes, userNameAttributeName);
+    }
+
+    private Map exchangeAttributes(OAuth2UserRequest userRequest) {
+        final HttpHeaders headers = new HttpHeaders();
+        headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + userRequest.accessToken().token());
+        return rest.exchange(
+                userRequest.clientRegistration().providerDetails().userInfoEndpoint().uri(),
+                HttpMethod.GET,
+                new HttpEntity<>(headers),
+                Map.class
+        ).getBody();
+    }
+}

리뷰: 반영 잘 해주셨습니다!
DefaultOAuth2UserService에서 제공해주는 것 외에 커스텀이 필요한 부분은 어떤 부분인지 확인해보고 구현을 해보면
추후 스프링 시큐리티를 사용할 때 조금 더 색다르게 다가올 것 같네요 :) 
어떤 부분이 있을 지 고민만 해보셔요~
@@ -0,0 +1,95 @@
+'use client';
+
+import { useState } from 'react';
+import { useGlobalModal } from './useGlobalModal';
+import { useParams } from 'next/navigation';
+import { createReview } from '@/api/reivews';
+
+export interface IReviewState {
+	valid: boolean;
+	comment: string;
+	score: number;
+}
+
+export function useCreateReview(initialState: IReviewState) {
+	const [state, setState] = useState(initialState);
+	const { openModal, closeAllModal } = useGlobalModal(); // 전역 모달 제어
+	const params = useParams<{ id: string }>();
+
+	/** 유효성 검사 */
+	const validate = (
+		name: string,
+		value: string | number,
+		prev: IReviewState,
+	) => {
+		let valid = false;
+
+		// 평점 0점 이상
+		if (name === 'score') {
+			valid =
+				typeof value === 'number' && value > 0 && prev.comment?.length > 0;
+		}
+
+		// 빈 문자열 유효성 검사
+		if (name === 'comment' && typeof value === 'string') {
+			valid = value.trim().length > 0 && prev.score > 0;
+		}
+
+		return valid;
+	};
+
+	// 공통 핸들러 함수
+	const handleChange = (
+		e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
+	) => {
+		const { name, value } = e.currentTarget;
+		const nextValue = name === 'score' ? Number(value) : value;
+
+		setState((prevState) => {
+			const valid = validate(name, nextValue, prevState);
+			return {
+				...prevState,
+				valid,
+				[name]: nextValue,
+			};
+		});
+	};
+
+	// form submit 핸들러 함수
+	const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
+		e.preventDefault();
+		const res = await createReview(params?.id, state);
+		if (res) {
+			closeAllModal();
+		}
+
+		// const formData = new FormData(e.currentTarget);
+
+		// for (const [name, value] of formData) {
+		// 	console.log(`${name} = ${value}`); // key1 = value1, then key2 = value2
+		// }
+	};
+
+	// 작성 중간에 이탈 시 실행 함수
+	const handleClose = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
+		// 작성된 내용이 있을 때만 표시
+		if (state.score !== 0 || state.comment?.trim()?.length !== 0) {
+			openModal({
+				content: (
+					<>
+						정말 나가시겠어요?
+						<br />
+						작성된 내용이 모두 삭제됩니다.
+					</>
+				),
+				confirmType: 'Alert',
+				onConfirm: closeAllModal,
+				buttonPosition: 'right',
+			});
+		} else {
+			closeAllModal();
+		}
+	};
+
+	return { state, handleChange, handleSubmit, handleClose };
+}

리뷰: P4: 나중에 에러처리만 잘 넣어주시면 좋을 것 같아요! 고생하셨습니다~
@@ -13,15 +13,13 @@
 import nextstep.security.context.SecurityContextHolderFilter;
 import nextstep.security.matcher.AnyRequestMatcher;
 import nextstep.security.matcher.MvcRequestMatcher;
-import nextstep.security.matcher.RequestMatcherEntry;
 import nextstep.security.userdetails.UserDetails;
 import nextstep.security.userdetails.UserDetailsService;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.http.HttpMethod;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -50,20 +48,31 @@ public SecuredMethodInterceptor securedMethodInterceptor() {
         return new SecuredMethodInterceptor();
     }
 
+//    @Bean
+//    public SecuredAspect securedAspect() {
+//        return new SecuredAspect();
+//    }
+
+
     @Bean
-    public SecuredAspect securedAspect() {
-        return new SecuredAspect();
+    public RoleHierarchy roleHierarchy() {
+       return RoleHierarchyImpl.with()
+               .role("ADMIN")
+               .implies("USER","GUEST")
+               .build();
     }
 
+
     @Bean
     public RequestMatcherDelegatingAuthorizationManager requestAuthorizationManager() {
-        List<RequestMatcherEntry<AuthorizationManager>> mappings = new ArrayList<>();
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members/me"), new AuthenticatedAuthorizationManager()));
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members"), new HasAuthorityAuthorizationManager("ADMIN")));
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/search"), new PermitAllAuthorizationManager()));
-        mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.POST, "/private"), new DenyAllAuthorizationManager()));
-        mappings.add(new RequestMatcherEntry<>(new AnyRequestMatcher(), new PermitAllAuthorizationManager()));
-        return new RequestMatcherDelegatingAuthorizationManager(mappings);
+        return RequestMatcherAuthorizationManagerBuilder.withRoleHierarchy(roleHierarchy())
+                .authenticated(new MvcRequestMatcher(HttpMethod.GET, "/members/me"))
+                .permitAll(new MvcRequestMatcher(HttpMethod.GET, "/search"))
+                .hasAuthority("ADMIN", new MvcRequestMatcher(HttpMethod.GET, "/members"))
+                .hasAuthority("USER", new MvcRequestMatcher(HttpMethod.POST, "/hierarchy"))
+                .denyAll(new MvcRequestMatcher(HttpMethod.POST, "/private"))
+                .permitAll(new AnyRequestMatcher())
+        .build();
     }
 
     @Bean

리뷰: 빌더 패턴을 적용하셨군요! 재미있는 시도입니다 👍