Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -163,26 +163,26 @@ private OidcIdToken createOidcToken(ClientRegistration clientRegistration, Strin
}

private JwtDecoder getJwtDecoder(ClientRegistration clientRegistration) {
JwtDecoder jwtDecoder = this.jwtDecoders.get(clientRegistration.getRegistrationId());
final String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
if (!StringUtils.hasText(jwkSetUri)) {
OAuth2Error oauth2Error = new OAuth2Error(
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
"Failed to find a Signature Verifier for Client Registration: '" +
clientRegistration.getRegistrationId() + "'. Check to ensure you have configured the JwkSet URI.",
null
);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
JwtDecoder jwtDecoder = this.jwtDecoders.get(jwkSetUri);
if (jwtDecoder == null) {
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
OAuth2Error oauth2Error = new OAuth2Error(
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
"Failed to find a Signature Verifier for Client Registration: '" +
clientRegistration.getRegistrationId() + "'. Check to ensure you have configured the JwkSet URI.",
null
);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// TODO This should look at the Cache-Control header so to expire old jwtDecoders.
// Canvas looks to rotate it's keys monthly.
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder decoderBuilder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithm(SignatureAlgorithm.from(JwsAlgorithms.RS256));
if (restOperations != null) {
decoderBuilder.restOperations(restOperations);
}
jwtDecoder = decoderBuilder.build();
this.jwtDecoders.put(clientRegistration.getRegistrationId(), jwtDecoder);
this.jwtDecoders.put(jwkSetUri, jwtDecoder);
}

Copilot AI Jan 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a potential race condition in the check-then-act pattern. If multiple threads call this method concurrently with the same jwkSetUri, they could all pass the null check at line 177 and create multiple JwtDecoder instances, with only the last one winning. While ConcurrentHashMap is thread-safe for individual operations, the composite operation (check if absent, then put) is not atomic.

Consider using computeIfAbsent to ensure atomic creation of JwtDecoder instances:

return this.jwtDecoders.computeIfAbsent(jwkSetUri, uri -> {
NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder decoderBuilder = NimbusJwtDecoder.withJwkSetUri(uri).jwsAlgorithm(SignatureAlgorithm.from(JwsAlgorithms.RS256));
if (restOperations != null) {
decoderBuilder.restOperations(restOperations);
}
return decoderBuilder.build();
});

This ensures only one JwtDecoder is created per unique jwkSetUri, even under concurrent access.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

return jwtDecoder;
}
Expand Down