From 97cc119d86f12de58f3772fb15e29fc4f29752cb Mon Sep 17 00:00:00 2001 From: Ropi Date: Sat, 2 Nov 2019 23:58:08 +0100 Subject: [PATCH] Add DelegatingJwtGrantedAuthoritiesConverter Closes gh-7596 --- ...egatingJwtGrantedAuthoritiesConverter.java | 80 +++++++++++++++++++ ...ingJwtGrantedAuthoritiesConverterTest.java | 79 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverter.java create mode 100644 oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverterTest.java diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverter.java new file mode 100644 index 0000000000..0bfd1b5dce --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.server.resource.authentication; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; + +/** + * Implementation of {@link Converter} that wraps multiple {@link Converter} instances into one. + * + * @author Laszlo Stahorszki + * @since 5.5 + */ +public class DelegatingJwtGrantedAuthoritiesConverter implements Converter> { + + private final Collection>> converters = new HashSet<>(); + + /** + * Constructs a {@link DelegatingJwtGrantedAuthoritiesConverter} using the provided {@link Collection} of + * {@link Converter}s + * + * @param converters the {@link Collection} of {@link Converter}s to use + */ + public DelegatingJwtGrantedAuthoritiesConverter(Collection>> converters) { + this.converters.addAll(converters); + } + + /** + * Constructs a {@link DelegatingJwtGrantedAuthoritiesConverter} using the provided array of + * {@link Converter}s + * + * @param converters the array of {@link Converter}s to use + */ + @SafeVarargs + public DelegatingJwtGrantedAuthoritiesConverter(Converter>... converters) { + this(Arrays.asList(converters)); + } + + /** + * Collects the {@link Collection} of authorities from the provided {@link Jwt} token. The method iterates through + * all the {@link Converter}s provided during construction and returns the union of {@link GrantedAuthority}s + * they extract. + * @param source the source object to convert, which must be an instance of {@code S} (never {@code null}) + * @return the converted object, which must be an instance of {@code T} (potentially {@code null}) + * @throws IllegalArgumentException if the source cannot be converted to the desired target type + */ + @Override + public Collection convert(Jwt source) { + Collection result = new LinkedHashSet<>(); + + for (Converter> converter: this.converters) { + Collection authorities = converter.convert(source); + if (authorities != null) { + result.addAll(authorities); + } + } + + return result; + } +} diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverterTest.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverterTest.java new file mode 100644 index 0000000000..87c2e4b77a --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DelegatingJwtGrantedAuthoritiesConverterTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.server.resource.authentication; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.Test; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for verifying {@link DelegatingJwtGrantedAuthoritiesConverter} + * + * @author Laszlo Stahorszki + */ +public class DelegatingJwtGrantedAuthoritiesConverterTest { + + @Test + public void convertNoConverters() { + DelegatingJwtGrantedAuthoritiesConverter subject = new DelegatingJwtGrantedAuthoritiesConverter(); + + assertThat(subject.convert(Jwt.withTokenValue("some-token-value") + .header("header", "value") + .claim("claim", "value") + .build())).isEmpty(); + } + + @Test + public void convert() { + DelegatingJwtGrantedAuthoritiesConverter subject = new DelegatingJwtGrantedAuthoritiesConverter(((source) -> + Collections.singletonList(new SimpleGrantedAuthority(source.getClaim("claim"))))); + + assertThat(subject.convert(Jwt.withTokenValue("some-token-value") + .header("header", "value") + .claim("claim", "value") + .build())).containsExactlyInAnyOrder(new SimpleGrantedAuthority("value")); + } + + @Test + public void convertMultipleConverters() { + DelegatingJwtGrantedAuthoritiesConverter subject = new DelegatingJwtGrantedAuthoritiesConverter( + (source) -> Collections.singletonList(new SimpleGrantedAuthority(source.getClaim("claim"))), + (source) -> Arrays.stream(source.getHeaders().entrySet().toArray(new Map.Entry[]{})) + .map(Map.Entry::getValue) + .map(Object::toString) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList())); + + assertThat(subject.convert(Jwt.withTokenValue("some-token-value") + .header("header", "value") + .header("header2", "value2") + .claim("claim", "value3") + .build())).containsExactlyInAnyOrder( + new SimpleGrantedAuthority("value"), + new SimpleGrantedAuthority("value2"), + new SimpleGrantedAuthority("value3") + ); + } +}