Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
29b5cd0
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 17, 2020
081cb82
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 17, 2020
c8a3508
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 17, 2020
b9aa067
* add JavaDoc
ZhuXiaoBing-cn Nov 17, 2020
fdfb007
* remove tenant validator
ZhuXiaoBing-cn Nov 18, 2020
bddc2e4
* add Converter,it's effect will be to store the UserPrincipal to Aut…
ZhuXiaoBing-cn Nov 19, 2020
cb82a9b
Merge branch 'master' into access_token_validate
ZhuXiaoBing-cn Nov 19, 2020
0732746
Merge branch 'master' into access_token_validate
ZhuXiaoBing-cn Nov 19, 2020
3f8dac0
* update file name
ZhuXiaoBing-cn Nov 20, 2020
4b6e974
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 23, 2020
ce65efe
Merge branch 'master' into access_token_validate
ZhuXiaoBing-cn Nov 23, 2020
05d929d
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 23, 2020
4e4d053
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 23, 2020
b362dfb
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 23, 2020
ae39fe7
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 23, 2020
acaeefa
* update AzureJwtBearerTokenAuthenticationConverter
ZhuXiaoBing-cn Nov 23, 2020
d25e9df
Merge branch 'master' into access_token_validate
ZhuXiaoBing-cn Nov 23, 2020
a31f1a6
* add AzureOAuth2AuthenticatedPrincipal
ZhuXiaoBing-cn Nov 25, 2020
f7aa628
Merge branch 'master' into access_token_validate
ZhuXiaoBing-cn Nov 25, 2020
0851fc6
* update AzureActiveDirectoryResourceServerConfiguration
ZhuXiaoBing-cn Nov 25, 2020
0ac20f9
* Make changes according to checkstyle
ZhuXiaoBing-cn Nov 25, 2020
9f80db0
* Make changes according to checkstyle.
ZhuXiaoBing-cn Nov 25, 2020
fd2155b
* update unit test
ZhuXiaoBing-cn Nov 26, 2020
7403602
* Modify invalid import
ZhuXiaoBing-cn Nov 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/versioning/external_dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ org.springframework.kafka:spring-kafka;2.5.7.RELEASE
org.springframework.security:spring-security-config;5.3.5.RELEASE
org.springframework.security:spring-security-core;5.3.5.RELEASE
org.springframework.security:spring-security-oauth2-client;5.3.5.RELEASE
org.springframework.security:spring-security-oauth2-resource-server;5.3.5.RELEASE
org.springframework.security:spring-security-oauth2-core;5.3.5.RELEASE
org.springframework.security:spring-security-oauth2-jose;5.3.5.RELEASE
org.springframework.security:spring-security-web;5.3.5.RELEASE
Expand Down
7 changes: 7 additions & 0 deletions sdk/spring/azure-spring-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@
<version>5.3.5.RELEASE</version> <!-- {x-version-update;org.springframework.security:spring-security-oauth2-client;external_dependency} -->
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
<version>5.3.5.RELEASE</version> <!-- {x-version-update;org.springframework.security:spring-security-oauth2-resource-server;external_dependency} -->
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
Expand Down Expand Up @@ -336,6 +342,7 @@
<include>org.springframework.security:spring-security-config:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-config;external_dependency} -->
<include>org.springframework.security:spring-security-core:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-core;external_dependency} -->
<include>org.springframework.security:spring-security-oauth2-client:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-oauth2-client;external_dependency} -->
<include>org.springframework.security:spring-security-oauth2-resource-server:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-oauth2-resource-server;external_dependency} -->
<include>org.springframework.security:spring-security-oauth2-jose:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-oauth2-jose;external_dependency} -->
<include>org.springframework.security:spring-security-web:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-web;external_dependency} -->
<include>org.apache.qpid:qpid-jms-client:[0.53.0]</include> <!-- {x-include-update;org.apache.qpid:qpid-jms-client;external_dependency} -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@
package com.azure.spring.autoconfigure.aad;

import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.validation.annotation.Validated;

import javax.annotation.PostConstruct;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.validation.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.validation.annotation.Validated;

/**
* Configuration properties for Azure Active Directory Authentication.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.autoconfigure.aad;

import java.util.List;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.util.Assert;

/**
* Validates the "aud" claim in a {@link Jwt}, that is matches a configured value
*/
public class AADJwtAudienceValidator implements OAuth2TokenValidator<Jwt> {

private final JwtClaimValidator<List<String>> validator;

/**
* Constructs a {@link AADJwtAudienceValidator} using the provided parameters
*
* @param audiences - The audience that each {@link Jwt} should have.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public AADJwtAudienceValidator(List<String> audiences) {
Assert.notNull(audiences, "audiences cannot be null");
this.validator = new JwtClaimValidator(AADTokenClaim.AUD, aud -> audiences.containsAll((List<String>) aud));
Comment thread
saragluna marked this conversation as resolved.
}

/**
* {@inheritDoc}
*/
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
Assert.notNull(token, "token cannot be null");
return this.validator.validate(token);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.autoconfigure.aad;

import java.util.function.Predicate;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.util.Assert;

/**
* Validates the "iss" claim in a {@link Jwt}, that is matches a configured value
*/
public class AADJwtIssuerValidator implements OAuth2TokenValidator<Jwt> {

private static final String LOGIN_MICROSOFT_ONLINE_ISSUER = "https://login.microsoftonline.com/";
private static final String STS_WINDOWS_ISSUER = "https://sts.windows.net/";
private static final String STS_CHINA_CLOUD_API_ISSUER = "https://sts.chinacloudapi.cn/";
private final JwtClaimValidator<String> validator;

/**
* Constructs a {@link AADJwtIssuerValidator} using the provided parameters
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public AADJwtIssuerValidator() {
this.validator = new JwtClaimValidator(AADTokenClaim.ISS, validIssuer());
}

private Predicate<String> validIssuer() {
return iss -> {
if (iss == null) {
return false;
}
return iss.startsWith(LOGIN_MICROSOFT_ONLINE_ISSUER)
|| iss.startsWith(STS_WINDOWS_ISSUER)
|| iss.startsWith(STS_CHINA_CLOUD_API_ISSUER);
};
}

/**
* {@inheritDoc}
*/
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
Assert.notNull(token, "token cannot be null");
return this.validator.validate(token);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.autoconfigure.aad;


import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.util.StringUtils;

/**
* <p>
* The configuration will not be activated if no {@link BearerTokenAuthenticationToken} class provided.
* <p>
* By default, creating a JwtDecoder through JwkKeySetUri will be auto-configured.
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({AADAuthenticationProperties.class, ServiceEndpointsProperties.class})
@ConditionalOnClass(BearerTokenAuthenticationToken.class)
public class AADResourceServerAutoConfiguration {

@Autowired
private AADAuthenticationProperties aadAuthenticationProperties;
@Autowired
private ServiceEndpointsProperties serviceEndpointsProperties;

/**
* Use JwkKeySetUri to create JwtDecoder
*
* @return JwtDecoder bean
*/
@Bean
@ConditionalOnMissingBean(JwtDecoder.class)
public JwtDecoder jwtDecoderByJwkKeySetUri() {
if (StringUtils.isEmpty(aadAuthenticationProperties.getTenantId())) {
aadAuthenticationProperties.setTenantId("common");
}
String authorizationServerUri =
serviceEndpointsProperties.getServiceEndpoints(aadAuthenticationProperties.getEnvironment())
.getAadSigninUri();
AuthorizationServerEndpoints endpoints = new AuthorizationServerEndpoints(authorizationServerUri);
NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder
.withJwkSetUri(endpoints.jwkSetEndpoint(aadAuthenticationProperties.getTenantId())).build();
List<OAuth2TokenValidator<Jwt>> validators = createDefaultValidator();
nimbusJwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
return nimbusJwtDecoder;
}

public List<OAuth2TokenValidator<Jwt>> createDefaultValidator() {
List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
if (!StringUtils.isEmpty(aadAuthenticationProperties.getAppIdUri())) {
List<String> validAudiences = new ArrayList<>();
validAudiences.add(aadAuthenticationProperties.getAppIdUri());
validators.add(new AADJwtAudienceValidator(validAudiences));
}
validators.add(new AADJwtIssuerValidator());
validators.add(new JwtTimestampValidator());
return validators;
}

/**
* Default configuration class for using AAD authentication and authorization. User can write another configuration
* bean to override it.
*/
@Configuration
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@EnableWebSecurity
public static class DefaultAzureOAuth2ResourceServerWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests((requests) -> requests.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new AzureJwtBearerTokenAuthenticationConverter());
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public class AADTokenClaim {
public static final String NAME = "name";
public static final String TID = "tid";
public static final String ROLES = "roles";
public static final String ISS = "iss";
public static final String AUD = "aud";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.azure.spring.autoconfigure.aad;

import com.nimbusds.jose.JWSObject;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import java.text.ParseException;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
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.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

/**
* A {@link Converter} that takes a {@link Jwt} and converts it into a {@link PreAuthenticatedAuthenticationToken}.
*/
public class AzureJwtBearerTokenAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

private final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();

public AzureJwtBearerTokenAuthenticationConverter() {
}

public AbstractAuthenticationToken convert(Jwt jwt) {
AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt);
Collection<GrantedAuthority> authorities = token.getAuthorities();
Comment thread
saragluna marked this conversation as resolved.
Outdated
OAuth2AccessToken accessToken = new OAuth2AccessToken(TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt());
Map<String, Object> attributes = jwt.getClaims();
JWTClaimsSet.Builder builder = new Builder();
for (Entry<String, Object> entry : attributes.entrySet()) {
builder.claim(entry.getKey(), entry.getValue());
}
JWSObject jwsObject = null;
try {
jwsObject = JWSObject.parse(accessToken.getTokenValue());
} catch (ParseException e) {
e.printStackTrace();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need any log info here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we throw this exception?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a log here.

}
UserPrincipal userPrincipal = new UserPrincipal(accessToken.getTokenValue(), jwsObject, builder.build());
return new PreAuthenticatedAuthenticationToken(userPrincipal, null, authorities);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.azure.spring.autoconfigure.aad.AADAuthenticationFilterAutoConfiguration,\
com.azure.spring.autoconfigure.aad.AADOAuth2AutoConfiguration, \
com.azure.spring.aad.implementation.AzureActiveDirectoryConfiguration,\
com.azure.spring.autoconfigure.aad.AADResourceServerAutoConfiguration, \
com.azure.spring.autoconfigure.b2c.AADB2CAutoConfiguration,\
com.azure.spring.autoconfigure.cosmos.CosmosAutoConfiguration,\
com.azure.spring.autoconfigure.cosmos.CosmosHealthConfiguration,\
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.autoconfigure.aad;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;

public class AADJwtAudienceValidatorTest {

final AADAuthenticationProperties aadAuthenticationProperties = mock(AADAuthenticationProperties.class);
final Jwt jwt = mock(Jwt.class);
final List<String> audiences = new ArrayList<>();
final List<String> claimAudience = new ArrayList<>();

@Test
public void testClientIdExistAndSuccessVerify() {
when(aadAuthenticationProperties.getClientId()).thenReturn("fake-client-id");
when(jwt.getClaim(AADTokenClaim.AUD)).thenReturn(claimAudience);
claimAudience.add("fake-client-id");
audiences.add(aadAuthenticationProperties.getClientId());
AADJwtAudienceValidator aadJwtAudienceValidator = new AADJwtAudienceValidator(audiences);
OAuth2TokenValidatorResult result = aadJwtAudienceValidator.validate(jwt);

assertThat(result).isNotNull();
assertThat(result.getErrors()).isEmpty();
}

@Test
public void testAppIdUriExistAndSuccessVerify() {
when(aadAuthenticationProperties.getClientId()).thenReturn("fake-app-id-uri");
when(jwt.getClaim(AADTokenClaim.AUD)).thenReturn(claimAudience);
claimAudience.add("fake-app-id-uri");
audiences.add(aadAuthenticationProperties.getClientId());
AADJwtAudienceValidator aadJwtAudienceValidator = new AADJwtAudienceValidator(audiences);
OAuth2TokenValidatorResult result = aadJwtAudienceValidator.validate(jwt);

assertThat(result).isNotNull();
assertThat(result.getErrors()).isEmpty();
}

@Test
public void testAppIdUriExistAndClientIdAndSuccessVerify() {
when(aadAuthenticationProperties.getClientId()).thenReturn("fake-app-id-uri");
when(aadAuthenticationProperties.getAppIdUri()).thenReturn("fake-client-id");
when(jwt.getClaim(AADTokenClaim.AUD)).thenReturn(claimAudience);
//claimAudience.add("fake-client-id");
claimAudience.add("fake-app-id-uri");
audiences.add(aadAuthenticationProperties.getClientId());
audiences.add(aadAuthenticationProperties.getAppIdUri());
AADJwtAudienceValidator aadJwtAudienceValidator = new AADJwtAudienceValidator(audiences);
OAuth2TokenValidatorResult result = aadJwtAudienceValidator.validate(jwt);

assertThat(result).isNotNull();
assertThat(result.getErrors()).isEmpty();
}
}
Loading