11/*
2- * Copyright 2002-2021 the original author or authors.
2+ * Copyright 2002-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
4646import com .nimbusds .jwt .JWTClaimsSet ;
4747import com .nimbusds .jwt .SignedJWT ;
4848
49+ import org .springframework .core .convert .converter .Converter ;
4950import org .springframework .security .oauth2 .jose .jws .SignatureAlgorithm ;
5051import org .springframework .util .Assert ;
5152import org .springframework .util .CollectionUtils ;
@@ -86,6 +87,19 @@ public final class NimbusJwtEncoder implements JwtEncoder {
8687
8788 private final JWKSource <SecurityContext > jwkSource ;
8889
90+ private Converter <List <JWK >, JWK > jwkSelector = (jwks )->{
91+ if (jwks .size () > 1 ) {
92+ throw new JwtEncodingException (String .format (
93+ "Failed to select a key since there are multiple for the signing algorithm [%s]; " +
94+ "please specify a selector in NimbusJwsEncoder#setJwkSelector" ,jwks .get (0 ).getAlgorithm ()));
95+ }
96+ if (jwks .isEmpty ()) {
97+ throw new JwtEncodingException (
98+ String .format (ENCODING_ERROR_MESSAGE_TEMPLATE , "Failed to select a JWK signing key" ));
99+ }
100+ return jwks .get (0 );
101+ };
102+
89103 /**
90104 * Constructs a {@code NimbusJwtEncoder} using the provided parameters.
91105 * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
@@ -94,6 +108,18 @@ public NimbusJwtEncoder(JWKSource<SecurityContext> jwkSource) {
94108 Assert .notNull (jwkSource , "jwkSource cannot be null" );
95109 this .jwkSource = jwkSource ;
96110 }
111+ /**
112+ * Use this strategy to reduce the list of matching JWKs down to a since one.
113+ * <p> For example, you can call {@code setJwkSelector(List::getFirst)} in order
114+ * to have this encoder select the first match.
115+ *
116+ * <p> By default, the class with throw an exception if there is more than one result.
117+ * @since 6.5
118+ */
119+ public void setJwkSelector (Converter <List <JWK >, JWK > jwkSelector ) {
120+ if (null !=jwkSelector )
121+ this .jwkSelector = jwkSelector ;
122+ }
97123
98124 @ Override
99125 public Jwt encode (JwtEncoderParameters parameters ) throws JwtEncodingException {
@@ -123,18 +149,7 @@ private JWK selectJwk(JwsHeader headers) {
123149 throw new JwtEncodingException (String .format (ENCODING_ERROR_MESSAGE_TEMPLATE ,
124150 "Failed to select a JWK signing key -> " + ex .getMessage ()), ex );
125151 }
126-
127- if (jwks .size () > 1 ) {
128- throw new JwtEncodingException (String .format (ENCODING_ERROR_MESSAGE_TEMPLATE ,
129- "Found multiple JWK signing keys for algorithm '" + headers .getAlgorithm ().getName () + "'" ));
130- }
131-
132- if (jwks .isEmpty ()) {
133- throw new JwtEncodingException (
134- String .format (ENCODING_ERROR_MESSAGE_TEMPLATE , "Failed to select a JWK signing key" ));
135- }
136-
137- return jwks .get (0 );
152+ return this .jwkSelector .convert (jwks );
138153 }
139154
140155 private String serialize (JwsHeader headers , JwtClaimsSet claims , JWK jwk ) {
0 commit comments