* Update to new graph api Update to new graph api * Test update And remove unused import Test update and remove unused import * Update to constant for test Update to constant for test * Formatting/Hard Code Changes Formatting/Hard Code Changes * Added flag to set which graph api to use Added flag to set which graph api to use * changes to abstract user groups changes to abstract user groups * Code qual fixes Code qual fixes * code qual fixes code qual fixes * code cleanup code cleanup * code cleanup code cleanup * Go to single UserGroupProperties Go to single UserGroupProperties * Removed api token from securitycontext Removed api token from securitycontext * cleanup cleanup * Add v2 readme added v2 graph readme section, removed bool flag in properties, updated v2 properties to be clearer * Added azure ad v2 api sample Added azure ad v2 api sample * Sample update Sample update
This commit is contained in:
Родитель
fbc9fa9958
Коммит
2490339bf4
|
@ -0,0 +1,82 @@
|
|||
### How to configure
|
||||
|
||||
#### Register your application with your Azure Active Directory Tenant
|
||||
|
||||
Follow the guide [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app).
|
||||
|
||||
#### Configure groups for sign in user
|
||||
|
||||
In order to try the authorization action with this sample with minimum effort, [configure the user and groups in Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-groups-create-azure-portal), configure the user with `group1`.
|
||||
|
||||
|
||||
#### Configure application.properties
|
||||
|
||||
```properties
|
||||
spring.security.oauth2.client.registration.azure.client-id=xxxxxx-your-client-id-xxxxxx
|
||||
spring.security.oauth2.client.registration.azure.client-secret=xxxxxx-your-client-secret-xxxxxx
|
||||
|
||||
azure.activedirectory.tenant-id=xxxxxx-your-tenant-id-xxxxxx
|
||||
# It's suggested the logged in user should at least belong to one of the below groups
|
||||
# If not, the logged in user will not be able to access any authorization controller rest APIs
|
||||
azure.activedirectory.active-directory-groups=group1, group2
|
||||
```
|
||||
|
||||
### How to run
|
||||
|
||||
- Use Maven
|
||||
|
||||
```
|
||||
# Under azure-spring-boot project root directory
|
||||
mvn clean install -DskipTests
|
||||
cd azure-spring-boot-samples
|
||||
cd azure-active-directory-spring-boot-backend-sample
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### Check the authentication and authorization
|
||||
|
||||
1. Access http://localhost:8080
|
||||
2. Login
|
||||
3. Access `group1 Message` link, should success
|
||||
4. Access `group2 Message` link, should fail with forbidden error message
|
||||
|
||||
|
||||
### Want to take full control over every configuration property
|
||||
|
||||
If you want to adjust the configuration properties according to certain requirements, try below application.properties and change accordingly.
|
||||
|
||||
```properties
|
||||
spring.security.oauth2.client.registration.azure.client-id=xxxxxx-your-client-id-xxxxxx
|
||||
spring.security.oauth2.client.registration.azure.client-secret=xxxxxx-your-client-secret-xxxxxx
|
||||
spring.security.oauth2.client.registration.azure.client-name=Azure
|
||||
spring.security.oauth2.client.registration.azure.provider=azure-oauth-provider
|
||||
spring.security.oauth2.client.registration.azure.scope=openid, https://graph.microsoft.com/user.read
|
||||
spring.security.oauth2.client.registration.azure.redirect-uri-template={baseUrl}/login/oauth2/code/{registrationId}
|
||||
spring.security.oauth2.client.registration.azure.client-authentication-method=basic
|
||||
spring.security.oauth2.client.registration.azure.authorization-grant-type=authorization_code
|
||||
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.authorization-uri=https://login.microsoftonline.com/common/oauth2/authorize
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.token-uri=https://login.microsoftonline.com/common/oauth2/token
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.user-info-uri=https://login.microsoftonline.com/common/openid/userinfo
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.jwk-set-uri=https://login.microsoftonline.com/common/discovery/keys
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.user-name-attribute=name
|
||||
|
||||
azure.activedirectory.tenant-id=xxxxxx-your-tenant-id-xxxxxx
|
||||
azure.activedirectory.active-directory-groups=group1, group2
|
||||
```
|
||||
|
||||
### FAQ
|
||||
|
||||
#### If registered application is not multi-tenanted, how to run this sample?
|
||||
In this auto-configuration, by [default](https://github.com/Microsoft/azure-spring-boot/blob/master/azure-spring-boot/src/main/resources/aad-oauth2-common.properties#L1-L4) `/common` is used for the tenant value. According to [Active Directory Sign In Request format](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#send-the-sign-in-request), if your application is not multi-tenanted, you have to configure a tenant specific authorization endpoints.
|
||||
|
||||
Configure endpoints with specific tenant-id by replacing `common` in your application.properties file:
|
||||
```properties
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.authorization-uri=https://login.microsoftonline.com/{your-tenant-id}/oauth2/authorize
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.token-uri=https://login.microsoftonline.com/{your-tenant-id}/oauth2/token
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.user-info-uri=https://login.microsoftonline.com/{your-tenant-id}/openid/userinfo
|
||||
spring.security.oauth2.client.provider.azure-oauth-provider.jwk-set-uri=https://login.microsoftonline.com/{your-tenant-id}/discovery/keys
|
||||
```
|
||||
|
||||
#### Meet with `AADSTS240002: Input id_token cannot be used as 'urn:ietf:params:oauth:grant-type:jwt-bearer' grant` error.
|
||||
In Azure portal, app registration manifest page, configure `oauth2AllowImplicitFlow` in your application manifest to `true`. See [this issue](https://github.com/MicrosoftDocs/azure-docs/issues/8121#issuecomment-387090099) for details on this workaround.
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-spring-boot-samples</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>azure-active-directory-v2-spring-boot-backend-sample</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Azure AD Spring Security 5 OAuth2 V2 Integration Spring Boot Sample</name>
|
||||
<description>Azure AD Spring Security 5 OAuth2 V2 Integration Spring Boot Sample</description>
|
||||
<url>https://github.com/Microsoft/azure-spring-boot</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-active-directory-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf.extras</groupId>
|
||||
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<project.rootdir>${project.basedir}/../..</project.rootdir>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
package sample.aad;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AzureADOAuth2V2BackendSampleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AzureADOAuth2V2BackendSampleApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
package sample.aad.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
public class HomeController {
|
||||
@Autowired
|
||||
private OAuth2AuthorizedClientService authorizedClientService;
|
||||
|
||||
@GetMapping("group1")
|
||||
@ResponseBody
|
||||
@PreAuthorize("hasRole('ROLE_group1')")
|
||||
public String group1() {
|
||||
return "group1 message";
|
||||
}
|
||||
|
||||
@GetMapping("group2")
|
||||
@ResponseBody
|
||||
@PreAuthorize("hasRole('ROLE_group2')")
|
||||
public String group2() {
|
||||
return "group2 message";
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(Model model, OAuth2AuthenticationToken authentication) {
|
||||
final OAuth2AuthorizedClient authorizedClient =
|
||||
this.authorizedClientService.loadAuthorizedClient(
|
||||
authentication.getAuthorizedClientRegistrationId(),
|
||||
authentication.getName());
|
||||
model.addAttribute("userName", authentication.getName());
|
||||
model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
|
||||
return "index";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
package sample.aad.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
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.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class AADOAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Autowired
|
||||
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.oauth2Login()
|
||||
.userInfoEndpoint()
|
||||
.oidcUserService(oidcUserService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
spring.security.oauth2.client.registration.azure.client-id=xxxxxx-your-client-id-xxxxxx
|
||||
spring.security.oauth2.client.registration.azure.client-secret=xxxxxx-your-client-secret-xxxxxx
|
||||
|
||||
azure.activedirectory.tenant-id=xxxxxx-your-tenant-id-xxxxxx
|
||||
|
||||
# It's suggested the logged in user should at least belong to one of the below groups
|
||||
# If not, the logged in user will not be able to access any authorization controller rest APIs
|
||||
azure.activedirectory.active-directory-groups=group1, group2
|
||||
azure.activedirectory.environment=global-v2-graph
|
||||
azure.activedirectory.user-group.key=@odata.type
|
||||
azure.activedirectory.user-group.value=#microsoft.graph.group
|
||||
azure.activedirectory.user-group.object-id-key=id
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
|
||||
<head>
|
||||
<title>Spring Security - OAuth 2.0 Login</title>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()">
|
||||
<div>
|
||||
<span style="font-weight:bold">User: </span><span sec:authentication="name"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span style="font-weight:bold">Default Authorities: </span><span sec:authentication="principal.authorities"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span style="font-weight:bold">Mapped Authorities: </span><span sec:authentication="authorities"></span>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div style="float:right">
|
||||
<form action="#" th:action="@{/logout}" method="post">
|
||||
<input type="submit" value="Logout" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Azure Active Directory OAuth 2.0 Login with Spring Security</h1>
|
||||
<div>
|
||||
You are successfully logged in <span style="font-weight:bold" th:text="${userName}"></span>
|
||||
via the OAuth 2.0 Client <span style="font-weight:bold" th:text="${clientName}"></span>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div> </div>
|
||||
<div>
|
||||
<a href="/group1" >group1 Message</a> |
|
||||
<a href="/group2" >group2 Message</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -143,6 +143,26 @@ Autowire the auth filter and attach it to the filter chain:
|
|||
The roles you want to use within your application have to be [set up in the manifest of your
|
||||
application registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps).
|
||||
|
||||
##### Using The Microsoft Graph API
|
||||
By default, azure-spring-boot is set up to utilize the Azure AD Graph. If you would prefer, it can be set up to utilize the Microsoft Graph instead. In order to do this, you will need to update the app registration in Azure to grant the application permissions to the Microsoft Graph API and add some properties to the application.properties file.
|
||||
|
||||
* **Grant permissions to the application**: After application registration succeeded, go to API permissions - Add a permission, select `Microsoft Graph`, select Delegated permissions, tick `Directory.AccessAsUser.All - Access the directory as the signed-in user` and `Use.Read - Sign in and read user profile`. Click `Add Permissions` (Note: you will need administrator privilege to grant permission). Furthermore, you can remove the API permissions to the Azure Active Directory Graph, as these will not be needed.
|
||||
|
||||
* **Configure your `application properties`**:
|
||||
```properties
|
||||
azure.activedirectory.environment=global-v2-graph
|
||||
azure.activedirectory.user-group.key=@odata.type
|
||||
azure.activedirectory.user-group.value=#microsoft.graph.group
|
||||
azure.activedirectory.user-group.object-id-key=id
|
||||
```
|
||||
|
||||
If you're using [Azure China](https://docs.microsoft.com/en-us/azure/china/china-welcome), please set the environment property in the `application.properties` file to:
|
||||
```properties
|
||||
azure.activedirectory.environment=cn-v2-graph
|
||||
```
|
||||
|
||||
Please refer to [azure-active-directory-v2-spring-boot-backend-sample](../../azure-spring-boot-samples/azure-active-directory-v2-spring-boot-backend-sample/) to see a sample configured to use the Microsoft Graph API.
|
||||
|
||||
#### Allow telemetry
|
||||
Microsoft would like to collect data about how users use this Spring boot starter.
|
||||
Microsoft uses this information to improve our tooling experience. Participation is voluntary.
|
||||
|
|
|
@ -11,10 +11,13 @@ import com.microsoft.aad.adal4j.AuthenticationContext;
|
|||
import com.microsoft.aad.adal4j.AuthenticationResult;
|
||||
import com.microsoft.aad.adal4j.ClientCredential;
|
||||
import com.microsoft.aad.adal4j.UserAssertion;
|
||||
import com.microsoft.azure.spring.autoconfigure.aad.AADAuthenticationProperties.UserGroupProperties;
|
||||
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
import javax.naming.ServiceUnavailableException;
|
||||
import java.io.BufferedReader;
|
||||
|
@ -42,22 +45,43 @@ public class AzureADGraphClient {
|
|||
private final ServiceEndpoints serviceEndpoints;
|
||||
private final AADAuthenticationProperties aadAuthenticationProperties;
|
||||
|
||||
private static final String V2_VERSION_ENV_FLAG = "v2-graph";
|
||||
private boolean aadMicrosoftGraphApiBool;
|
||||
|
||||
public AzureADGraphClient(ClientCredential clientCredential, AADAuthenticationProperties aadAuthProps,
|
||||
ServiceEndpointsProperties serviceEndpointsProps) {
|
||||
this.clientId = clientCredential.getClientId();
|
||||
this.clientSecret = clientCredential.getClientSecret();
|
||||
this.aadAuthenticationProperties = aadAuthProps;
|
||||
this.serviceEndpoints = serviceEndpointsProps.getServiceEndpoints(aadAuthProps.getEnvironment());
|
||||
|
||||
this.initAADMicrosoftGraphApiBool(aadAuthProps.getEnvironment());
|
||||
}
|
||||
|
||||
private void initAADMicrosoftGraphApiBool(String endpointEnv) {
|
||||
this.aadMicrosoftGraphApiBool = false;
|
||||
if (endpointEnv.contains(V2_VERSION_ENV_FLAG)) {
|
||||
this.aadMicrosoftGraphApiBool = true;
|
||||
}
|
||||
}
|
||||
|
||||
private String getUserMembershipsV1(String accessToken) throws IOException {
|
||||
final URL url = new URL(serviceEndpoints.getAadMembershipRestUri());
|
||||
|
||||
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
// Set the appropriate header fields in the request header.
|
||||
conn.setRequestProperty("api-version", "1.6");
|
||||
conn.setRequestProperty("Authorization", accessToken);
|
||||
conn.setRequestProperty("Accept", "application/json;odata=minimalmetadata");
|
||||
|
||||
if (this.aadMicrosoftGraphApiBool) {
|
||||
conn.setRequestMethod(HttpMethod.GET.toString());
|
||||
conn.setRequestProperty(HttpHeaders.AUTHORIZATION,
|
||||
String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), accessToken));
|
||||
conn.setRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
|
||||
conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
} else {
|
||||
conn.setRequestMethod(HttpMethod.GET.toString());
|
||||
conn.setRequestProperty("api-version", "1.6");
|
||||
conn.setRequestProperty(HttpHeaders.AUTHORIZATION, String.format("%s", accessToken));
|
||||
conn.setRequestProperty(HttpHeaders.ACCEPT, "application/json;odata=minimalmetadata");
|
||||
}
|
||||
final String responseInJson = getResponseStringFromConn(conn);
|
||||
final int responseCode = conn.getResponseCode();
|
||||
if (responseCode == HTTPResponse.SC_OK) {
|
||||
|
@ -93,7 +117,6 @@ public class AzureADGraphClient {
|
|||
final JsonNode valuesNode = rootNode.get("value");
|
||||
|
||||
if (valuesNode != null) {
|
||||
|
||||
lUserGroups
|
||||
.addAll(StreamSupport.stream(valuesNode.spliterator(), false).filter(this::isMatchingUserGroupKey)
|
||||
.map(node -> {
|
||||
|
|
|
@ -2,8 +2,15 @@ azure.service.endpoints.cn.aadSigninUri=https://login.partner.microsoftonline.cn
|
|||
azure.service.endpoints.cn.aadGraphApiUri=https://graph.chinacloudapi.cn/
|
||||
azure.service.endpoints.cn.aadKeyDiscoveryUri=https://login.partner.microsoftonline.cn/common/discovery/keys
|
||||
azure.service.endpoints.cn.aadMembershipRestUri=https://graph.chinacloudapi.cn/me/memberOf?api-version=1.6
|
||||
azure.service.endpoints.cn-v2-graph.aadSigninUri=https://login.partner.microsoftonline.cn/
|
||||
azure.service.endpoints.cn-v2-graph.aadGraphApiUri=https://microsoftgraph.chinacloudapi.cn/
|
||||
azure.service.endpoints.cn-v2-graph.aadKeyDiscoveryUri=https://login.partner.microsoftonline.cn/common/discovery/keys
|
||||
azure.service.endpoints.cn-v2-graph.aadMembershipRestUri=https://microsoftgraph.chinacloudapi.cn/v1.0/me/memberOf
|
||||
azure.service.endpoints.global.aadSigninUri=https://login.microsoftonline.com/
|
||||
azure.service.endpoints.global.aadGraphApiUri=https://graph.windows.net/
|
||||
azure.service.endpoints.global.aadKeyDiscoveryUri=https://login.microsoftonline.com/common/discovery/keys/
|
||||
azure.service.endpoints.global.aadMembershipRestUri=https://graph.windows.net/me/memberOf?api-version=1.6
|
||||
|
||||
azure.service.endpoints.global-v2-graph.aadSigninUri=https://login.microsoftonline.com/
|
||||
azure.service.endpoints.global-v2-graph.aadGraphApiUri=https://graph.microsoft.com/
|
||||
azure.service.endpoints.global-v2-graph.aadKeyDiscoveryUri=https://login.microsoftonline.com/common/discovery/keys/
|
||||
azure.service.endpoints.global-v2-graph.aadMembershipRestUri=https://graph.microsoft.com/v1.0/me/memberOf
|
||||
|
|
|
@ -55,7 +55,7 @@ public class AADAuthenticationAutoConfigurationTest {
|
|||
assertThat(serviceEndpointsProperties.getEndpoints()).isNotEmpty();
|
||||
|
||||
final Map<String, ServiceEndpoints> endpoints = serviceEndpointsProperties.getEndpoints();
|
||||
assertThat(endpoints).hasSize(2);
|
||||
assertThat(endpoints).hasSize(4);
|
||||
assertThat(endpoints.get("cn")).isNotNull()
|
||||
.extracting(ServiceEndpoints::getAadGraphApiUri, ServiceEndpoints::getAadKeyDiscoveryUri,
|
||||
ServiceEndpoints::getAadMembershipRestUri, ServiceEndpoints::getAadSigninUri)
|
||||
|
|
|
@ -96,7 +96,7 @@ public class AADOAuth2ConfigTest {
|
|||
assertThat(serviceEndpointsProperties.getEndpoints()).isNotEmpty();
|
||||
|
||||
final Map<String, ServiceEndpoints> endpoints = serviceEndpointsProperties.getEndpoints();
|
||||
assertThat(endpoints).hasSize(2);
|
||||
assertThat(endpoints).hasSize(4);
|
||||
assertThat(endpoints.get("cn")).isNotNull()
|
||||
.extracting(ServiceEndpoints::getAadGraphApiUri, ServiceEndpoints::getAadKeyDiscoveryUri,
|
||||
ServiceEndpoints::getAadMembershipRestUri, ServiceEndpoints::getAadSigninUri)
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* 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.spring.autoconfigure.aad;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MicrosoftGraphConstants {
|
||||
public static final String SERVICE_ENVIRONMENT_PROPERTY = "azure.activedirectory.environment";
|
||||
public static final String CLIENT_ID_PROPERTY = "azure.activedirectory.client-id";
|
||||
public static final String CLIENT_SECRET_PROPERTY = "azure.activedirectory.client-secret";
|
||||
public static final String TARGETED_GROUPS_PROPERTY = "azure.activedirectory.active-directory-groups";
|
||||
public static final String TENANT_ID_PROPERTY = "azure.activedirectory.tenant-id";
|
||||
|
||||
public static final String DEFAULT_ENVIRONMENT = "global";
|
||||
public static final String CLIENT_ID = "real_client_id";
|
||||
public static final String CLIENT_SECRET = "real_client_secret";
|
||||
public static final List<String> TARGETED_GROUPS = Arrays.asList("group1", "group2", "group3");
|
||||
|
||||
public static final String TOKEN_HEADER = "Authorization";
|
||||
public static final String BEARER_TOKEN = "Bearer real_jtw_bearer_token";
|
||||
|
||||
public static final String USERGROUPS_JSON = "{\n" +
|
||||
" \"odata.metadata\": \"https://graph.windows.net/myorganization/$metadata#directoryObjects\",\n" +
|
||||
" \"value\": [\n" +
|
||||
" {\n" +
|
||||
" \"@odata.type\": \"#microsoft.graph.group\",\n" +
|
||||
" \"id\": \"12345678-7baf-48ce-96f4-a2d60c26391e\",\n" +
|
||||
" \"deletedDateTime\": null,\n" +
|
||||
" \"classification\": null,\n" +
|
||||
" \"createdDateTime\": \"2017-08-02T12:54:37Z\",\n" +
|
||||
" \"creationOptions\": [],\n" +
|
||||
" \"description\": \"this is group1\",\n" +
|
||||
" \"displayName\": \"group1\",\n" +
|
||||
" \"groupTypes\": [],\n" +
|
||||
" \"mail\": null,\n" +
|
||||
" \"mailEnabled\": false,\n" +
|
||||
" \"mailNickname\": \"something\",\n" +
|
||||
" \"onPremisesLastSyncDateTime\": null,\n" +
|
||||
" \"onPremisesSecurityIdentifier\": null,\n" +
|
||||
" \"onPremisesSyncEnabled\": null,\n" +
|
||||
" \"preferredDataLocation\": null,\n" +
|
||||
" \"proxyAddresses\": [],\n" +
|
||||
" \"renewedDateTime\": \"2017-08-02T12:54:37Z\",\n" +
|
||||
" \"resourceBehaviorOptions\": [],\n" +
|
||||
" \"resourceProvisioningOptions\": [],\n" +
|
||||
" \"securityEnabled\": true,\n" +
|
||||
" \"visibility\": null,\n" +
|
||||
" \"onPremisesProvisioningErrors\": []\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"@odata.type\": \"#microsoft.graph.group\",\n" +
|
||||
" \"id\": \"12345678-e757-4474-b9c4-3f00a9ac17a0\",\n" +
|
||||
" \"deletedDateTime\": null,\n" +
|
||||
" \"classification\": null,\n" +
|
||||
" \"createdDateTime\": \"2017-08-09T13:45:03Z\",\n" +
|
||||
" \"creationOptions\": [],\n" +
|
||||
" \"description\": \"this is group2\",\n" +
|
||||
" \"displayName\": \"group2\",\n" +
|
||||
" \"groupTypes\": [],\n" +
|
||||
" \"mail\": null,\n" +
|
||||
" \"mailEnabled\": false,\n" +
|
||||
" \"mailNickname\": \"somethingelse\",\n" +
|
||||
" \"onPremisesLastSyncDateTime\": null,\n" +
|
||||
" \"onPremisesSecurityIdentifier\": null,\n" +
|
||||
" \"onPremisesSyncEnabled\": null,\n" +
|
||||
" \"preferredDataLocation\": null,\n" +
|
||||
" \"proxyAddresses\": [],\n" +
|
||||
" \"renewedDateTime\": \"2017-08-09T13:45:03Z\",\n" +
|
||||
" \"resourceBehaviorOptions\": [],\n" +
|
||||
" \"resourceProvisioningOptions\": [],\n" +
|
||||
" \"securityEnabled\": true,\n" +
|
||||
" \"visibility\": null,\n" +
|
||||
" \"onPremisesProvisioningErrors\": []\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"@odata.type\": \"#microsoft.graph.group\",\n" +
|
||||
" \"id\": \"12345678-86a4-4237-aeb0-60bad29c1de0\",\n" +
|
||||
" \"deletedDateTime\": null,\n" +
|
||||
" \"classification\": null,\n" +
|
||||
" \"createdDateTime\": \"2017-08-09T05:41:43Z\",\n" +
|
||||
" \"creationOptions\": [],\n" +
|
||||
" \"description\": \"this is group3\",\n" +
|
||||
" \"displayName\": \"group3\",\n" +
|
||||
" \"groupTypes\": [],\n" +
|
||||
" \"mail\": null,\n" +
|
||||
" \"mailEnabled\": false,\n" +
|
||||
" \"mailNickname\": \"somethingelse\",\n" +
|
||||
" \"onPremisesLastSyncDateTime\": null,\n" +
|
||||
" \"onPremisesSecurityIdentifier\": null,\n" +
|
||||
" \"onPremisesSyncEnabled\": null,\n" +
|
||||
" \"preferredDataLocation\": null,\n" +
|
||||
" \"proxyAddresses\": [],\n" +
|
||||
" \"renewedDateTime\": \"2017-08-09T05:41:43Z\",\n" +
|
||||
" \"resourceBehaviorOptions\": [],\n" +
|
||||
" \"resourceProvisioningOptions\": [],\n" +
|
||||
" \"securityEnabled\": true,\n" +
|
||||
" \"visibility\": null,\n" +
|
||||
" \"onPremisesProvisioningErrors\": []\n" +
|
||||
" }" +
|
||||
"],\n" +
|
||||
" \"odata.nextLink\": \"directoryObjects/$/Microsoft.DirectoryServices.User/" +
|
||||
"12345678-2898-434a-a370-8ec974c2fb57/memberOf?$skiptoken=X'4453707407000100000000" +
|
||||
"00000000100000009D29CBA7B45D854A84FF7F9B636BD9DC000000000000000000000017312E322E3" +
|
||||
"834302E3131333535362E312E342E3233333100000000'\"\n" +
|
||||
"}";
|
||||
|
||||
/** Token from https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-id-and-access-tokens */
|
||||
public static final String JWT_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1" +
|
||||
"iYTlnb0VLWSJ9.eyJhdWQiOiI2NzMxZGU3Ni0xNGE2LTQ5YWUtOTdiYy02ZWJhNjkxNDM5MWUiLCJpc3MiOiJodHRwczovL2xvZ2lu" +
|
||||
"Lm1pY3Jvc29mdG9ubGluZS5jb20vYjk0MTk4MTgtMDlhZi00OWMyLWIwYzMtNjUzYWRjMWYzNzZlL3YyLjAiLCJpYXQiOjE0NTIyOD" +
|
||||
"UzMzEsIm5iZiI6MTQ1MjI4NTMzMSwiZXhwIjoxNDUyMjg5MjMxLCJuYW1lIjoiQmFiZSBSdXRoIiwibm9uY2UiOiIxMjM0NSIsIm9p" +
|
||||
"ZCI6ImExZGJkZGU4LWU0ZjktNDU3MS1hZDkzLTMwNTllMzc1MGQyMyIsInByZWZlcnJlZF91c2VybmFtZSI6InRoZWdyZWF0YmFtYm" +
|
||||
"lub0BueXkub25taWNyb3NvZnQuY29tIiwic3ViIjoiTUY0Zi1nZ1dNRWppMTJLeW5KVU5RWnBoYVVUdkxjUXVnNWpkRjJubDAxUSIs" +
|
||||
"InRpZCI6ImI5NDE5ODE4LTA5YWYtNDljMi1iMGMzLTY1M2FkYzFmMzc2ZSIsInZlciI6IjIuMCJ9.p_rYdrtJ1oCmgDBggNHB9O38K" +
|
||||
"TnLCMGbMDODdirdmZbmJcTHiZDdtTc-hguu3krhbtOsoYM2HJeZM3Wsbp_YcfSKDY--X_NobMNsxbT7bqZHxDnA2jTMyrmt5v2EKUn" +
|
||||
"EeVtSiJXyO3JWUq9R0dO-m4o9_8jGP6zHtR62zLaotTBYHmgeKpZgTFB9WtUq8DVdyMn_HSvQEfz-LWqckbcTwM_9RNKoGRVk38KCh" +
|
||||
"VJo4z5LkksYRarDo8QgQ7xEKmYmPvRr_I7gvM2bmlZQds2OeqWLB1NSNbFZqyFOCgYn3bAQ-nEQSKwBaA36jYGPOVG2r2Qv1uKcpSO" +
|
||||
"xzxaQybzYpQ";
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* 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.spring.autoconfigure.aad;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import com.microsoft.aad.adal4j.ClientCredential;
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
public class UserPrincipalMicrosoftGraphTest {
|
||||
private AzureADGraphClient graphClientMock;
|
||||
|
||||
@Rule
|
||||
public WireMockRule wireMockRule = new WireMockRule(9519);
|
||||
|
||||
private ClientCredential credential;
|
||||
private AADAuthenticationProperties aadAuthProps;
|
||||
private ServiceEndpointsProperties endpointsProps;
|
||||
private String accessToken;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
accessToken = MicrosoftGraphConstants.BEARER_TOKEN;
|
||||
aadAuthProps = new AADAuthenticationProperties();
|
||||
aadAuthProps.setEnvironment("global-v2-graph");
|
||||
aadAuthProps.getUserGroup().setKey("@odata.type");
|
||||
aadAuthProps.getUserGroup().setValue("#microsoft.graph.group");
|
||||
aadAuthProps.getUserGroup().setObjectIDKey("id");
|
||||
endpointsProps = new ServiceEndpointsProperties();
|
||||
final ServiceEndpoints serviceEndpoints = new ServiceEndpoints();
|
||||
serviceEndpoints.setAadMembershipRestUri("http://localhost:9519/memberOf");
|
||||
endpointsProps.getEndpoints().put("global-v2-graph", serviceEndpoints);
|
||||
credential = new ClientCredential("client", "pass");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getAuthoritiesByUserGroups() throws Exception {
|
||||
|
||||
aadAuthProps.getUserGroup().setAllowedGroups(Collections.singletonList("group1"));
|
||||
this.graphClientMock = new AzureADGraphClient(credential, aadAuthProps, endpointsProps);
|
||||
|
||||
stubFor(get(urlEqualTo("/memberOf")).withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON_VALUE))
|
||||
.willReturn(aResponse().withStatus(200)
|
||||
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.withBody(MicrosoftGraphConstants.USERGROUPS_JSON)));
|
||||
|
||||
assertThat(graphClientMock.getGrantedAuthorities(MicrosoftGraphConstants.BEARER_TOKEN)).isNotEmpty()
|
||||
.extracting(GrantedAuthority::getAuthority).containsExactly("ROLE_group1");
|
||||
|
||||
verify(getRequestedFor(urlMatching("/memberOf"))
|
||||
.withHeader(HttpHeaders.AUTHORIZATION,
|
||||
equalTo(String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), accessToken)))
|
||||
.withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON_VALUE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGroups() throws Exception {
|
||||
|
||||
aadAuthProps.setActiveDirectoryGroups(Arrays.asList("group1", "group2", "group3"));
|
||||
this.graphClientMock = new AzureADGraphClient(credential, aadAuthProps, endpointsProps);
|
||||
|
||||
stubFor(get(urlEqualTo("/memberOf")).withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON_VALUE))
|
||||
.willReturn(aResponse().withStatus(200)
|
||||
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.withBody(MicrosoftGraphConstants.USERGROUPS_JSON)));
|
||||
final Collection<? extends GrantedAuthority> authorities = graphClientMock
|
||||
.getGrantedAuthorities(MicrosoftGraphConstants.BEARER_TOKEN);
|
||||
|
||||
assertThat(authorities).isNotEmpty().extracting(GrantedAuthority::getAuthority)
|
||||
.containsExactly("ROLE_group1", "ROLE_group2", "ROLE_group3");
|
||||
|
||||
verify(getRequestedFor(urlMatching("/memberOf"))
|
||||
.withHeader(HttpHeaders.AUTHORIZATION,
|
||||
equalTo(String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), accessToken)))
|
||||
.withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON_VALUE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userPrinciplaIsSerializable() throws ParseException, IOException, ClassNotFoundException {
|
||||
final File tmpOutputFile = File.createTempFile("test-user-principal", "txt");
|
||||
|
||||
try (final FileOutputStream fileOutputStream = new FileOutputStream(tmpOutputFile);
|
||||
final ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
|
||||
final FileInputStream fileInputStream = new FileInputStream(tmpOutputFile);
|
||||
final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);) {
|
||||
|
||||
final JWSObject jwsObject = JWSObject.parse(MicrosoftGraphConstants.JWT_TOKEN);
|
||||
final JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().subject("fake-subject").build();
|
||||
final UserPrincipal principal = new UserPrincipal(jwsObject, jwtClaimsSet);
|
||||
|
||||
objectOutputStream.writeObject(principal);
|
||||
|
||||
final UserPrincipal serializedPrincipal = (UserPrincipal) objectInputStream.readObject();
|
||||
|
||||
Assert.assertNotNull("Serialized UserPrincipal not null", serializedPrincipal);
|
||||
Assert.assertTrue("Serialized UserPrincipal kid not empty",
|
||||
!StringUtils.isEmpty(serializedPrincipal.getKid()));
|
||||
Assert.assertNotNull("Serialized UserPrincipal claims not null.", serializedPrincipal.getClaims());
|
||||
Assert.assertTrue("Serialized UserPrincipal claims not empty.",
|
||||
serializedPrincipal.getClaims().size() > 0);
|
||||
} finally {
|
||||
Files.deleteIfExists(tmpOutputFile.toPath());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
|
@ -57,15 +59,19 @@ public class UserPrincipalTest {
|
|||
aadAuthProps.getUserGroup().setAllowedGroups(Collections.singletonList("group1"));
|
||||
this.graphClientMock = new AzureADGraphClient(credential, aadAuthProps, endpointsProps);
|
||||
|
||||
stubFor(get(urlEqualTo("/memberOf")).withHeader("Accept", equalTo("application/json;odata=minimalmetadata"))
|
||||
.willReturn(aResponse().withStatus(200).withHeader("Content-Type", "application/json")
|
||||
stubFor(get(urlEqualTo("/memberOf")).withHeader(HttpHeaders.ACCEPT,
|
||||
equalTo("application/json;odata=minimalmetadata"))
|
||||
.willReturn(aResponse().withStatus(200)
|
||||
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.withBody(Constants.USERGROUPS_JSON)));
|
||||
|
||||
assertThat(graphClientMock.getGrantedAuthorities(Constants.BEARER_TOKEN)).isNotEmpty()
|
||||
.extracting(GrantedAuthority::getAuthority).containsExactly("ROLE_group1");
|
||||
|
||||
verify(getRequestedFor(urlMatching("/memberOf")).withHeader("Authorization", equalTo(accessToken))
|
||||
.withHeader("Accept", equalTo("application/json;odata=minimalmetadata"))
|
||||
verify(getRequestedFor(urlMatching("/memberOf"))
|
||||
.withHeader(HttpHeaders.AUTHORIZATION,
|
||||
equalTo(String.format("%s", accessToken)))
|
||||
.withHeader(HttpHeaders.ACCEPT, equalTo("application/json;odata=minimalmetadata"))
|
||||
.withHeader("api-version", equalTo("1.6")));
|
||||
}
|
||||
|
||||
|
@ -75,8 +81,10 @@ public class UserPrincipalTest {
|
|||
aadAuthProps.setActiveDirectoryGroups(Arrays.asList("group1", "group2", "group3"));
|
||||
this.graphClientMock = new AzureADGraphClient(credential, aadAuthProps, endpointsProps);
|
||||
|
||||
stubFor(get(urlEqualTo("/memberOf")).withHeader("Accept", equalTo("application/json;odata=minimalmetadata"))
|
||||
.willReturn(aResponse().withStatus(200).withHeader("Content-Type", "application/json")
|
||||
stubFor(get(urlEqualTo("/memberOf")).withHeader(HttpHeaders.ACCEPT,
|
||||
equalTo("application/json;odata=minimalmetadata"))
|
||||
.willReturn(aResponse().withStatus(200)
|
||||
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.withBody(Constants.USERGROUPS_JSON)));
|
||||
final Collection<? extends GrantedAuthority> authorities = graphClientMock
|
||||
.getGrantedAuthorities(Constants.BEARER_TOKEN);
|
||||
|
@ -84,8 +92,10 @@ public class UserPrincipalTest {
|
|||
assertThat(authorities).isNotEmpty().extracting(GrantedAuthority::getAuthority)
|
||||
.containsExactly("ROLE_group1", "ROLE_group2", "ROLE_group3");
|
||||
|
||||
verify(getRequestedFor(urlMatching("/memberOf")).withHeader("Authorization", equalTo(accessToken))
|
||||
.withHeader("Accept", equalTo("application/json;odata=minimalmetadata"))
|
||||
verify(getRequestedFor(urlMatching("/memberOf"))
|
||||
.withHeader(HttpHeaders.AUTHORIZATION,
|
||||
equalTo(String.format("%s", accessToken)))
|
||||
.withHeader(HttpHeaders.ACCEPT, equalTo("application/json;odata=minimalmetadata"))
|
||||
.withHeader("api-version", equalTo("1.6")));
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче