Skip to content

The selectJwk method of NimbusJwtEncoder class should not throw Exception when jwks size great than one #16170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
douxiaofeng99 opened this issue Nov 26, 2024 · 3 comments · May be fixed by GuusArts/spring-security#4 or GuusArts/spring-security#14
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue type: enhancement A general enhancement

Comments

@douxiaofeng99
Copy link
Contributor

Describe the bug
I implemented a rotating JWKS using Redis, where a new JWK is generated at regular intervals, and the old JWKs are also retained for a certain period. In this scenario, the selectJwk method of NimbusJwtEncoder retrieves multiple JWKs when selecting the JWKs. This happens because the jwkSelector only sets the algorithm but does not provide any kid, leading to the exception being thrown below.
image

To Reproduce
Steps to reproduce the behavior.

Expected behavior
remove the block:
if (jwks.size() > 1) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Found multiple JWK signing keys for algorithm '" + headers.getAlgorithm().getName() + "'"));
}
because in the last the method, return jwks.get(0); already use first jwk.

Sample

A link to a GitHub repository with a minimal, reproducible sample.

Reports that include a sample will take priority over reports that do not.
At times, we may require a sample, so it is good to try and include a sample up front.

@douxiaofeng99 douxiaofeng99 added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Nov 26, 2024
@jzheaux
Copy link
Contributor

jzheaux commented Dec 9, 2024

Thanks for reaching out, @douxiaofeng99.

Note that while you are able to specify a kid or a thumbprint in the JwsHeader, I agree that NimbusJwtEncoder could be improved when it comes to supporting key rotation when those are not specified.

I think a good start would be to introduce a setter that allows applications to indicate which JWK to pick. Something like:

setJwkSelector(Converter<List<JWK>, JWK> selector);

Then applications could do:

setJwkSelector(List::getFirst); // or
setJwkSelector(List::getLast);  // or
setJwkSelector((jwks) -> jwks.stream().max(Comparator.comparing(JWK::getIssueTime)).orElseThrow())

Given the following from the RFC:

The value of the "keys" parameter is an array of JWK values. By
default, the order of the JWK values within the array does not imply
an order of preference among them, although applications of JWK Sets
can choose to assign a meaning to the order for their purposes, if
desired.

and to stay passive, the default should continue to be an exception. That said, I think it would also be helpful to change the error message to be something like:

Failed to select a key since there are multiple for the signing algorithm [%s]; please specify a selector in NimbusJwsEncoder#setJwkSelector

How well would this work for you and are you able to submit a PR that adds this?

@jzheaux jzheaux added type: enhancement A general enhancement status: ideal-for-contribution An issue that we actively are looking for someone to help us with in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Dec 9, 2024
@douxiaofeng99
Copy link
Contributor Author

douxiaofeng99 commented Dec 16, 2024

Hello, @jzheaux: I understand your point above as adding a selector for the JWKs list to choose which JWK to use after fetching them. The selectJwk method has been updated as follows:

private JWK selectJwk(JwsHeader headers) {
    List<JWK> jwks;
    try {
        JWKSelector jwkSelector = new JWKSelector(createJwkMatcher(headers));
        jwks = this.jwkSource.get(jwkSelector, null);
    } catch (Exception ex) {
        throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
                "Failed to select a JWK signing key -> " + ex.getMessage()), ex);
    }
    if (jwks.isEmpty()) {
        throw new JwtEncodingException(
                String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Failed to select a JWK signing key"));
    }
    if (this.jwkSelector != null) {
        return this.jwkSelector.convert(jwks);
    }
    if (jwks.size() > 1) {
        throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
                "Found multiple JWK signing keys for algorithm '" + headers.getAlgorithm().getName() + "'"));
    }
    return jwks.get(0);
}

The main change involves moving the null-check earlier in the logic. If a converter is set, it selects and returns the JWK using the converter. If no converter is present, the method continues with the original logic.

If that’s the case, I will submit a PR.

douxiaofeng99 pushed a commit to douxiaofeng99/spring-security that referenced this issue Feb 11, 2025
Signed-off-by: douxiaofeng99 <[email protected]>
douxiaofeng99 added a commit to douxiaofeng99/spring-security that referenced this issue Feb 11, 2025
Signed-off-by: douxiaofeng99 <[email protected]>
douxiaofeng99 added a commit to douxiaofeng99/spring-security that referenced this issue Feb 14, 2025
jzheaux pushed a commit to douxiaofeng99/spring-security that referenced this issue Feb 14, 2025
jzheaux added a commit to douxiaofeng99/spring-security that referenced this issue Feb 14, 2025
Make so that it runs only when selection is needed.
Require the provided selector be non-null.
Add Tests.

Issue spring-projectsgh-16170
jzheaux added a commit that referenced this issue Feb 18, 2025
Make so that it runs only when selection is needed.
Require the provided selector be non-null.
Add Tests.

Issue gh-16170
@jzheaux jzheaux removed the status: ideal-for-contribution An issue that we actively are looking for someone to help us with label Feb 18, 2025
@jzheaux jzheaux added the status: duplicate A duplicate of another issue label Feb 18, 2025
@jzheaux
Copy link
Contributor

jzheaux commented Feb 18, 2025

Superceded by #16570

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment