Spring Boot 2 OIDC(OAuth2)客户端/资源服务器未在WebClient中传播访问令牌

问题描述 投票:2回答:1

Sample project available on Github

我已经成功配置了两个Spring Boot 2 application2作为针对Keycloak和SSO的客户端/资源服务器。

此外,我正在测试彼此之间经过身份验证的REST调用,并将访问令牌传播为Authorization: Bearer ACCESS_TOKEN标头。

启动Keycloak和应用程序后,我访问http://localhost:8181/resource-server1http://localhost:8282/resource-server-2并在Keycloak登录页面中进行身份验证。 HomeController使用WebClient调用另一个资源服务器的HelloRestController /rest/hello端点。

@Controller
class HomeController(private val webClient: WebClient) {

    @GetMapping
    fun home(httpSession: HttpSession,
             @RegisteredOAuth2AuthorizedClient authorizedClient: OAuth2AuthorizedClient,
             @AuthenticationPrincipal oauth2User: OAuth2User): String {
        val authentication = SecurityContextHolder.getContext().authentication
        println(authentication)

        val pair = webClient.get().uri("http://localhost:8282/resource-server-2/rest/hello").retrieve()
                .bodyToMono(Pair::class.java)
                .block()

        return "home"
    }

}

此调用返回302,因为请求未通过身份验证(未传播访问令牌):

2019-12-25 14:09:03.737 DEBUG 8322 --- [nio-8181-exec-5] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry point

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]

OAuth2Configuration:

@Configuration
class OAuth2Config : WebSecurityConfigurerAdapter() {

    @Bean
    fun webClient(): WebClient {
        return WebClient.builder()
                .filter(ServletBearerExchangeFilterFunction())
                .build()
    }

    @Bean
    fun clientRegistrationRepository(): ClientRegistrationRepository {
        return InMemoryClientRegistrationRepository(keycloakClientRegistration())
    }

    private fun keycloakClientRegistration(): ClientRegistration {
        val clientRegistration = ClientRegistration
                .withRegistrationId("resource-server-1")
                .clientId("resource-server-1")
                .clientSecret("c00670cc-8546-4d5f-946e-2a0e998b9d7f")
                .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
                .scope("openid", "profile", "email", "address", "phone")
                .authorizationUri("http://localhost:8080/auth/realms/insight/protocol/openid-connect/auth")
                .tokenUri("http://localhost:8080/auth/realms/insight/protocol/openid-connect/token")
                .userInfoUri("http://localhost:8080/auth/realms/insight/protocol/openid-connect/userinfo")
                .userNameAttributeName(IdTokenClaimNames.SUB)
                .jwkSetUri("http://localhost:8080/auth/realms/insight/protocol/openid-connect/certs")
                .clientName("Keycloak")
                .providerConfigurationMetadata(mapOf("end_session_endpoint" to "http://localhost:8080/auth/realms/insight/protocol/openid-connect/logout"))
                .build()
        return clientRegistration
    }

    override fun configure(http: HttpSecurity) {
        http.authorizeRequests { authorizeRequests ->
            authorizeRequests
                    .anyRequest().authenticated()
        }.oauth2Login(withDefaults())
                .logout { logout ->
                    logout.logoutSuccessHandler(oidcLogoutSuccessHandler())
                }
    }

    private fun oidcLogoutSuccessHandler(): LogoutSuccessHandler? {
        val oidcLogoutSuccessHandler = OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository())
        oidcLogoutSuccessHandler.setPostLogoutRedirectUri(URI.create("http://localhost:8181/resource-server-1"))
        return oidcLogoutSuccessHandler
    }
}

如您所见,我正在ServletBearerExchangeFilterFunction中设置一个WebClient。这是我所看到的调试:

enter image description here

SubscriberContext没有设置任何内容,因为authentication.getCredentials() instanceof AbstractOAuth2Token为假。实际上,它只是一个字符串:

public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {

    ... 

    @Override
    public Object getCredentials() {
        // Credentials are never exposed (by the Provider) for an OAuth2 User
        return "";
    }

这里有什么问题?如何自动执行令牌的传播?]​​>

Github上可用的示例项目,我已经成功配置了两个Spring Boot 2 application2作为针对Keycloak和SSO的客户端/资源服务器。此外,我正在测试...

spring-boot spring-security openid-connect keycloak spring-oauth2
1个回答
0
投票

[对于纯OAuth2 / OIDC登录应用程序似乎没有开箱即用的解决方案,为此我创建了一个Github issue

© www.soinside.com 2019 - 2024. All rights reserved.