-
Notifications
You must be signed in to change notification settings - Fork 460
Conversation
@yaweiw, |
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>azure-ad-integration-spring-boot-autoconfigure</artifactId> |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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/"; |
There was a problem hiding this comment.
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")); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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 Report
@@ 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
Continue to review full report at Codecov.
|
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>1.7</source> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
There was a problem hiding this 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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is case important here?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on Azure Portal
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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")); |
There was a problem hiding this comment.
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/"; |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
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/
autoconfigure for AAD integration jwt token filter