2
0

bearer-tokens.adoc 8.8 KB

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