如何在使用spring-security-oauth2-authorization-server时仅向具有特定角色的用户颁发token?

hiz5n14c  于 2023-04-19  发布在  Spring
关注(0)|答案(1)|浏览(299)

我的auth服务中有3个角色的用户:ADMIN、USER和USER_NOT_VERIFIED。我希望spring-security-oauth2-authorization-server只向ADMIN和USER颁发令牌
下面是向每个人发放令牌的原始代码

@Bean
@Order(1)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .oidc(Customizer.withDefaults());

    http.exceptionHandling(
            e -> e.authenticationEntryPoint(
                    new LoginUrlAuthenticationEntryPoint("/login")
            )
    );
    return http.build();
}

我尝试用以下代码替换OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
        new OAuth2AuthorizationServerConfigurer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
    http.securityMatcher(endpointsMatcher).authorizeHttpRequests((authorize) -> {
    ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)authorize.anyRequest()).hasAnyRole("USER", "ADMIN");
}).csrf((csrf) -> {
    csrf.ignoringRequestMatchers(new RequestMatcher[]{endpointsMatcher});
}).apply(authorizationServerConfigurer);
uxhixvfz

uxhixvfz1#

在客户端发出token请求时,该请求将通过RegisteredClientclientIdclientSecret进行身份验证(例如客户端身份验证)。由于请求者是客户端,它没有USERADMIN角色,因此您的授权规则anyRequest().hasAnyRole("USER", "ADMIN")将不适用于token请求,并导致AccessDeniedException

**注意:**您可以启用跟踪日志(logging.level.org.springframework.security=trace),您将在日志中看到“拒绝访问”。

此外,Authorization Endpoint当前并不位于Spring Security的AuthorizationFilter之后,这意味着像anyRequest().hasAnyRole("USER", "ADMIN")这样的规则也不会影响authorization_code流。这是因为规范所需的请求参数的验证应该在身份验证之前进行。如果这是一个足够常见的用例,也许可以重新访问此设置。
为了在此期间解决这个问题,您可以为Authorization Request设置额外的验证,以模拟授权规则中期望发生的事情。这有点复杂,但我们可以在Spring Security中重用新的AuthorizationManager实现,以使其更容易。下面是一个示例:

@Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        var authorizationServerConfigurer =
                http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);
        var authenticationValidator =
                new OAuth2AuthorizationCodeRequestAuthenticationValidator()
                        .andThen(hasAnyRole("USER", "ADMIN"));

        authorizationServerConfigurer
            .authorizationEndpoint(withAuthenticationValidator(authenticationValidator))
            .oidc(Customizer.withDefaults());

        // ...

        return http.build();
    }

    private static Customizer<OAuth2AuthorizationEndpointConfigurer> withAuthenticationValidator(Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
        return authorizationEndpoint -> authorizationEndpoint
                .authenticationProviders(authenticationProviders -> {
                    for (AuthenticationProvider ap : authenticationProviders) {
                        if (ap instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider authenticationProvider) {
                            authenticationProvider.setAuthenticationValidator(authenticationValidator);
                        }
                    }
                });
    }

    private static Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> hasAnyRole(String... roles) {
        var authenticated = AuthenticatedAuthorizationManager.authenticated();
        var hasAnyRole = AuthorityAuthorizationManager.hasAnyRole(roles);
        return context -> {
            var authentication = context.getAuthentication();
            var principal = (Authentication) authentication.getPrincipal();

            // Skip validation check if user is not authenticated, since we expect the
            // Authorization Endpoint to perform this check and skip processing if the
            // user is not authenticated.
            if (!authenticated.check(() -> principal, context).isGranted()) {
                return;
            }

            // If the user is authenticated, reject request if user does not have the
            // necessary role(s).
            if (!hasAnyRole.check(() -> principal, context).isGranted()) {
                var cause = new AccessDeniedException("Access Denied");
                var error = new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED);
                throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, cause, context.getAuthentication());
            }
        };
    }

值得注意的是,access_deniedOAuth2Error必须由OAuth2客户端处理,因为这个错误将在重定向回客户端时给出。在默认示例中,它只是以无限重定向结束,因为我没有设置我的示例来处理这个错误。
有关自定义授权端点验证的详细信息,请参阅文档。

相关问题