* 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:
wandrille-hubert 2019-09-03 22:20:27 -07:00 коммит произвёл Zhou Liu
Родитель fbc9fa9958
Коммит 2490339bf4
15 изменённых файлов: 615 добавлений и 17 удалений

Просмотреть файл

@ -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>&nbsp;</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>&nbsp;</div>
<div>&nbsp;</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")));
}