diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverter.java new file mode 100644 index 0000000000..7cd7fd518a --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2019 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.Collection; +import java.util.Map; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; + +/** + * A {@link Converter} that takes a {@link Jwt} and converts it into a {@link BearerTokenAuthentication}. + * + * In the process, it will attempt to parse either the "scope" or "scp" attribute, whichever it finds first. + * + * It's not intended that this implementation be configured since it is simply an adapter. If you are using, + * for example, a custom {@link JwtGrantedAuthoritiesConverter}, then it's recommended that you simply + * create your own {@link Converter} that delegates to your custom {@link JwtGrantedAuthoritiesConverter} + * and instantiates the appropriate {@link BearerTokenAuthentication}. + * + * @author Josh Cummings + * @since 5.2 + */ +public final class JwtBearerTokenAuthenticationConverter implements Converter { + private final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + + @Override + public AbstractAuthenticationToken convert(Jwt jwt) { + OAuth2AccessToken accessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt()); + Map attributes = jwt.getClaims(); + + AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt); + Collection authorities = token.getAuthorities(); + + OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal(attributes, authorities); + return new BearerTokenAuthentication(principal, accessToken, authorities); + } +} diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverterTests.java new file mode 100644 index 0000000000..f96073aeb7 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverterTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2019 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 org.junit.Test; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JwtBearerTokenAuthenticationConverter} + * + * @author Josh Cummings + */ +public class JwtBearerTokenAuthenticationConverterTests { + private final JwtBearerTokenAuthenticationConverter converter = + new JwtBearerTokenAuthenticationConverter(); + + @Test + public void convertWhenJwtThenBearerTokenAuthentication() { + Jwt jwt = Jwt.withTokenValue("token-value") + .claim("claim", "value") + .header("header", "value") + .build(); + + AbstractAuthenticationToken token = this.converter.convert(jwt); + + assertThat(token).isInstanceOf(BearerTokenAuthentication.class); + BearerTokenAuthentication bearerToken = (BearerTokenAuthentication) token; + assertThat(bearerToken.getToken().getTokenValue()).isEqualTo("token-value"); + assertThat(bearerToken.getTokenAttributes()).containsOnlyKeys("claim"); + assertThat(bearerToken.getAuthorities()).isEmpty(); + } + + @Test + public void convertWhenJwtWithScopeAttributeThenBearerTokenAuthentication() { + Jwt jwt = Jwt.withTokenValue("token-value") + .claim("scope", "message:read message:write") + .header("header", "value") + .build(); + + AbstractAuthenticationToken token = this.converter.convert(jwt); + + assertThat(token).isInstanceOf(BearerTokenAuthentication.class); + BearerTokenAuthentication bearerToken = (BearerTokenAuthentication) token; + assertThat(bearerToken.getAuthorities()) + .containsExactly(new SimpleGrantedAuthority("SCOPE_message:read"), + new SimpleGrantedAuthority("SCOPE_message:write")); + } + + @Test + public void convertWhenJwtWithScpAttributeThenBearerTokenAuthentication() { + Jwt jwt = Jwt.withTokenValue("token-value") + .claim("scp", Arrays.asList("message:read", "message:write")) + .header("header", "value") + .build(); + + AbstractAuthenticationToken token = this.converter.convert(jwt); + + assertThat(token).isInstanceOf(BearerTokenAuthentication.class); + BearerTokenAuthentication bearerToken = (BearerTokenAuthentication) token; + assertThat(bearerToken.getAuthorities()) + .containsExactly(new SimpleGrantedAuthority("SCOPE_message:read"), + new SimpleGrantedAuthority("SCOPE_message:write")); + } +}