oauth2.adoc 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. = OAuth 2.0 Changes
  2. == Validate `typ` Header with `JwtTypeValidator`
  3. `NimbusJwtDecoder` in Spring Security 7 will move `typ` header validation to `JwtTypeValidator` instead of relying on Nimbus.
  4. This brings it in line with `NimbusJwtDecoder` validating claims instead of relying on Nimbus to validate them.
  5. If you are changing Nimbus's default type validation in a `jwtProcessorCustomizer` method, then you should move that to `JwtTypeValidator` or an implementation of `OAuth2TokenValidator` of your own.
  6. To check if you are prepared for this change, add the default `JwtTypeValidator` to your list of validators, as this will be included by default in 7:
  7. [tabs]
  8. ======
  9. Java::
  10. +
  11. [source,java,role="primary"]
  12. ----
  13. @Bean
  14. JwtDecoder jwtDecoder() {
  15. NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  16. .validateTypes(false) <1>
  17. // ... your remaining configuration
  18. .build();
  19. jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
  20. new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2>
  21. return jwtDecoder;
  22. }
  23. ----
  24. Kotlin::
  25. +
  26. [source,kotlin,role="secondary"]
  27. ----
  28. @Bean
  29. fun jwtDecoder(): JwtDecoder {
  30. val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  31. .validateTypes(false) <1>
  32. // ... your remaining configuration
  33. .build()
  34. jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
  35. JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2>
  36. return jwtDecoder
  37. }
  38. ----
  39. ======
  40. <1> - Switch off Nimbus verifying the `typ` (this will be off by default in 7)
  41. <2> - Add the default `typ` validator (this will be included by default in 7)
  42. Note the default value verifies that the `typ` value either be `JWT` or not present, which is the same as the Nimbus default.
  43. It is also aligned with https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.9[RFC 7515] which states that `typ` is optional.
  44. === I'm Using A `DefaultJOSEObjectTypeVerifier`
  45. If you have something like the following in your configuration:
  46. [tabs]
  47. ======
  48. Java::
  49. +
  50. [source,java,role="primary"]
  51. ----
  52. @Bean
  53. JwtDecoder jwtDecoder() {
  54. NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  55. .jwtProcessorCustomizer((c) -> c
  56. .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE"))
  57. )
  58. .build();
  59. return jwtDecoder;
  60. }
  61. ----
  62. Kotlin::
  63. +
  64. [source,kotlin,role="secondary"]
  65. ----
  66. @Bean
  67. fun jwtDecoder(): JwtDecoder {
  68. val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  69. .jwtProcessorCustomizer {
  70. it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE"))
  71. }
  72. .build()
  73. return jwtDecoder
  74. }
  75. ----
  76. ======
  77. Then change this to:
  78. [tabs]
  79. ======
  80. Java::
  81. +
  82. [source,java,role="primary"]
  83. ----
  84. @Bean
  85. JwtDecoder jwtDecoder() {
  86. NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  87. .validateTypes(false)
  88. .build();
  89. jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
  90. new JwtIssuerValidator(location), new JwtTypeValidator("JOSE")));
  91. return jwtDecoder;
  92. }
  93. ----
  94. Kotlin::
  95. +
  96. [source,kotlin,role="secondary"]
  97. ----
  98. @Bean
  99. fun jwtDecoder(): JwtDecoder {
  100. val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  101. .validateTypes(false)
  102. .build()
  103. jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
  104. JwtIssuerValidator(location), JwtTypeValidator("JOSE")))
  105. return jwtDecoder
  106. }
  107. ----
  108. ======
  109. To indicate that the `typ` header is optional, use `#setAllowEmpty(true)` (this is the equivalent of including `null` in the list of allowed types in `DefaultJOSEObjectTypeVerifier`).
  110. === I want to opt-out
  111. If you want to keep doing things the way that you are, then the steps are similar, just in reverse:
  112. [tabs]
  113. ======
  114. Java::
  115. +
  116. [source,java,role="primary"]
  117. ----
  118. @Bean
  119. JwtDecoder jwtDecoder() {
  120. NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  121. .validateTypes(true) <1>
  122. .jwtProcessorCustomizer((c) -> c
  123. .setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE"))
  124. )
  125. .build();
  126. jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
  127. new JwtTimestampValidator(), new JwtIssuerValidator(location))); <2>
  128. return jwtDecoder;
  129. }
  130. ----
  131. Kotlin::
  132. +
  133. [source,kotlin,role="secondary"]
  134. ----
  135. @Bean
  136. fun jwtDecoder(): JwtDecoder {
  137. val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
  138. .validateTypes(true) <1>
  139. .jwtProcessorCustomizer {
  140. it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE"))
  141. }
  142. .build()
  143. jwtDecoder.setJwtValidator(DelegatingOAuth2TokenValidator(
  144. JwtTimestampValidator(), JwtIssuerValidator(location))) <2>
  145. return jwtDecoder
  146. }
  147. ----
  148. ======
  149. <1> - leave Nimbus type verification on
  150. <2> - specify the list of validators you need, excluding `JwtTypeValidator`
  151. For additional guidance, please see the xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-validation[JwtDecoder Validators] section in the reference.
  152. == Opaque Token Credentials Will Be Encoded For You
  153. In order to comply more closely with the Introspection RFC, Spring Security's opaque token support will encode the client id and secret before creating the authorization header.
  154. This change means you will no longer have to encode the client id and secret yourself.
  155. If your client id or secret contain URL-unsafe characters, then you can prepare yourself for this change by doing the following:
  156. === Replace Usage of `introspectionClientCredentials`
  157. Since Spring Security can now do the encoding for you, replace xref:servlet/oauth2/resource-server/opaque-token.adoc#oauth2resourceserver-opaque-introspectionuri-dsl[using `introspectionClientCredentials`] with publishing the following `@Bean`:
  158. [tabs]
  159. ======
  160. Java::
  161. +
  162. [source,java,role="primary"]
  163. ----
  164. @Bean
  165. OpaqueTokenIntrospector introspector() {
  166. return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri)
  167. .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build();
  168. }
  169. ----
  170. Kotlin::
  171. +
  172. [source,kotlin,role="secondary"]
  173. ----
  174. @Bean
  175. fun introspector(): OpaqueTokenIntrospector {
  176. return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri)
  177. .clientId(unencodedClientId).clientSecret(unencodedClientSecret).build()
  178. }
  179. ----
  180. ======
  181. The above will be the default in 7.0.
  182. If this setting gives you trouble or you cannot apply it for now, you can use the `RestOperations` constructor instead:
  183. [tabs]
  184. ======
  185. Java::
  186. +
  187. [source,java,role="primary"]
  188. ----
  189. @Bean
  190. OpaqueTokenIntrospector introspector() {
  191. RestTemplate rest = new RestTemplate();
  192. rest.addInterceptor(new BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret));
  193. return new SpringOpaqueTokenIntrospector(introspectionUri, rest);
  194. }
  195. ----
  196. Kotlin::
  197. +
  198. [source,kotlin,role="secondary"]
  199. ----
  200. @Bean
  201. fun introspector(): OpaqueTokenIntrospector {
  202. val rest = RestTemplate()
  203. rest.addInterceptor(BasicAuthenticationInterceptor(encodedClientId, encodedClientSecret))
  204. return SpringOpaqueTokenIntrospector(introspectionUri, rest)
  205. }
  206. ----
  207. ======