Skip to content

Add NimbusJwtDecoder #5936

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

Merged
merged 2 commits into from
Nov 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -30,6 +30,7 @@
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
Expand All @@ -43,6 +44,8 @@
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;

import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;

/**
*
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
Expand Down Expand Up @@ -218,7 +221,7 @@ public JwtConfigurer decoder(JwtDecoder decoder) {
}

public JwtConfigurer jwkSetUri(String uri) {
this.decoder = new NimbusJwtDecoderJwkSupport(uri);
this.decoder = new NimbusJwtDecoder(withJwkSetUri(uri).build());
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
Expand Down Expand Up @@ -109,6 +109,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
Expand Down Expand Up @@ -737,7 +738,7 @@ public void getJwtDecoderWhenConfiguredWithDecoderAndJwkSetUriThenLastOneWins()
jwtConfigurer.decoder(decoder);
jwtConfigurer.jwkSetUri(JWK_SET_URI);

assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoderJwkSupport.class);
assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class);

}

Expand Down Expand Up @@ -770,7 +771,7 @@ public void getJwtDecoderWhenContextHasBeanAndUserConfiguresJwkSetUriThenJwkSetU
jwtConfigurer.jwkSetUri(JWK_SET_URI);

assertThat(jwtConfigurer.getJwtDecoder()).isNotEqualTo(decoder);
assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoderJwkSupport.class);
assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class);
}

@Test
Expand Down Expand Up @@ -1460,8 +1461,7 @@ static class CustomJwtValidatorConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
NimbusJwtDecoderJwkSupport jwtDecoder =
new NimbusJwtDecoderJwkSupport(this.uri);
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build());
jwtDecoder.setJwtValidator(this.jwtValidator);

// @formatter:off
Expand All @@ -1488,7 +1488,7 @@ protected void configure(HttpSecurity http) throws Exception {
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1));
jwtValidator.setClock(nearlyAnHourFromTokenExpiry);

NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(this.uri);
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build());
jwtDecoder.setJwtValidator(jwtValidator);

// @formatter:off
Expand All @@ -1511,7 +1511,7 @@ protected void configure(HttpSecurity http) throws Exception {
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1));
jwtValidator.setClock(justOverOneHourAfterExpiry);

NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(this.uri);
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build());
jwtDecoder.setJwtValidator(jwtValidator);

// @formatter:off
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;

/**
* An implementation of an {@link AuthenticationProvider}
* for the OpenID Connect Core 1.0 Authorization Code Grant Flow.
Expand Down Expand Up @@ -209,7 +211,8 @@ private JwtDecoder getJwtDecoder(ClientRegistration clientRegistration) {
);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
jwtDecoder = new NimbusJwtDecoderJwkSupport(clientRegistration.getProviderDetails().getJwkSetUri());
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
this.jwtDecoders.put(clientRegistration.getRegistrationId(), jwtDecoder);
}
return jwtDecoder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,4 @@ public interface JwtDecoder {
* @throws JwtException if an error occurs while attempting to decode the JWT
*/
Jwt decode(String token) throws JwtException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
*/
package org.springframework.security.oauth2.jwt;

import java.net.URI;
import java.util.Map;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.Map;
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;

/**
* Allows creating a {@link JwtDecoder} from an
Expand Down Expand Up @@ -58,8 +60,8 @@ public static JwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) {
OAuth2TokenValidator<Jwt> jwtValidator =
JwtValidators.createDefaultWithIssuer(oidcIssuerLocation);

NimbusJwtDecoderJwkSupport jwtDecoder =
new NimbusJwtDecoderJwkSupport(openidConfiguration.get("jwks_uri").toString());
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(
withJwkSetUri(openidConfiguration.get("jwks_uri").toString()).build());
jwtDecoder.setJwtValidator(jwtValidator);

return jwtDecoder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.jwt;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.util.Resource;
import com.nimbusds.jose.util.ResourceRetriever;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.nimbusds.jwt.proc.JWTProcessor;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

/**
* A collection of builders for creating Nimbus {@link JWTProcessor} instances.
*
* @author Josh Cummings
* @since 5.2
* @see NimbusJwtDecoder
*/
public final class JwtProcessors {

/**
* Use the given
* <a href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> uri.
*
* @param jwkSetUri the JWK Set uri to use
* @return a {@link JwtProcessors} for further configurations
*/
public static JwkSetUriJwtProcessorBuilder withJwkSetUri(String jwkSetUri) {
return new JwkSetUriJwtProcessorBuilder(jwkSetUri);
}

/**
* A builder for creating Nimbus {@link JWTProcessor} instances based on a
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> uri.
*/
public static final class JwkSetUriJwtProcessorBuilder {
private String jwkSetUri;
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
private RestOperations restOperations = new RestTemplate();

private JwkSetUriJwtProcessorBuilder(String jwkSetUri) {
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
this.jwkSetUri = jwkSetUri;
}

/**
* Use the given signing
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target="_blank">algorithm</a>.
*
* @param jwsAlgorithm the algorithm to use
* @return a {@link JwtProcessors} for further configurations
*/
public JwkSetUriJwtProcessorBuilder jwsAlgorithm(String jwsAlgorithm) {
Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty");
this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm);
return this;
}

/**
* Use the given {@link RestOperations} to coordinate with the authorization servers indicated in the
* <a href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> uri
* as well as the
* <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>.
*
* @param restOperations
* @return
*/
public JwkSetUriJwtProcessorBuilder restOperations(RestOperations restOperations) {
Assert.notNull(restOperations, "restOperations cannot be null");
this.restOperations = restOperations;
return this;
}

/**
* Build the configured {@link JwtDecoder}.
*
* @return the configured {@link JwtDecoder}
*/
public JWTProcessor<SecurityContext> build() {
ResourceRetriever jwkSetRetriever = new RestOperationsResourceRetriever(this.restOperations);
JWKSource<SecurityContext> jwkSource = new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever);
JWSKeySelector<SecurityContext> jwsKeySelector =
new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource);
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector);

// Spring Security validates the claim set independent from Nimbus
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });

return jwtProcessor;
}

private static URL toURL(String url) {
try {
return new URL(url);
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Invalid JWK Set URL \"" + url + "\" : " + ex.getMessage(), ex);
}
}

private static class RestOperationsResourceRetriever implements ResourceRetriever {
private final RestOperations restOperations;

RestOperationsResourceRetriever(RestOperations restOperations) {
Assert.notNull(restOperations, "restOperations cannot be null");
this.restOperations = restOperations;
}

@Override
public Resource retrieveResource(URL url) throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));

ResponseEntity<String> response;
try {
RequestEntity<Void> request = new RequestEntity<>(headers, HttpMethod.GET, url.toURI());
response = this.restOperations.exchange(request, String.class);
} catch (Exception ex) {
throw new IOException(ex);
}

if (response.getStatusCodeValue() != 200) {
throw new IOException(response.toString());
}

return new Resource(response.getBody(), "UTF-8");
}
}
}
}
Loading