From 124d9964d7a5274d1dbc9298911a8b3541060036 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 20 Sep 2019 12:02:37 -0600 Subject: [PATCH] Document Bearer Token Propagation Fixes gh-7461 --- .../reactive/oauth2/resource-server.adoc | 46 +++++++++++ .../preface/oauth2-resourceserver.adoc | 76 +++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc index 9bba4fb40a..99a70c4532 100644 --- a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc @@ -1000,3 +1000,49 @@ ReactiveOpaqueTokenIntrospector introspector() { } ---- +== Bearer Token Propagation + +Now that you're in possession of a bearer token, it might be handy to pass that to downstream services. +This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServerBearerExchangeFilterFunction.html[ServerBearerExchangeFilterFunction]`, which you can see in the following example: + +[source,java] +---- +@Bean +public WebClient rest() { + return WebClient.builder() + .filter(new ServerBearerExchangeFilterFunction()) + .build(); +} +---- + +When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential. +Then, it will propagate that token in the `Authorization` header. + +For example: + +[source,java] +---- +this.rest.get() + .uri("https://other-service.example.com/endpoint") + .retrieve() + .bodyToMono(String.class) +---- + +Will invoke the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you. + +In places where you need to override this behavior, it's a simple matter of supplying the header yourself, like so: + +[source,java] +---- +this.rest.get() + .uri("https://other-service.example.com/endpoint") + .headers(headers -> headers.setBearerAuth(overridingToken)) + .retrieve() + .bodyToMono(String.class) +---- + +In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain. + +[NOTE] +Unlike the https://docs.spring.io/spring-security/site/docs/current-SNAPSHOT/api/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired. +To obtain this level of support, please use the OAuth 2.0 Client filter. diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-resourceserver.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-resourceserver.adoc index c6bfd557e1..8ec0647023 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-resourceserver.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-resourceserver.adoc @@ -1150,3 +1150,79 @@ OpaqueTokenIntrospector introspector() { Thus far we have only taken a look at the most basic authentication configuration. Let's take a look at a few slightly more advanced options for configuring authentication. + +=== Bearer Token Propagation + +Now that you're in possession of a bearer token, it might be handy to pass that to downstream services. +This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServletBearerExchangeFilterFunction.html[ServletBearerExchangeFilterFunction]`, which you can see in the following example: + +[source,java] +---- +@Bean +public WebClient rest() { + return WebClient.builder() + .filter(new ServletBearerExchangeFilterFunction()) + .build(); +} +---- + +When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential. +Then, it will propagate that token in the `Authorization` header. + +For example: + +[source,java] +---- +this.rest.get() + .uri("https://other-service.example.com/endpoint") + .retrieve() + .bodyToMono(String.class) + .block() +---- + +Will invoke the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you. + +In places where you need to override this behavior, it's a simple matter of supplying the header yourself, like so: + +[source,java] +---- +this.rest.get() + .uri("https://other-service.example.com/endpoint") + .headers(headers -> headers.setBearerAuth(overridingToken)) + .retrieve() + .bodyToMono(String.class) + .block() +---- + +In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain. + +[NOTE] +Unlike the https://docs.spring.io/spring-security/site/docs/current-SNAPSHOT/api/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired. +To obtain this level of support, please use the OAuth 2.0 Client filter. + +==== `RestTemplate` support + +There is no dedicated support for `RestTemplate` at the moment, but you can achieve propagation quite simply with your own interceptor: + +[source,java] +---- +@Bean +RestTemplate rest() { + RestTemplate rest = new RestTemplate(); + rest.getInterceptors().add((request, body, execution) -> { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return execution.execute(request, body); + } + + if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) { + return execution.execute(request, body); + } + + AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials(); + request.getHeaders().setBearerAuth(token.getTokenValue()); + return execution.execute(request, body); + }); + return rest; +} +----