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

Azure AD Integration #97

merged 13 commits into from
Aug 7, 2017

Conversation

yaweiw
Copy link
Contributor

@yaweiw yaweiw commented Aug 4, 2017

autoconfigure for AAD integration jwt token filter

@msftclas
Copy link

msftclas commented Aug 4, 2017

@yaweiw,
Thanks for your contribution as a Microsoft full-time employee or intern. You do not need to sign a CLA.
Thanks,
Microsoft Pull Request Bot

</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,

<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>4.39.2</version>
Copy link
Contributor

Choose a reason for hiding this comment

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

add the version in parent pom

Copy link
Contributor

Choose a reason for hiding this comment

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

add this package to parent's depedency list

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

@NotEmpty
private List<String> allowedRolesGroups;

private final String aadSignInUri = "https://login.microsoftonline.com/";
Copy link
Contributor

Choose a reason for hiding this comment

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

static?

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.

define as constants?

}

private JWKSet loadAadPublicKeys() throws IOException, ParseException {
final int connectTimeout = 1000;
Copy link
Contributor

Choose a reason for hiding this comment

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

just cusrious, is there plan to make these configuration items configurable for users?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as discussed, theses settings are used to prevent overloading aad discover keys endpoint.

i++;
}
} catch (Exception e) {
e.printStackTrace();
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure about business logic, besides print out, is it necessary to rethrow the exception?

Copy link
Contributor

Choose a reason for hiding this comment

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

and catch specific exception instead of generic exception

Copy link
Contributor Author

Choose a reason for hiding this comment

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

url.openConnection() throws Exception. so no specific exception in this case.

@@ -229,6 +229,8 @@

<modules>
<module>../azure-spring-common</module>
<module>../../activedirectory/azure-ad-integration-spring-boot-autoconfigure</module>
<module>../../activedirectory/azure-ad-integration-spring-boot-autoconfigure-sample</module>
Copy link
Contributor

Choose a reason for hiding this comment

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

seems there's no xxx-sample folder in this pr..

@codecov-io
Copy link

codecov-io commented Aug 4, 2017

Codecov Report

Merging #97 into master will decrease coverage by 6.63%.
The diff coverage is 12.9%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #97      +/-   ##
==========================================
- Coverage   50.48%   43.84%   -6.64%     
==========================================
  Files          41       49       +8     
  Lines         723      878     +155     
  Branches       72       89      +17     
==========================================
+ Hits          365      385      +20     
- Misses        321      456     +135     
  Partials       37       37
Impacted Files Coverage Δ
...osoft/azure/autoconfigure/aad/AzureADJwtToken.java 0% <0%> (ø)
.../autoconfigure/aad/JacksonObjectMapperFactory.java 0% <0%> (ø)
...azure/autoconfigure/aad/AzureADUserMembership.java 0% <0%> (ø)
...e/autoconfigure/aad/CustomPermissionEvaluator.java 0% <0%> (ø)
...zure/autoconfigure/aad/DirectoryServiceObject.java 0% <0%> (ø)
...nfigure/aad/AzureADJwtFilterAutoConfiguration.java 100% <100%> (ø)
...azure/autoconfigure/aad/AzureADJwtTokenFilter.java 8.16% <8.16%> (ø)
.../autoconfigure/aad/AzureADJwtFilterProperties.java 83.33% <83.33%> (ø)
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f39f426...0621ed3. Read the comment docs.

<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
Copy link
Contributor

Choose a reason for hiding this comment

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

1.8 for other starters?

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

Copy link

@aovechki aovechki left a comment

Choose a reason for hiding this comment

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

can you please include a readme showing basic usage in the initial PR?

if (responseCode == 200) {
return responseInJson;
} else {
throw new Exception(responseInJson);
Copy link

Choose a reason for hiding this comment

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

This should be a more specific exception, depending on 1) the actual response status code 2) parsed response body

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea is for any non-OK responses. Rethrow the exception with response Json. should be self-explanatory for troubleshooting.

conn.setRequestProperty("Accept", "application/json;odata=minimalmetadata");
String responseInJson = getResponseStringFromConn(conn);
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
Copy link

Choose a reason for hiding this comment

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

is there a const for http 200 already defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes. HTTPResponse.SC_OK. thanks.

}

private JWKSet loadAadPublicKeys() throws IOException, ParseException {
final int connectTimeout = 1000;
Copy link

Choose a reason for hiding this comment

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

It's better to include time unit in the name as its suffix, ie connectTimeoutInSeconds

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree. the unit here is in milliseconds. the naming was descended from JWKSet load method in nimbusds. But we should make it more intuitive by adding units.
e.g.
public static JWKSet load(final URL url,
final int connectTimeout,
final int readTimeout,
final int sizeLimit)
throws IOException, ParseException {

}

if (result == null) {
throw new ServiceUnavailableException("authentication result was null");
Copy link

Choose a reason for hiding this comment

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

Consider a more helpful message here, ie Unable to acquire OBO token for

Copy link
Contributor Author

Choose a reason for hiding this comment

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

make sense

public static boolean hasPermission(List<DirectoryServiceObject> customerRolesGroups, List<String> targetRolesGroups) {
boolean permitted = false;
for (DirectoryServiceObject rg : customerRolesGroups) {
if (targetRolesGroups.contains(rg.getDisplayName())) {
Copy link

Choose a reason for hiding this comment

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

is case important here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i think so. they are string literals.

AuthenticationResult result = acquireTokenForGraphApi(
tokenEncoded,
tid);
userProfile = new AzureADUserProfile(result.getAccessToken());
Copy link

Choose a reason for hiding this comment

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

looks like authz code will always run (which will retrieve user's group memberships). But this is not really necessary unless authZ requirements are explicitly configred on the resources that this filter is protecting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the current design, users must specify what AAD roles/groups that can access the API. So membership info is retrieved upon authentication. this can be changed.
@notempty
private List allowedRolesGroups;

try {
String tid = jwtToken.getClaim("tid").toString();

AuthenticationResult result = acquireTokenForGraphApi(
Copy link

Choose a reason for hiding this comment

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

What is the validity period of the graph api token? Could it be cached?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just like normal JWT token, OBO token has a "exp" field in token payload. It can be cached but may not required in this case. doFilterInternal is executed once per request upon authentication. The user will be mapped to "ROLE_ALLOWED" or "ROLE_DISALLOWED" based on it's membership in aad. The second request will require a new token so will not be reused anyway.

* Licensed under the MIT License. See LICENSE in the project root for
* license information.
*/
package com.microsoft.azure.autoconfigure.adintegration;
Copy link

Choose a reason for hiding this comment

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

Consider using AAD or AzureAD for the folder/package instead of adintegration

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

import java.util.ArrayList;
import java.util.List;

public class AzureADUserProfile {
Copy link

Choose a reason for hiding this comment

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

naming: this class effectively loads user's groups, it's not really a "userprofile"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

AuthenticationResult result = acquireTokenForGraphApi(
tokenEncoded,
tid);
userProfile = new AzureADUserProfile(result.getAccessToken());
Copy link

Choose a reason for hiding this comment

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

Should consider caching user's memberships, it's likely an expensive operation to retrieve them

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 i can get a cache implemented later.

## Usage

### Register the application in Azure AD
* Go to Azure Active Directory - App registrations - New application registration to register the application in Azure Active Directory. `Application ID` is `clientId` in `application.properties`.
Copy link
Contributor

Choose a reason for hiding this comment

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

on Azure Portal

Copy link
Contributor

Choose a reason for hiding this comment

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

and description of this autoconfigure package


@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

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?

private List<String> allowedRolesGroups;

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

Choose a reason for hiding this comment

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

static?

pom.xml Outdated
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
<version>${azure.adal4j.version}</version>
Copy link
Contributor

Choose a reason for hiding this comment

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

move this dependency up to the azure lib category.

</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?

}

if (result == null) {
throw new ServiceUnavailableException("authentication result was null");
throw new ServiceUnavailableException(
"unable to acquire on-behalf-of token for client " + aadJwtFilterProp.getClientId());
Copy link

Choose a reason for hiding this comment

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

unable->Unable

@@ -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

@ZhijunZhao ZhijunZhao merged commit 670a965 into master Aug 7, 2017
@ZhijunZhao ZhijunZhao deleted the aadintegration branch August 7, 2017 03:30
chenrujun pushed a commit to chenrujun/azure-sdk-for-java that referenced this pull request Sep 3, 2021
This feature is deprecated in version 3.x.
We will not support these features in 4.0.0.

I found the first PR of this feature: microsoft/azure-spring-boot#97
It's on 2017-08-07.

At that time, there is no spring-security-oauth2-resource-server.
spring-security-oauth2-resource-server first released on 2018-09-21.

refs:

https://repo1.maven.org/maven2/org/springframework/security/spring-security-oauth2-resource-server/
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants