Add RFC 9068 Support
Closes gh-13185
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
= OAuth 2.0 Changes
|
||||
|
||||
== Validate `typ` Header with `JwtTypeValidator`
|
||||
|
||||
`NimbusJwtDecoder` in Spring Security 7 will move `typ` header validation to `JwtTypeValidator` intsead of relying on Nimbus.
|
||||
This brings it in line with `NimbusJwtDecoder` validating claims instead of relying on Nimbus to validate them.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.validateTypes(false) <1>
|
||||
// ... your remaining configuration
|
||||
.build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
|
||||
new JwtIssuerValidator(location), JwtTypeValidator.jwt())); <2>
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.validateTypes(false) <1>
|
||||
// ... your remaining configuration
|
||||
.build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
|
||||
JwtIssuerValidator(location), JwtTypeValidator.jwt())) <2>
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> - Switch off Nimbus verifying the `typ` (this will be off by default in 7)
|
||||
<2> - Add the default `typ` validator (this will be included by default in 7)
|
||||
|
||||
Note the default value verifies that the `typ` value either be `JWT` or not present, which is the same as the Nimbus default.
|
||||
It is also aligned with https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.9[RFC 7515] which states that `typ` is optional.
|
||||
|
||||
|
||||
=== I'm Using A `DefaultJOSEObjectTypeVerifier`
|
||||
|
||||
If you have something like the following in your configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.jwtProcessorCustomizer((c) -> c
|
||||
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE"))
|
||||
)
|
||||
.build();
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.jwtProcessorCustomizer {
|
||||
it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE"))
|
||||
}
|
||||
.build()
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Then change this to:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.validateTypes(false)
|
||||
.build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
|
||||
new JwtIssuerValidator(location), new JwtTypeValidator("JOSE")));
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.validateTypes(false)
|
||||
.build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(
|
||||
JwtIssuerValidator(location), JwtTypeValidator("JOSE")))
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
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`).
|
||||
|
||||
=== I want to opt-out
|
||||
|
||||
If you want to keep doing things the way that you are, then the steps are similar, just in reverse:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.validateTypes(true) <1>
|
||||
.jwtProcessorCustomizer((c) -> c
|
||||
.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>("JOSE"))
|
||||
)
|
||||
.build();
|
||||
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
|
||||
new JwtTimestampValidator(), new JwtIssuerValidator(location))); <2>
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(location)
|
||||
.validateTypes(true) <1>
|
||||
.jwtProcessorCustomizer {
|
||||
it.setJWSTypeVerifier(DefaultJOSEObjectTypeVerifier("JOSE"))
|
||||
}
|
||||
.build()
|
||||
jwtDecoder.setJwtValidator(DelegatingOAuth2TokenValidator(
|
||||
JwtTimestampValidator(), JwtIssuerValidator(location))) <2>
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> - leave Nimbus type verification on
|
||||
<2> - specify the list of validators you need, excluding `JwtTypeValidator`
|
||||
|
||||
For additional guidance, please see the xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-validation[JwtDecoder Validators] section in the reference.
|
||||
@@ -936,6 +936,46 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
By default, Resource Server configures a clock skew of 60 seconds.
|
||||
====
|
||||
|
||||
[[webflux-oauth2resourceserver-validation-rfc9068]]
|
||||
=== Configuring RFC 9068 Validation
|
||||
|
||||
If you need to require tokens that meet https://datatracker.ietf.org/doc/rfc9068/[RFC 9068], you can configure validation in the following way:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri)
|
||||
.validateTypes(false).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
|
||||
.audience("https://audience.example.org")
|
||||
.clientId("client-identifier")
|
||||
.issuer("https://issuer.example.org").build());
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri)
|
||||
.validateTypes(false).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
|
||||
.audience("https://audience.example.org")
|
||||
.clientId("client-identifier")
|
||||
.issuer("https://issuer.example.org").build())
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-validation-custom]]
|
||||
==== Configuring a Custom Validator
|
||||
|
||||
|
||||
@@ -1213,6 +1213,46 @@ fun jwtDecoder(): JwtDecoder {
|
||||
[NOTE]
|
||||
By default, Resource Server configures a clock skew of 60 seconds.
|
||||
|
||||
[[oauth2resourceserver-jwt-validation-rfc9068]]
|
||||
=== Configuring RFC 9068 Validation
|
||||
|
||||
If you need to require tokens that meet https://datatracker.ietf.org/doc/rfc9068/[RFC 9068], you can configure validation in the following way:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuerUri)
|
||||
.validateTypes(false).build();
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
|
||||
.audience("https://audience.example.org")
|
||||
.clientId("client-identifier")
|
||||
.issuer("https://issuer.example.org").build());
|
||||
return jwtDecoder;
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
val jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuerUri)
|
||||
.validateTypes(false).build()
|
||||
jwtDecoder.setJwtValidator(JwtValidators.createAtJwtValidator()
|
||||
.audience("https://audience.example.org")
|
||||
.clientId("client-identifier")
|
||||
.issuer("https://issuer.example.org").build())
|
||||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-validation-custom]]
|
||||
=== Configuring a Custom Validator
|
||||
|
||||
|
||||
Reference in New Issue
Block a user