From 76319cff1ede29be932e091d150d1d187249f123 Mon Sep 17 00:00:00 2001 From: Nena Raab Date: Fri, 11 Jan 2019 19:08:47 +0100 Subject: [PATCH] getAuthorities should only return app scopes #34 --- .../testservice/api/v1/TestController.java | 30 +---- spring-xsuaa/README.md | 124 ++++++++++-------- .../sap/cloud/security/xsuaa/token/Token.java | 25 ++-- .../token/TokenAuthenticationConverter.java | 13 +- .../cloud/security/xsuaa/token/TokenImpl.java | 24 ++-- .../TokenAuthenticationConverterTest.java | 25 ++-- .../security/xsuaa/token/TokenImplTest.java | 40 ++++-- 7 files changed, 151 insertions(+), 130 deletions(-) diff --git a/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java b/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java index 6257c74b0..8a8047657 100644 --- a/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java +++ b/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java @@ -1,18 +1,3 @@ -/* - * Copyright 2002-2018 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 - * - * http://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 testservice.api.v1; import static org.hamcrest.CoreMatchers.*; @@ -72,22 +57,17 @@ public String message(@AuthenticationPrincipal Token token) { // service instance id Assert.assertEquals("abcd1234", token.getCloneServiceInstanceId()); - // // groups - // Assert.assertEquals(1, token.getSystemAttribute("xs.saml.groups").length); - // Assert.assertEquals("g1", token.getSystemAttribute("xs.saml.groups")[0]); - // role collections - // Assert.assertEquals(1, token.getSystemAttribute("xs.rolecollections").length); - // Assert.assertEquals("rc1", token.getSystemAttribute("xs.rolecollections")[0]); + return "user:" + token.getLogonName(); } @GetMapping("/scope") public void checkScope(@AuthenticationPrincipal Token token) { Collection authorities = (Collection) token.getAuthorities(); - assertThat(authorities.size(), is(4)); - assertThat(authorities, hasItem(new SimpleGrantedAuthority("openid"))); - assertThat(authorities, hasItem(new SimpleGrantedAuthority("java-hello-world.Display"))); - assertThat(authorities, not(hasItem(new SimpleGrantedAuthority("java-hello-world.Other")))); + assertThat(authorities.size(), is(3)); + assertThat(authorities, not(hasItem(new SimpleGrantedAuthority("openid")))); + assertThat(authorities, hasItem(new SimpleGrantedAuthority("Display"))); + assertThat(authorities, not(hasItem(new SimpleGrantedAuthority("Other")))); } @GetMapping("/requesttoken") diff --git a/spring-xsuaa/README.md b/spring-xsuaa/README.md index 2f6b3a50a..3429a658f 100644 --- a/spring-xsuaa/README.md +++ b/spring-xsuaa/README.md @@ -2,81 +2,97 @@ ## Integrate in a OAuth resource server -This library enhances the spring-security project. As of version 5 of spring-security, this includes the OAuth resource-server functionality. A Spring boot application needs a security configuration class that enables the resource server and configures authentication using JWT tokens. +This library enhances the [spring-security](https://github.com/spring-projects/spring-security/) project. As of version 5 of spring-security, this includes the OAuth resource-server functionality. A Spring boot application needs a security configuration class that enables the resource server and configures authentication using JWT tokens. -## Usage +## Setup Set the property source for xsuaa service binding on the application: -``` +```java @SpringBootApplication -@ComponentScan(basePackageClasses=XsuaaServiceConfigurationDefault.class) -@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" }) -public class Application +@ComponentScan +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + XsuaaServiceConfigurationDefault xsuaaDefaultConfig() { + return new XsuaaServiceConfigurationDefault(); + } +} ``` Configure the OAuth resource server -``` +```java +@Configuration @EnableWebSecurity +@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = {""}) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + XsuaaServiceConfigurationDefault xsuaaServiceConfiguration; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http.authorizeRequests() + .antMatchers("/hello-token/**").hasAuthority("Read") // checks whether it has scope ".Read" + .antMatchers("/actuator/**").authenticated() + .anyRequest().denyAll() + .and() + .oauth2ResourceServer() + .jwt() + .decoder(jwtDecoder()) + .jwtAuthenticationConverter(jwtAuthenticationConverter()); + // @formatter:on + } + + @Bean + JwtDecoder jwtDecoder() { + return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build(); + } + + @Bean + Converter jwtAuthenticationConverter() { + return new TokenAuthenticationConverter(xsuaaServiceConfiguration); + } +} +``` - @Autowired - XsuaaServiceConfigurationDefault xsuaaServiceConfiguration; - - @Override - protected void configure(HttpSecurity http) throws Exception { - // @formatter:off - http.authorizeRequests(). - antMatchers("/hello-token/**").hasAuthority("openid") - .anyRequest().authenticated(). - and() - .oauth2ResourceServer().jwt() - .jwtAuthenticationConverter(new TokenAuthenticationConverter(xsuaaServiceConfiguration)); - // @formatter:on - } +> Note: with `XsuaaServicePropertySourceFactory` the VCAP_SERVICES properties are read from the system environment variable and mapped to properties such as `xsuaa.xsappname`. +> You can access them via Spring `@Value` annotation e.g. `@Value("${xsuaa.xsappname:}") String appId`. +> For testing purposes you can overwrite them, for example, as part of a *.properties file. +## Usage - @Bean - JwtDecoder jwtDecoder() { - return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build(); - } +### Check authorization on method level +```java +@GetMapping("/hello-token") +@PreAuthorize("hasAuthority('Display') +public Map message() { + ... } ``` +### Access user/token information In the Java coding, use the `Token` to extract user information: +```java +@GetMapping("/hello-token") +public Map message(@AuthenticationPrincipal Token token) { + token.getGivenName(); +} ``` - @GetMapping("/hello-token") - public Map message(@AuthenticationPrincipal Token token) { -``` - -## Inject VCAP-Service Properties - -Reading xsuaa variables from VCAP SERVICES and injecting into fields. - -### Usage - +Or alternatively: ```java -@Configuration -@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" }) -public class XsuaaConfiguration { - - public XsuaaConfiguration() { - } - - @Value("${xsuaa.clientid:}") - private String clientId; - - @Value("${xsuaa.clientsecret:}") - private String clientSecret; - - @Value("${xsuaa.url:}") - private String uaaUrl; - - @Value("${xsuaa.uaadomain:}") - private String uaadomain; - +public Map message() { + Token token = SecurityContext.getToken(); + token.getGivenName(); } ``` + + diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java index 4dd435463..b0583b809 100644 --- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java +++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/Token.java @@ -1,16 +1,12 @@ -/** - * Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. - * This file is licensed under the Apache Software License, - * v. 2 except as noted otherwise in the LICENSE file - * https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/LICENSE - */ package com.sap.cloud.security.xsuaa.token; -import com.sap.xsa.security.container.XSTokenRequest; +import java.net.URISyntaxException; +import java.util.Collection; + import org.springframework.lang.Nullable; import org.springframework.security.core.userdetails.UserDetails; -import java.net.URISyntaxException; +import com.sap.xsa.security.container.XSTokenRequest; public interface Token extends UserDetails { String CLAIM_XS_USER_ATTRIBUTES = "xs.user.attributes"; @@ -119,8 +115,8 @@ public interface Token extends UserDetails { /** * Get the encoded authentication token, e.g. for token forwarding to another app. - * - * Never expose this token via log or via HTTP. + * + * Never expose this token via log or via HTTP. * * @return token */ @@ -132,8 +128,15 @@ public interface Token extends UserDetails { * @param tokenRequest * request data * @throws URISyntaxException - * in case of wron URLs + * in case of wron URLs * @return requested token */ String requestToken(XSTokenRequest tokenRequest) throws URISyntaxException; + + /** + * Returns list of scopes with appId prefix, e.g. ".Display" + * + * @return all scopes + */ + Collection getScopes(); } \ No newline at end of file diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverter.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverter.java index 15c69ee57..0981b73ac 100644 --- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverter.java +++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverter.java @@ -1,9 +1,3 @@ -/** - * Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. - * This file is licensed under the Apache Software License, - * v. 2 except as noted otherwise in the LICENSE file - * https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/LICENSE - */ package com.sap.cloud.security.xsuaa.token; import java.util.Collection; @@ -44,7 +38,7 @@ protected Collection extractAuthorities(Jwt jwt) { Collection customAuthorities = getCustomAuthorities(new TokenImpl(jwt, appId)); Stream authorities = Stream.of(scopeAuthorities, customAuthorities).flatMap(Collection::stream); - return authorities.map(authority -> authority).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); + return authorities.map(SimpleGrantedAuthority::new).collect(Collectors.toList()); } protected Collection getCustomAuthorities(Token token) { @@ -53,6 +47,9 @@ protected Collection getCustomAuthorities(Token token) { protected Collection getScopes(Jwt jwt) { List scopesList = jwt.getClaimAsStringList(Token.CLAIM_SCOPES); - return scopesList != null ? scopesList : Collections.emptyList(); + if (scopesList != null) { + return scopesList.stream().filter(scope -> scope.startsWith(appId + ".")).map(scope -> scope.replace(appId + ".", "")).collect(Collectors.toList()); + } + return Collections.emptyList(); } } \ No newline at end of file diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenImpl.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenImpl.java index 51768ae12..fb2589a5d 100644 --- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenImpl.java +++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenImpl.java @@ -1,13 +1,9 @@ -/** - * Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. - * This file is licensed under the Apache Software License, - * v. 2 except as noted otherwise in the LICENSE file - * https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/LICENSE - */ package com.sap.cloud.security.xsuaa.token; import java.net.URISyntaxException; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import com.sap.xs2.security.container.XSTokenRequestImpl; @@ -50,23 +46,23 @@ public class TokenImpl implements Token { static final String CLAIM_EXTERNAL_CONTEXT = "ext_ctx"; private final Log logger = LogFactory.getLog(getClass()); - private String xsappname = null; + private String appId = null; private Jwt jwt; /** * @param jwt * token - * @param xsappname + * @param appId * app name */ - protected TokenImpl(Jwt jwt, String xsappname) { - this.xsappname = xsappname; + protected TokenImpl(Jwt jwt, String appId) { + this.appId = appId; this.jwt = jwt; } @Override public Collection getAuthorities() { - TokenAuthenticationConverter converter = new TokenAuthenticationConverter(xsappname); + TokenAuthenticationConverter converter = new TokenAuthenticationConverter(appId); return converter.extractAuthorities(jwt); } @@ -257,6 +253,12 @@ public String requestToken(XSTokenRequest tokenRequest) throws URISyntaxExceptio } } + @Override + public Collection getScopes() { + List scopesList = jwt.getClaimAsStringList(Token.CLAIM_SCOPES); + return scopesList != null ? scopesList : Collections.emptyList(); + } + /** * Check if the authentication token contains a claim, e.g. "email". * @param claim diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverterTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverterTest.java index ac6614d07..602fb54ad 100644 --- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverterTest.java +++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverterTest.java @@ -1,13 +1,6 @@ -/** - * Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. - * This file is licensed under the Apache Software License, - * v. 2 except as noted otherwise in the LICENSE file - * https://github.com/SAP/cloud-security-xsuaa-integration/blob/master/LICENSE - */ package com.sap.cloud.security.xsuaa.token; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; import java.util.Collection; @@ -27,6 +20,7 @@ public class TokenAuthenticationConverterTest { private TokenAuthenticationConverter tokenConverter; String scopeAdmin = xsAppName + "." + "Admin"; String scopeRead = xsAppName + "." + "Read"; + String scopeOther = "other-app!234" + "." + "Other"; @Before public void setup() throws Exception { @@ -41,14 +35,23 @@ public void extractAuthoritiesWithoutScopes() throws Exception { assertThat(authenticationToken.getAuthorities().size(), is(0)); } + @Test + public void extractAuthoritiesIgnoresForeignScopes() throws Exception { + Jwt jwt = new JwtGenerator().addScopes(scopeAdmin, scopeOther, scopeRead).getToken(); + + AbstractAuthenticationToken authenticationToken = tokenConverter.convert(jwt); + assertThat(authenticationToken.getAuthorities().size(), is(2)); + assertThat(authenticationToken.getAuthorities(), not(hasItem(new SimpleGrantedAuthority("Other")))); + } + @Test public void extractAuthoritiesWithScopes() throws Exception { Jwt jwt = new JwtGenerator().addScopes(scopeAdmin, scopeRead).getToken(); AbstractAuthenticationToken authenticationToken = tokenConverter.convert(jwt); assertThat(authenticationToken.getAuthorities().size(), is(2)); - assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority(scopeRead))); - assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority(scopeAdmin))); + assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority("Read"))); + assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority("Admin"))); } @Test @@ -62,7 +65,7 @@ public void extractCustomAuthoritiesWithScopes() throws Exception { assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority("ATTR:COST-CENTER=0815"))); assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority("ATTR:COUNTRY=DE"))); assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority("ATTR:COUNTRY=IL"))); - assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority(scopeAdmin))); + assertThat(authenticationToken.getAuthorities(), hasItem(new SimpleGrantedAuthority("Admin"))); } private static class MyTokenAuthenticationConverter extends TokenAuthenticationConverter { diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenImplTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenImplTest.java index fe5966a88..746c3ce4d 100644 --- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenImplTest.java +++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/TokenImplTest.java @@ -34,8 +34,9 @@ public class TokenImplTest { private Jwt jwtCCNoAttributes; private JWTClaimsSet.Builder claimsSetBuilder = null; private String xsAppName = "my-app-name!400"; - private String scopeRead = xsAppName + "." + "Display"; - private String scopeWrite = xsAppName + "." + "Edit"; + private String scopeRead = xsAppName + "." + "Read"; + private String scopeWrite = xsAppName + "." + "Write"; + private String scopeOther = "other-app-name!777.Other"; private String userName = "testUser"; private String zoneId = "e2f7fbdb-0326-40e6-940f-dfddad057ff3"; @@ -69,18 +70,36 @@ public void checkBasicJwtWithoutScopes() throws Exception { } @Test - public void getAuthoritiesReturnsManyScopes() throws Exception { + public void getAuthoritiesReturnsApplicationScopes() throws Exception { List scopesList = new ArrayList<>(); scopesList.add(scopeWrite); scopesList.add(scopeRead); + scopesList.add(scopeOther); claimsSetBuilder.claim("scope", scopesList); token = createToken(claimsSetBuilder); Collection authorities = (Collection) token.getAuthorities(); assertThat(authorities.size(), is(2)); - assertThat(authorities, hasItem(new SimpleGrantedAuthority(scopeRead))); - assertThat(authorities, hasItem(new SimpleGrantedAuthority(scopeWrite))); + assertThat(authorities, hasItem(new SimpleGrantedAuthority("Read"))); + assertThat(authorities, hasItem(new SimpleGrantedAuthority("Write"))); + } + + @Test + public void getScopesReturnsAllScopes() throws Exception { + List scopesList = new ArrayList<>(); + scopesList.add(scopeWrite); + scopesList.add(scopeRead); + scopesList.add(scopeOther); + claimsSetBuilder.claim("scope", scopesList); + + token = createToken(claimsSetBuilder); + + Collection scopes = token.getScopes(); + assertThat(scopes.size(), is(3)); + assertThat(scopes, hasItem(scopeRead)); + assertThat(scopes, hasItem(scopeWrite)); + assertThat(scopes, hasItem(scopeOther)); } @Test @@ -160,12 +179,13 @@ public void getPrincipalNameReturnUniqueClientId() { } @Test - public void getAuthoritiesReturnsAllScopes() throws Exception { - Token token = new TokenImpl(jwtSaml, xsAppName); + public void getAuthoritiesReturnsAllAppScopes() { + Token token = new TokenImpl(jwtSaml, "java-hello-world"); Collection authorities = (Collection) token.getAuthorities(); - assertThat(authorities.size(), is(4)); - assertThat(authorities, hasItem(new SimpleGrantedAuthority("openid"))); - assertThat(authorities, hasItem(new SimpleGrantedAuthority("java-hello-world.Delete"))); + assertThat(authorities.size(), is(3)); //no openid scope + assertThat(authorities, hasItem(new SimpleGrantedAuthority("Delete"))); + assertThat(authorities, hasItem(new SimpleGrantedAuthority("Display"))); + assertThat(authorities, hasItem(new SimpleGrantedAuthority("Create"))); } @Test