Spring Boot 2 OIDC (OAuth2) -Client / Ressourcenserver, der das Zugriffstoken im WebClient nicht weitergibt

codependent

Beispielprojekt auf Github verfügbar

Ich habe zwei Spring Boot 2-Anwendungen2 erfolgreich als Client- / Ressourcenserver für Keycloak konfiguriert, und SSO zwischen ihnen ist in Ordnung.

Außerdem teste ich authentifizierte REST-Aufrufe untereinander und verteile das Zugriffstoken als Authorization: Bearer ACCESS_TOKENHeader.

Nach dem Start von Keycloak und den Anwendungen greife ich entweder auf http: // localhost: 8181 / resource-server1 oder http: // localhost: 8282 / resource-server-2 zu und authentifiziere mich auf der Keycloak-Anmeldeseite. Der HomeController verwendet einen WebClient, um den HelloRestController /rest/helloEndpunkt des anderen Ressourcenservers aufzurufen .

@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"
    }

}

Dieser Aufruf gibt eine 302 zurück, da die Anforderung nicht authentifiziert ist (das Zugriffstoken wird nicht weitergegeben):

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
    }
}

Wie Sie sehen können, setze ich eine ServletBearerExchangeFilterFunctionin der WebClient. Folgendes habe ich beim Debuggen gesehen:

Geben Sie hier die Bildbeschreibung ein

Der SubscriberContext setzt nichts, weil er authentication.getCredentials() instanceof AbstractOAuth2Tokenfalsch ist. Eigentlich ist es nur ein String:

public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {

    ... 

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

Was ist das Problem hier? Wie kann ich die Weitergabe des Tokens automatisieren?

codependent

Es scheint keine sofort einsatzbereite Lösung für reine OAuth2 / OIDC-Anmeldeanwendungen zu geben. Ich habe hierfür ein Github-Problem erstellt .

In der Zwischenzeit habe ich eine spezifische erstellt ServletBearerExchangeFilterFunction, die das Zugriffstoken von der abruft OAuth2AuthorizedClientRepository.

Dies ist meine benutzerdefinierte Lösung:

    @Autowired
    lateinit var oAuth2AuthorizedClientRepository: OAuth2AuthorizedClientRepository

    @Bean
    fun webClient(): WebClient {
        val servletBearerExchangeFilterFunction = ServletBearerExchangeFilterFunction("resource-server-1", oAuth2AuthorizedClientRepository)
        return WebClient.builder()
                .filter(servletBearerExchangeFilterFunction)
                .build()
    }

    ...

    private fun keycloakClientRegistration(): ClientRegistration {
        return ClientRegistration
                .withRegistrationId("resource-server-1")
                ...
const val SECURITY_REACTOR_CONTEXT_ATTRIBUTES_KEY = "org.springframework.security.SECURITY_CONTEXT_ATTRIBUTES"

class ServletBearerExchangeFilterFunction(private val clientRegistrationId: String,
                                          private val oAuth2AuthorizedClientRepository: OAuth2AuthorizedClientRepository?) : ExchangeFilterFunction {

    /**
     * {@inheritDoc}
     */
    override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
        return oauth2Token()
                .map { token: AbstractOAuth2Token -> bearer(request, token) }
                .defaultIfEmpty(request)
                .flatMap { request: ClientRequest -> next.exchange(request) }
    }

    private fun oauth2Token(): Mono<AbstractOAuth2Token> {
        return Mono.subscriberContext()
                .flatMap { ctx: Context -> currentAuthentication(ctx) }
                .map { authentication ->
                    val authorizedClient = oAuth2AuthorizedClientRepository?.loadAuthorizedClient<OAuth2AuthorizedClient>(clientRegistrationId, authentication, null)
                    if (authorizedClient != null) {
                        authorizedClient.accessToken
                    } else {
                        Unit
                    }
                }
                .filter { it != null }
                .cast(AbstractOAuth2Token::class.java)
    }

    private fun currentAuthentication(ctx: Context): Mono<Authentication> {
        return Mono.justOrEmpty(getAttribute(ctx, Authentication::class.java))
    }

    private fun <T> getAttribute(ctx: Context, clazz: Class<T>): T? { // NOTE: SecurityReactorContextConfiguration.SecurityReactorContextSubscriber adds this key
        if (!ctx.hasKey(SECURITY_REACTOR_CONTEXT_ATTRIBUTES_KEY)) {
            return null
        }
        val attributes: Map<Class<T>, T> = ctx[SECURITY_REACTOR_CONTEXT_ATTRIBUTES_KEY]
        return attributes[clazz]
    }

    private fun bearer(request: ClientRequest, token: AbstractOAuth2Token): ClientRequest {
        return ClientRequest.from(request)
                .headers { headers: HttpHeaders -> headers.setBearerAuth(token.tokenValue) }
                .build()
    }

}

Dieser Artikel stammt aus dem Internet. Bitte geben Sie beim Nachdruck die Quelle an.

Bei Verstößen wenden Sie sich bitte [email protected] Löschen.

bearbeiten am
0

Lass mich ein paar Worte sagen

0Kommentare
LoginNach der Teilnahme an der Überprüfung

Verwandte Artikel

<Spring Security> Zugriffstoken nicht im Header enthalten, wenn der Client den Ressourcenserver aufruft

Spring Boot Oauth2 - Wo ist das Zugriffstoken gespeichert?

spring boot oauth2 Konfiguration: Ressourcenserver bleibt ungeschützt

Spring Boot Security, Oauth2 ersetzt das Zugriffstoken durch ein langlebiges Token von Facebook

Spring Security OAuth2 reiner Ressourcenserver

OAuth2 erhält das Zugriffstoken nicht

Ist es empfehlenswert, das Zugriffstoken im Browser localStorage in angle-oauth2-oidc zu speichern? Wenn nicht, was ist die empfohlene Option?

Spring OAuth2 XML-Konfiguration für Client und Ressourcenserver

Wird Spring Boot WebClient OAuth2 client_credentials unterstützt?

Spring-Boot oauth2 teilt Autorisierungsserver und Ressourcenserver auf

Autorisierung funktioniert nicht in Gateway mit OAuth2-Client + Ressourcenserver

OAuth2: Verhindern Sie den Missbrauch von Zugriffstoken durch einen legitimen Ressourcenserver

Spring Security oauth2 Login und Ressourcenserver in derselben Anwendung

Wie erhalte ich das Zugriffstoken über den OAuth2AuthorizedClientService im Client Credentials Flow?

was genau vom Ressourcenserver an den Authentifizierungsserver gesendet wird Im Frühjahr Sicherheit oauth2 während der Tokenvalidierung

Spring Boot mit Sicherheit OAuth2 - Wie verwende ich den Ressourcenserver mit dem Web-Anmeldeformular?

Hallo, ich bin neu bei Spring Boot. Ich muss einen Rest-Template-Client erstellen, der das oauth2-Zugriffstoken von einem mir bereitgestellten API-Link abrufen kann

Google OAuth2 Überprüfen Sie das Zugriffstoken im Server Flow

Spring OAuth2 / OIDC - OAuth2AuthorizedClientService registriert den Benutzerprinzipal nicht (authroizedClient)

Spring Boot 2: Das Pluszeichen (+) wird im Abfrageparameter nicht codiert

spring boot oauth2 - kann bei Verwendung von Basic Auth kein Zugriffstoken erhalten

Wie gebe ich das Zugriffstoken bei der Anmeldung mit oauth2 in drf zurück?

Spring OAuth2 wurde implementiert und erhält das gleiche Zugriffstoken von verschiedenen Geräten

Wie erhalte ich ein Zugriffstoken direkt nach der Anmeldung bei successHandler in spring oauth2 security?

OAuth2 und SSO mit angle-oauth2-oidc rufen kein Zugriffstoken ab

Spring Boot 2 + Oauth2 - Sichern der Restanrufe in Microservices

Lassen Sie angle-oauth2-oidc das Zugriffstoken von anderen Registerkarten abrufen

Spring Boot Security Oauth2 - Hinzufügen dynamischer OIDC-Parameter

Spring Boot 2 WebClient ruft Kontextparameter im Abonnenten ab

TOP Liste

  1. 1

    So legen Sie mit dem Interface Builder unterschiedliche führende Speicherplätze für unterschiedliche Geräte fest

  2. 2

    Fügen Sie eine weitere Schaltfläche zu gwt Suggest Box hinzu

  3. 3

    Wie konvertiere ich einen Vektor von Bytes (u8) in eine Zeichenfolge?

  4. 4

    Wie kann ich in SCSS mehrere Klassen zu einer einzigen kombinieren?

  5. 5

    Wie konvertiert man einen Datenrahmen im langen Format in eine Liste mit einem geeigneten Format?

  6. 6

    Speichern Sie ein MPAndroidChart-Diagramm in einem Bild, ohne es in einer Aktivität anzuzeigen

  7. 7

    Gruppieren Sie Datenrahmenspalten nach ihrem Datum (die Spaltentitel enthalten) und fassen Sie die Instanzen von Einsen und Nullen in R . zusammen

  8. 8

    Tomcat - Leiten Sie den alten Kontextstamm zum neuen Kontextstamm um

  9. 9

    Eclipse Oxygen - Projekte verschwinden

  10. 10

    Wie wählt man Unterschiede mit drei Tabellen aus?

  11. 11

    Tic Tac Toe-Spiel im React-Reset-Button funktioniert nicht

  12. 12

    So berechnen Sie die Verfügbarkeit von Anwendungen (SLA)

  13. 13

    ElasticSearch BulkShardRequest ist aufgrund von org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor fehlgeschlagen

  14. 14

    Wie kann ich den Kaskadenmodus global einstellen?

  15. 15

    Python: Spalten mit demselben Namen zusammenführen, wobei der Mindestwert beibehalten wird

  16. 16

    So erhalten Sie eine gleichmäßige Höhe für alle Eingabefelder

  17. 17

    Wie erstelle ich einen neuen übergeordneten Knoten außerhalb der .ref (/ path) in der Firebase-Echtzeitdatenbank mithilfe von Cloud-Funktionen (Typescript)?

  18. 18

    Was ist schneller: SUM über NULL oder über 0?

  19. 19

    Wie kann ich eine verschachtelte Schleife mit lapply in R ersetzen?

  20. 20

    Kann ich ein Tkinter-Canvas erstellen, das mehrere Zeilen in einem Text-Widget umfasst?

  21. 21

    Ärgerliches Problem mit yaml, das ich nicht lösen kann

heißlabel

Archiv