diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc index 99059552cd..2809d981ad 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc @@ -1189,6 +1189,13 @@ include-code::./ExpressionJwtGrantedAuthoritiesConverterTests[tag=spel-expressio The SpEL expression result should be a `Collection`. +[[jwt-granted-authorities-composite]] +==== Reading from Multiple Locations + +When you are collecting authorities from more than one claim, you can use `DelegatingJwtGrantedAuthoritiesConverter` and provide multiple converter instances like so: + +include-code::./DelegatingJwtGrantedAuthoritiesConverterTests[tag=two-locations,indent=0] + [[oauth2resourceserver-jwt-validation]] == Configuring Validation diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/jwtgrantedauthoritiescomposite/DelegatingJwtGrantedAuthoritiesConverterTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/jwtgrantedauthoritiescomposite/DelegatingJwtGrantedAuthoritiesConverterTests.java new file mode 100644 index 0000000000..5fd374a5ca --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/jwtgrantedauthoritiescomposite/DelegatingJwtGrantedAuthoritiesConverterTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2004-present 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.docs.servlet.oauth2.resourceserver.jwtgrantedauthoritiescomposite; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.TestJwts; +import org.springframework.security.oauth2.server.resource.authentication.DelegatingJwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; + +import static org.assertj.core.api.Assertions.assertThat; + +class DelegatingJwtGrantedAuthoritiesConverterTests { + + @Test + public void convertWhenTokenHasMultipleClaimsThenAuthoritiesFromBothClaims() { + // @formatter:off + Jwt jwt = TestJwts.jwt() + .claim("scp", Arrays.asList("read", "write")) + .claim("roles", Arrays.asList("admin")) + .build(); + // @formatter:on + // tag::two-locations[] + JwtGrantedAuthoritiesConverter scopesConverter = new JwtGrantedAuthoritiesConverter(); + + JwtGrantedAuthoritiesConverter rolesConverter = new JwtGrantedAuthoritiesConverter(); + rolesConverter.setAuthoritiesClaimName("roles"); + rolesConverter.setAuthorityPrefix("ROLE_"); + + DelegatingJwtGrantedAuthoritiesConverter converter = + new DelegatingJwtGrantedAuthoritiesConverter(scopesConverter, rolesConverter); + Collection authorities = converter.convert(jwt); + // end::two-locations[] + assertThat(authorities).extracting(GrantedAuthority::getAuthority) + .containsExactlyInAnyOrder("SCOPE_read", "SCOPE_write", "ROLE_admin"); + } + +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/jwtgrantedauthoritiescomposite/DelegatingJwtGrantedAuthoritiesConverterTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/jwtgrantedauthoritiescomposite/DelegatingJwtGrantedAuthoritiesConverterTests.kt new file mode 100644 index 0000000000..d9f650e2a8 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/jwtgrantedauthoritiescomposite/DelegatingJwtGrantedAuthoritiesConverterTests.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2004-present 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.kt.docs.servlet.oauth2.resourceserver.jwtgrantedauthoritiescomposite + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.tuple +import org.junit.jupiter.api.Test +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.oauth2.jwt.TestJwts +import org.springframework.security.oauth2.server.resource.authentication.DelegatingJwtGrantedAuthoritiesConverter +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter + +class DelegatingJwtGrantedAuthoritiesConverterTests { + + @Test + fun convertWhenTokenHasMultipleClaimsThenAuthoritiesFromBothClaims() { + // @formatter:off + val jwt = TestJwts.jwt() + .claim("scp", listOf("read", "write")) + .claim("roles", listOf("admin")) + .build() + // @formatter:on + // tag::two-locations[] + val scopesConverter = JwtGrantedAuthoritiesConverter() + + val rolesConverter = JwtGrantedAuthoritiesConverter() + rolesConverter.setAuthoritiesClaimName("roles") + rolesConverter.setAuthorityPrefix("ROLE_") + + val converter = DelegatingJwtGrantedAuthoritiesConverter(scopesConverter, rolesConverter) + val authorities = converter.convert(jwt) + // end::two-locations[] + assertThat(authorities).extracting(GrantedAuthority::getAuthority) + .containsExactlyInAnyOrder(tuple("SCOPE_read"), tuple("SCOPE_write"), tuple("ROLE_admin")) + } + +}