Leave Issuer As String

Since StringOrURI is a valid issuer, MappedJwtClaimSetConverter and
JwtIssuerValidator no longer assume it.

Issue: gh-6073
This commit is contained in:
Josh Cummings 2018-11-13 10:36:41 -07:00
parent c70b65c5df
commit 19649db9ce
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
4 changed files with 51 additions and 43 deletions

View File

@ -15,9 +15,6 @@
*/ */
package org.springframework.security.oauth2.jwt; package org.springframework.security.oauth2.jwt;
import java.net.MalformedURLException;
import java.net.URL;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator;
@ -46,14 +43,7 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
*/ */
public JwtIssuerValidator(String issuer) { public JwtIssuerValidator(String issuer) {
Assert.notNull(issuer, "issuer cannot be null"); Assert.notNull(issuer, "issuer cannot be null");
this.issuer = issuer;
try {
this.issuer = new URL(issuer).toString();
} catch (MalformedURLException ex) {
throw new IllegalArgumentException(
"Invalid Issuer URL " + issuer + " : " + ex.getMessage(),
ex);
}
} }
/** /**

View File

@ -16,7 +16,6 @@
package org.springframework.security.oauth2.jwt; package org.springframework.security.oauth2.jwt;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.time.Instant; import java.time.Instant;
@ -42,7 +41,7 @@ public final class MappedJwtClaimSetConverter
implements Converter<Map<String, Object>, Map<String, Object>> { implements Converter<Map<String, Object>, Map<String, Object>> {
private static final Converter<Object, Collection<String>> AUDIENCE_CONVERTER = new AudienceConverter(); private static final Converter<Object, Collection<String>> AUDIENCE_CONVERTER = new AudienceConverter();
private static final Converter<Object, URL> ISSUER_CONVERTER = new IssuerConverter(); private static final Converter<Object, String> ISSUER_CONVERTER = new IssuerConverter();
private static final Converter<Object, String> STRING_CONVERTER = new StringConverter(); private static final Converter<Object, String> STRING_CONVERTER = new StringConverter();
private static final Converter<Object, Instant> TEMPORAL_CONVERTER = new InstantConverter(); private static final Converter<Object, Instant> TEMPORAL_CONVERTER = new InstantConverter();
@ -157,39 +156,27 @@ public final class MappedJwtClaimSetConverter
* Coerces an <a target="_blank" href="https://tools.ietf.org/html/rfc7519#section-4.1.1">Issuer</a> claim * Coerces an <a target="_blank" href="https://tools.ietf.org/html/rfc7519#section-4.1.1">Issuer</a> claim
* into a {@link URL}, ignoring null values, and throwing an error if its coercion efforts fail. * into a {@link URL}, ignoring null values, and throwing an error if its coercion efforts fail.
*/ */
private static class IssuerConverter implements Converter<Object, URL> { private static class IssuerConverter implements Converter<Object, String> {
@Override @Override
public URL convert(Object source) { public String convert(Object source) {
if (source == null) { if (source == null) {
return null; return null;
} }
if (source instanceof URL) { if (source instanceof URL) {
return (URL) source; return ((URL) source).toExternalForm();
} }
if (source instanceof URI) { if (source instanceof String && ((String) source).contains(":")) {
return toUrl((URI) source);
}
return toUrl(source.toString());
}
private URL toUrl(URI source) {
try { try {
return source.toURL(); return URI.create((String) source).toString();
} catch (MalformedURLException e) { } catch (Exception e) {
throw new IllegalStateException("Could not coerce " + source + " into a URL", e); throw new IllegalStateException("Could not coerce " + source + " into a URI String", e);
} }
} }
private URL toUrl(String source) { return source.toString();
try {
return new URL(source);
} catch (MalformedURLException e) {
throw new IllegalStateException("Could not coerce " + source + " into a URL", e);
}
} }
} }

View File

@ -82,15 +82,24 @@ public class JwtIssuerValidatorTests {
assertThat(result.getErrors()).isNotEmpty(); assertThat(result.getErrors()).isNotEmpty();
} }
// gh-6073
@Test @Test
public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() { public void validateWhenIssuerMatchesAndIsNotAUriThenReturnsSuccess() {
assertThatCode(() -> this.validator.validate(null)) Jwt jwt = new Jwt(
.isInstanceOf(IllegalArgumentException.class); MOCK_TOKEN,
MOCK_ISSUED_AT,
MOCK_EXPIRES_AT,
MOCK_HEADERS,
Collections.singletonMap(JwtClaimNames.ISS, "issuer"));
JwtIssuerValidator validator = new JwtIssuerValidator("issuer");
assertThat(validator.validate(jwt))
.isEqualTo(OAuth2TokenValidatorResult.success());
} }
@Test @Test
public void constructorWhenMalformedIssuerIsGivenThenThrowsIllegalArgumentException() { public void validateWhenJwtIsNullThenThrowsIllegalArgumentException() {
assertThatCode(() -> new JwtIssuerValidator("issuer")) assertThatCode(() -> this.validator.validate(null))
.isInstanceOf(IllegalArgumentException.class); .isInstanceOf(IllegalArgumentException.class);
} }

View File

@ -108,7 +108,7 @@ public class MappedJwtClaimSetConverterTests {
assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience")); assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience"));
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L)); assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L)); assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo(new URL("https://any.url")); assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("https://any.url");
assertThat(target.get(JwtClaimNames.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L)); assertThat(target.get(JwtClaimNames.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234"); assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
} }
@ -135,7 +135,7 @@ public class MappedJwtClaimSetConverterTests {
assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience")); assertThat(target.get(JwtClaimNames.AUD)).isEqualTo(Arrays.asList("audience"));
assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L)); assertThat(target.get(JwtClaimNames.EXP)).isEqualTo(Instant.ofEpochSecond(2000000000L));
assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L)); assertThat(target.get(JwtClaimNames.IAT)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo(new URL("https://any.url")); assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("https://any.url");
assertThat(target.get(JwtClaimNames.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L)); assertThat(target.get(JwtClaimNames.NBF)).isEqualTo(Instant.ofEpochSecond(1000000000L));
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234"); assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
} }
@ -196,7 +196,7 @@ public class MappedJwtClaimSetConverterTests {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap()); .withDefaults(Collections.emptyMap());
Map<String, Object> badIssuer = Collections.singletonMap(JwtClaimNames.ISS, "badly-formed-iss"); Map<String, Object> badIssuer = Collections.singletonMap(JwtClaimNames.ISS, "https://badly formed iss");
assertThatCode(() -> converter.convert(badIssuer)).isInstanceOf(IllegalStateException.class); assertThatCode(() -> converter.convert(badIssuer)).isInstanceOf(IllegalStateException.class);
Map<String, Object> badIssuedAt = Collections.singletonMap(JwtClaimNames.IAT, "badly-formed-iat"); Map<String, Object> badIssuedAt = Collections.singletonMap(JwtClaimNames.IAT, "badly-formed-iat");
@ -209,6 +209,28 @@ public class MappedJwtClaimSetConverterTests {
assertThatCode(() -> converter.convert(badNotBefore)).isInstanceOf(IllegalStateException.class); assertThatCode(() -> converter.convert(badNotBefore)).isInstanceOf(IllegalStateException.class);
} }
// gh-6073
@Test
public void convertWhenIssuerIsNotAUriThenConvertsToString() {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
Map<String, Object> nonUriIssuer = Collections.singletonMap(JwtClaimNames.ISS, "issuer");
Map<String, Object> target = converter.convert(nonUriIssuer);
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("issuer");
}
// gh-6073
@Test
public void convertWhenIssuerIsOfTypeURLThenConvertsToString() throws Exception {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.withDefaults(Collections.emptyMap());
Map<String, Object> issuer = Collections.singletonMap(JwtClaimNames.ISS, new URL("https://issuer"));
Map<String, Object> target = converter.convert(issuer);
assertThat(target.get(JwtClaimNames.ISS)).isEqualTo("https://issuer");
}
@Test @Test
public void constructWhenAnyParameterIsNullThenIllegalArgumentException() { public void constructWhenAnyParameterIsNullThenIllegalArgumentException() {
assertThatCode(() -> new MappedJwtClaimSetConverter(null)) assertThatCode(() -> new MappedJwtClaimSetConverter(null))