diff --git a/src/main/asciidoc/spring-cloud-security.adoc b/src/main/asciidoc/spring-cloud-security.adoc index e5382137..e891b3e2 100644 --- a/src/main/asciidoc/spring-cloud-security.adoc +++ b/src/main/asciidoc/spring-cloud-security.adoc @@ -59,6 +59,14 @@ Github for instance, your OAuth2 provider doesn't like header authentication). The `spring.oauth2.client.*` properties are bound to an instance of `AuthorizationCodeResourceDetails` so all its properties can be specified. +=== Token Type in User Info + +Google (and certain other 3rd party identity providers) is more strict +about the token type name that is sent in the headers to the user info +endpoint. The default is "Bearer" which suits most providers and +matches the spec, but if you need to change it you can set +`spring.oauth2.resource.tokenType`. + === Customizing the RestTemplate The SSO (and Resource Server) features use an `OAuth2RestTemplate` diff --git a/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerProperties.java b/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerProperties.java index 77743614..dafc37be 100644 --- a/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerProperties.java +++ b/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerProperties.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.util.StringUtils; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -65,6 +66,11 @@ public class ResourceServerProperties implements Validator { */ private boolean preferTokenInfo = true; + /** + * The token type to send when using the userInfoUri. + */ + private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE; + private Jwt jwt = new Jwt(); /** diff --git a/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java b/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java index 4aa78a33..34b0d1d8 100644 --- a/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java +++ b/src/main/java/org/springframework/cloud/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java @@ -205,6 +205,7 @@ public SpringSocialTokenServices socialTokenServices() { public UserInfoTokenServices userInfoTokenServices() { UserInfoTokenServices services = new UserInfoTokenServices( sso.getUserInfoUri(), sso.getClientId()); + services.setTokenType(sso.getTokenType()); services.setRestTemplate(restTemplate); return services; } @@ -229,6 +230,7 @@ public UserInfoTokenServices userInfoTokenServices() { UserInfoTokenServices services = new UserInfoTokenServices( sso.getUserInfoUri(), sso.getClientId()); services.setRestTemplate(restTemplate); + services.setTokenType(sso.getTokenType()); return services; } diff --git a/src/main/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServices.java b/src/main/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServices.java index ee243016..b1dace3d 100644 --- a/src/main/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServices.java +++ b/src/main/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServices.java @@ -42,10 +42,16 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { private OAuth2RestOperations restTemplate; + private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE; + public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) { this.userInfoEndpointUrl = userInfoEndpointUrl; this.clientId = clientId; } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } public void setRestTemplate(OAuth2RestOperations restTemplate) { this.restTemplate = restTemplate; @@ -99,8 +105,9 @@ private Map getMap(String path, String accessToken) { resource.setClientId(clientId); restTemplate = new OAuth2RestTemplate(resource); } - restTemplate.getOAuth2ClientContext().setAccessToken( - new DefaultOAuth2AccessToken(accessToken)); + DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken); + token.setTokenType(tokenType); + restTemplate.getOAuth2ClientContext().setAccessToken(token); @SuppressWarnings("rawtypes") Map map = restTemplate.getForEntity(path, Map.class).getBody(); @SuppressWarnings("unchecked") diff --git a/src/test/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServicesTests.java b/src/test/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServicesTests.java index c803ad4a..489dff3e 100644 --- a/src/test/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServicesTests.java +++ b/src/test/java/org/springframework/cloud/security/oauth2/resource/UserInfoTokenServicesTests.java @@ -23,6 +23,7 @@ import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -31,6 +32,7 @@ import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; /** * @author Dave Syer @@ -42,6 +44,7 @@ public class UserInfoTokenServicesTests { "http://example.com", "foo"); private BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); private OAuth2RestOperations template = Mockito.mock(OAuth2RestOperations.class); + private OAuth2ClientContext clientContext = Mockito.mock(OAuth2ClientContext.class); private Map map = new LinkedHashMap(); @Before @@ -55,7 +58,7 @@ public void init() { new DefaultOAuth2AccessToken("FOO")); Mockito.when(template.getResource()).thenReturn(resource); Mockito.when(template.getOAuth2ClientContext()).thenReturn( - Mockito.mock(OAuth2ClientContext.class)); + clientContext); } @Test @@ -64,6 +67,16 @@ public void sunnyDay() { assertEquals("unknown", services.loadAuthentication("FOO").getName()); } + @Test + public void tokenType() { + services.setRestTemplate(template); + services.setTokenType("access_token"); + assertEquals("unknown", services.loadAuthentication("FOO").getName()); + ArgumentCaptor argument = ArgumentCaptor.forClass(OAuth2AccessToken.class); + Mockito.verify(clientContext).setAccessToken(argument.capture()); + assertEquals("access_token", argument.getValue().getTokenType()); + } + @Test public void noClientId() { services = new UserInfoTokenServices("http://example.com", null);