|
| 1 | += OAuth 2.0 Bearer Tokens |
| 2 | + |
| 3 | +[[oauth2resourceserver-bearertoken-resolver]] |
| 4 | +== Bearer Token Resolution |
| 5 | + |
| 6 | +By default, Resource Server looks for a bearer token in the `Authorization` header. |
| 7 | +This, however, can be customized in a handful of ways. |
| 8 | + |
| 9 | +=== Reading the Bearer Token from a Custom Header |
| 10 | + |
| 11 | +For example, you may have a need to read the bearer token from a custom header. |
| 12 | +To achieve this, you can expose a `DefaultBearerTokenResolver` as a bean, or wire an instance into the DSL, as you can see in the following example: |
| 13 | + |
| 14 | +.Custom Bearer Token Header |
| 15 | +==== |
| 16 | +.Java |
| 17 | +[source,java,role="primary"] |
| 18 | +---- |
| 19 | +@Bean |
| 20 | +BearerTokenResolver bearerTokenResolver() { |
| 21 | + DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); |
| 22 | + bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION); |
| 23 | + return bearerTokenResolver; |
| 24 | +} |
| 25 | +---- |
| 26 | +
|
| 27 | +.Kotlin |
| 28 | +[source,kotlin,role="secondary"] |
| 29 | +---- |
| 30 | +@Bean |
| 31 | +fun bearerTokenResolver(): BearerTokenResolver { |
| 32 | + val bearerTokenResolver = DefaultBearerTokenResolver() |
| 33 | + bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION) |
| 34 | + return bearerTokenResolver |
| 35 | +} |
| 36 | +---- |
| 37 | +
|
| 38 | +.Xml |
| 39 | +[source,xml,role="secondary"] |
| 40 | +---- |
| 41 | +<http> |
| 42 | + <oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver"/> |
| 43 | +</http> |
| 44 | +
|
| 45 | +<bean id="bearerTokenResolver" |
| 46 | + class="org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver"> |
| 47 | + <property name="bearerTokenHeaderName" value="Proxy-Authorization"/> |
| 48 | +</bean> |
| 49 | +---- |
| 50 | +==== |
| 51 | + |
| 52 | +Or, in circumstances where a provider is using both a custom header and value, you can use `HeaderBearerTokenResolver` instead. |
| 53 | + |
| 54 | +=== Reading the Bearer Token from a Form Parameter |
| 55 | + |
| 56 | +Or, you may wish to read the token from a form parameter, which you can do by configuring the `DefaultBearerTokenResolver`, as you can see below: |
| 57 | + |
| 58 | +.Form Parameter Bearer Token |
| 59 | +==== |
| 60 | +.Java |
| 61 | +[source,java,role="primary"] |
| 62 | +---- |
| 63 | +DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); |
| 64 | +resolver.setAllowFormEncodedBodyParameter(true); |
| 65 | +http |
| 66 | + .oauth2ResourceServer(oauth2 -> oauth2 |
| 67 | + .bearerTokenResolver(resolver) |
| 68 | + ); |
| 69 | +---- |
| 70 | +
|
| 71 | +.Kotlin |
| 72 | +[source,kotlin,role="secondary"] |
| 73 | +---- |
| 74 | +val resolver = DefaultBearerTokenResolver() |
| 75 | +resolver.setAllowFormEncodedBodyParameter(true) |
| 76 | +http { |
| 77 | + oauth2ResourceServer { |
| 78 | + bearerTokenResolver = resolver |
| 79 | + } |
| 80 | +} |
| 81 | +---- |
| 82 | +
|
| 83 | +.Xml |
| 84 | +[source,xml,role="secondary"] |
| 85 | +---- |
| 86 | +<http> |
| 87 | + <oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver"/> |
| 88 | +</http> |
| 89 | +
|
| 90 | +<bean id="bearerTokenResolver" |
| 91 | + class="org.springframework.security.oauth2.server.resource.web.HeaderBearerTokenResolver"> |
| 92 | + <property name="allowFormEncodedBodyParameter" value="true"/> |
| 93 | +</bean> |
| 94 | +---- |
| 95 | +==== |
| 96 | + |
| 97 | +== Bearer Token Propagation |
| 98 | + |
| 99 | +Now that you're resource server has validated the token, it might be handy to pass it to downstream services. |
| 100 | +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: |
| 101 | + |
| 102 | +==== |
| 103 | +.Java |
| 104 | +[source,java,role="primary"] |
| 105 | +---- |
| 106 | +@Bean |
| 107 | +public WebClient rest() { |
| 108 | + return WebClient.builder() |
| 109 | + .filter(new ServletBearerExchangeFilterFunction()) |
| 110 | + .build(); |
| 111 | +} |
| 112 | +---- |
| 113 | +
|
| 114 | +.Kotlin |
| 115 | +[source,kotlin,role="secondary"] |
| 116 | +---- |
| 117 | +@Bean |
| 118 | +fun rest(): WebClient { |
| 119 | + return WebClient.builder() |
| 120 | + .filter(ServletBearerExchangeFilterFunction()) |
| 121 | + .build() |
| 122 | +} |
| 123 | +---- |
| 124 | +==== |
| 125 | + |
| 126 | +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. |
| 127 | +Then, it will propagate that token in the `Authorization` header. |
| 128 | + |
| 129 | +For example: |
| 130 | + |
| 131 | +==== |
| 132 | +.Java |
| 133 | +[source,java,role="primary"] |
| 134 | +---- |
| 135 | +this.rest.get() |
| 136 | + .uri("https://other-service.example.com/endpoint") |
| 137 | + .retrieve() |
| 138 | + .bodyToMono(String.class) |
| 139 | + .block() |
| 140 | +---- |
| 141 | +
|
| 142 | +.Kotlin |
| 143 | +[source,kotlin,role="secondary"] |
| 144 | +---- |
| 145 | +this.rest.get() |
| 146 | + .uri("https://other-service.example.com/endpoint") |
| 147 | + .retrieve() |
| 148 | + .bodyToMono<String>() |
| 149 | + .block() |
| 150 | +---- |
| 151 | +==== |
| 152 | + |
| 153 | +Will invoke the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you. |
| 154 | + |
| 155 | +In places where you need to override this behavior, it's a simple matter of supplying the header yourself, like so: |
| 156 | + |
| 157 | +==== |
| 158 | +.Java |
| 159 | +[source,java,role="primary"] |
| 160 | +---- |
| 161 | +this.rest.get() |
| 162 | + .uri("https://other-service.example.com/endpoint") |
| 163 | + .headers(headers -> headers.setBearerAuth(overridingToken)) |
| 164 | + .retrieve() |
| 165 | + .bodyToMono(String.class) |
| 166 | + .block() |
| 167 | +---- |
| 168 | +
|
| 169 | +.Kotlin |
| 170 | +[source,kotlin,role="secondary"] |
| 171 | +---- |
| 172 | +this.rest.get() |
| 173 | + .uri("https://other-service.example.com/endpoint") |
| 174 | + .headers{ headers -> headers.setBearerAuth(overridingToken)} |
| 175 | + .retrieve() |
| 176 | + .bodyToMono<String>() |
| 177 | + .block() |
| 178 | +---- |
| 179 | +==== |
| 180 | + |
| 181 | +In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain. |
| 182 | + |
| 183 | +[NOTE] |
| 184 | +Unlike the {security-api-url}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. |
| 185 | +To obtain this level of support, please use the OAuth 2.0 Client filter. |
| 186 | + |
| 187 | +=== `RestTemplate` support |
| 188 | + |
| 189 | +There is no `RestTemplate` equivalent for `ServletBearerExchangeFilterFunction` at the moment, but you can propagate the request's bearer token quite simply with your own interceptor: |
| 190 | + |
| 191 | +==== |
| 192 | +.Java |
| 193 | +[source,java,role="primary"] |
| 194 | +---- |
| 195 | +@Bean |
| 196 | +RestTemplate rest() { |
| 197 | + RestTemplate rest = new RestTemplate(); |
| 198 | + rest.getInterceptors().add((request, body, execution) -> { |
| 199 | + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
| 200 | + if (authentication == null) { |
| 201 | + return execution.execute(request, body); |
| 202 | + } |
| 203 | +
|
| 204 | + if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) { |
| 205 | + return execution.execute(request, body); |
| 206 | + } |
| 207 | +
|
| 208 | + AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials(); |
| 209 | + request.getHeaders().setBearerAuth(token.getTokenValue()); |
| 210 | + return execution.execute(request, body); |
| 211 | + }); |
| 212 | + return rest; |
| 213 | +} |
| 214 | +---- |
| 215 | +
|
| 216 | +.Kotlin |
| 217 | +[source,kotlin,role="secondary"] |
| 218 | +---- |
| 219 | +@Bean |
| 220 | +fun rest(): RestTemplate { |
| 221 | + val rest = RestTemplate() |
| 222 | + rest.interceptors.add(ClientHttpRequestInterceptor { request, body, execution -> |
| 223 | + val authentication: Authentication? = SecurityContextHolder.getContext().authentication |
| 224 | + if (authentication != null) { |
| 225 | + execution.execute(request, body) |
| 226 | + } |
| 227 | +
|
| 228 | + if (authentication!!.credentials !is AbstractOAuth2Token) { |
| 229 | + execution.execute(request, body) |
| 230 | + } |
| 231 | +
|
| 232 | + val token: AbstractOAuth2Token = authentication.credentials as AbstractOAuth2Token |
| 233 | + request.headers.setBearerAuth(token.tokenValue) |
| 234 | + execution.execute(request, body) |
| 235 | + }) |
| 236 | + return rest |
| 237 | +} |
| 238 | +---- |
| 239 | +==== |
| 240 | + |
| 241 | + |
| 242 | +[NOTE] |
| 243 | +Unlike the {security-api-url}org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.html[OAuth 2.0 Authorized Client Manager], this filter interceptor makes no attempt to renew the token, should it be expired. |
| 244 | +To obtain this level of support, please create an interceptor using the xref:servlet/oauth2/oauth2-client.adoc#oauth2client[OAuth 2.0 Authorized Client Manager]. |
| 245 | + |
| 246 | +[[oauth2resourceserver-bearertoken-failure]] |
| 247 | +== Bearer Token Failure |
| 248 | + |
| 249 | +A bearer token may be invalid for a number of reasons. For example, the token may no longer be active. |
| 250 | + |
| 251 | +In these circumstances, Resource Server throws an `InvalidBearerTokenException`. |
| 252 | +Like other exceptions, this results in an OAuth 2.0 Bearer Token error response: |
| 253 | + |
| 254 | +[source,http request] |
| 255 | +---- |
| 256 | +HTTP/1.1 401 Unauthorized |
| 257 | +WWW-Authenticate: Bearer error_code="invalid_token", error_description="Unsupported algorithm of none", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1" |
| 258 | +---- |
| 259 | + |
| 260 | +Additionally, it is published as an `AuthenticationFailureBadCredentialsEvent`, which you can xref:servlet/authentication/events.adoc#servlet-events[listen for in your application] like so: |
| 261 | + |
| 262 | +==== |
| 263 | +.Java |
| 264 | +[source,java,role="primary"] |
| 265 | +---- |
| 266 | +@Component |
| 267 | +public class FailureEvents { |
| 268 | + @EventListener |
| 269 | + public void onFailure(AuthenticationFailureBadCredentialsEvent badCredentials) { |
| 270 | + if (badCredentials.getAuthentication() instanceof BearerTokenAuthenticationToken) { |
| 271 | + // ... handle |
| 272 | + } |
| 273 | + } |
| 274 | +} |
| 275 | +---- |
| 276 | +
|
| 277 | +.Kotlin |
| 278 | +[source,kotlin,role="secondary"] |
| 279 | +---- |
| 280 | +@Component |
| 281 | +class FailureEvents { |
| 282 | + @EventListener |
| 283 | + fun onFailure(badCredentials: AuthenticationFailureBadCredentialsEvent) { |
| 284 | + if (badCredentials.authentication is BearerTokenAuthenticationToken) { |
| 285 | + // ... handle |
| 286 | + } |
| 287 | + } |
| 288 | +} |
| 289 | +---- |
| 290 | +==== |
0 commit comments