Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Azure AD Integration #97

Merged
merged 13 commits into from
Aug 7, 2017
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Overview
This package provides a Spring Security filter to validate the Jwt token from Azure AD. The Jwt token is also used to acquire a On-Behalf-Of token for Azure AD Graph API so that authenticated user's membership information is available for authorization of access of API resources.
Copy link

Choose a reason for hiding this comment

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

Should insert a paragraph that describes the scenario that this enables, ie using Azure AD to implement authN/Z for spring-boot based Rest APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure


### Register the application in Azure AD
* Go to Azure Portal - Azure Active Directory - App registrations - New application registration to register the application in Azure Active Directory. `Application ID` is `clientId` in `application.properties`.
* After application registration succeeded, go to API ACCESS - Required permissions - DELEGATED PERMISSIONS, tick `Access the directory as the signed-in user` and `Sign in and read user profile`.
* Click `Grant Permissions` (Note: you will need administrator privilege to grant permission).
* Go to API ACCESS - Keys to create a secret key (`clientSecret`).

### Add the dependency

`azure-ad-integration-spring-boot-autoconfigure` is published on Maven Central Repository.
If you are using Maven, add the following dependency.

```xml
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-ad-integration-spring-boot-autoconfigure</artifactId>
<version>0.1.4</version>
</dependency>
```

### Add application properties

Open `application.properties` file and add below properties.

```
azure.activedirectory.clientId=Application-ID-in-AAD-App-registrations
azure.activedirectory.clientSecret=Key-in-AAD-API-ACCESS
azure.activedirectory.allowedRolesGroups=roles-groups-allowed-to-access-API-resource e.g. group1,group2,group3
```

### Configure WebSecurityConfigurerAdapter class to use `AzureADJwtTokenFilter`

```
@Autowired
private AzureADJwtTokenFilter aadJwtFilter;
```

You can refer to [azure-ad-integration-spring-boot-autoconfigure-sample]() for how to integrate Spring Security and Azure AD for authentication and authorization in a Single Page Application (SPA) scenario.
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-spring-boot-starter-parent</artifactId>
<version>0.1.4</version>
<relativePath>../../common/azure-spring-boot-starter-parent/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>azure-ad-integration-spring-boot-autoconfigure</artifactId>
Copy link
Contributor

Choose a reason for hiding this comment

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

normally service name + spring boot autoconfiguration, no need integration,

<packaging>jar</packaging>

<name>Azure AD Spring Security Integration Spring Boot Autoconfigure</name>
<description>Spring Boot auto configuration package for Azure AD and Spring Security Integration</description>
<url>https://github.com/Microsoft/azure-spring-boot-starters</url>

<licenses>
<license>
<name>MIT</name>
<url>https://github.com/Microsoft/azure-spring-boot-starters/blob/master/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<id>yaweiw</id>
<name>Yawei Wang</name>
<email>yaweiw@microsoft.com</email>
</developer>
</developers>

<scm>
<connection>scm:git:git://github.com/Microsoft/azure-spring-boot-starters.git</connection>
<developerConnection>scm:git:ssh://github.com:Microsoft/azure-spring-boot-starters.git</developerConnection>
<url>https://github.com/Microsoft/azure-spring-boot-starters/tree/master</url>
</scm>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-spring-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Copy link
Contributor

Choose a reason for hiding this comment

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

spring-boot-starter-test contains mockito already, are you demanding different version of mockito here?

</dependency>
</dependencies>


</project>
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. See LICENSE in the project root for
* license information.
*/
package com.microsoft.azure.autoconfigure.aad;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.context.annotation.Scope;

@Configuration
@ConditionalOnMissingBean(AzureADJwtTokenFilter.class)
@EnableConfigurationProperties(AzureADJwtFilterProperties.class)
public class AzureADJwtFilterAutoConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(AzureADJwtFilterProperties.class);

private final AzureADJwtFilterProperties aadJwtFilterProperties;

public AzureADJwtFilterAutoConfiguration(AzureADJwtFilterProperties aadJwtFilterProperties) {
this.aadJwtFilterProperties = aadJwtFilterProperties;
}

/**
* Declare AzureADJwtFilter bean.
*
* @return AzureADJwtFilter bean
*/
@Bean
@Scope("prototype")
public AzureADJwtTokenFilter azureADJwtFilter() {
LOG.info("AzureADJwtTokenFilter Constructor.");
return new AzureADJwtTokenFilter(aadJwtFilterProperties);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for
* license information.
*/
package com.microsoft.azure.autoconfigure.aad;

import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

import java.util.List;

@Validated
@ConfigurationProperties("azure.activedirectory")
public class AzureADJwtFilterProperties {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please reference this commit 86aa124

to add Java doc for each field. There will be hints when user use the properties.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

/**
* Registered application ID in Azure AD.
*/
@NotEmpty
private String clientId;
/**
* API Access Key of the registered application.
*/
@NotEmpty
private String clientSecret;
/**
* Allowed roles and groups in Azure AD.
*/
@NotEmpty
private List<String> allowedRolesGroups;

private static final String aadSignInUri = "https://login.microsoftonline.com/";
private static final String aadGraphAPIUri = "https://graph.windows.net/";

public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getAadSignInUri() {
return aadSignInUri;
}
public String getAadGraphAPIUri() {
return aadGraphAPIUri;
}

public List<String> getAllowedRolesGroups() {
return allowedRolesGroups;
}
public void setAllowedRolesGroups(List<String> allowedRolesGroups) {
this.allowedRolesGroups = allowedRolesGroups;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for
* license information.
*/
package com.microsoft.azure.autoconfigure.aad;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.proc.*;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.Map;

public final class AzureADJwtToken {
private final JWSObject jwsObject;
private final JWTClaimsSet jwsClaimsSet;
private final JWKSet jwsKeySet;

public AzureADJwtToken(String bearerToken) throws Exception {
final ConfigurableJWTProcessor validator = getAadJwtTokenValidator(bearerToken);
jwsClaimsSet = validator.process(bearerToken, null);
final JWTClaimsSetVerifier verifier = validator.getJWTClaimsSetVerifier();
verifier.verify(jwsClaimsSet, null);
jwsObject = JWSObject.parse(bearerToken);
jwsKeySet = loadAadPublicKeys();
}

private ConfigurableJWTProcessor getAadJwtTokenValidator(
String bearerToken) throws ParseException, JOSEException, BadJOSEException, MalformedURLException {
final ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor();
final JWKSource keySource = new RemoteJWKSet(
new URL("https://login.microsoftonline.com/common/discovery/keys"));
Copy link
Contributor

Choose a reason for hiding this comment

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

Make this constant a static field?

final JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;
final JWSKeySelector keySelector = new JWSVerificationKeySelector(expectedJWSAlg, keySource);
jwtProcessor.setJWSKeySelector(keySelector);

jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier(){
@Override
public void verify(JWTClaimsSet claimsSet, SecurityContext ctx) throws BadJWTException {
super.verify(claimsSet, ctx);
final String issuer = claimsSet.getIssuer();
if (issuer == null || !issuer.contains("https://sts.windows.net/")) {
throw new BadJWTException("Invalid token issuer");
}
}
});
return jwtProcessor;
}

private JWKSet loadAadPublicKeys() throws IOException, ParseException {
final int connectTimeoutinMS = 1000;
final int readTimeoutinMS = 1000;
final int sizeLimitinBytes = 10000;
return JWKSet.load(
new URL("https://login.microsoftonline.com/common/discovery/keys"),
connectTimeoutinMS,
readTimeoutinMS,
sizeLimitinBytes);
}

// claimset
public String getIssuer() {
return jwsClaimsSet == null ? null : jwsClaimsSet.getIssuer();
}
public String getSubject() {
return jwsClaimsSet == null ? null : jwsClaimsSet.getSubject();
}
public Map<String, Object> getClaims() {
return jwsClaimsSet == null ? null : jwsClaimsSet.getClaims();
}
public Object getClaim(String name) {
return jwsClaimsSet == null ? null : jwsClaimsSet.getClaim(name);
}

// header
public String getKid() {
return jwsObject == null ? null : jwsObject.getHeader().getKeyID();
}

// JWK
public JWK getJWKByKid(String kid) {
return jwsKeySet == null ? null : jwsKeySet.getKeyByKeyId(kid);
}

}

Loading