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

Commit d3a887f

Browse files
yaweiwZhijunZhao
authored andcommitted
aad filter sample (#118)
1 parent 3d92eb6 commit d3a887f

37 files changed

+2348
-166
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## Overview
2+
This sample illustrates how to use `azure-ad-integration-spring-boot-autoconfigure` pre-release package to plugin JWT token filter into Spring Security filter chain. The filter injects `UserPrincipal` object that is associated with the thread of the current user request. User's AAD membership info, along with token claimsset, JWS object etc. are accessible from the object which can be used for role based authorization. Methods like `isMemberOf` is also supported.
3+
4+
### Get started
5+
The sample is composed of two layers: Angular JS client and Spring Boot RESTful Web Service. You need to make some changes to get it working with your Azure AD tenant on both sides.
6+
7+
#### Application.properties
8+
You need to have an registered app in your Azure AD tenant and create a security key.
9+
Put Application ID and Key in `clientId` and `clientSecret` respectively e.g.
10+
`azure.activedirectory.clientId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
11+
`azure.activedirectory.clientSecret=ABCDEFGHIJKLMNOOPQRSTUVWXYZABCDEFGHIJKLMNOPQ`
12+
List all the AAD groups `ActiveDirectoryGroups` that you want to have a Spring Security role object mapping to it. The role objects can then be used to manage access to resources that is behind Spring Security. e.g.
13+
`azure.activedirectory.ActiveDirectoryGroups=group1,group2`
14+
You can use `@PreAuthorize` annotation or `UserPrincipal` to manage access to web API based on user's group membership. You will need to change `ROLE_group1` to groups you want to allow to access the API or you will get "Access is denied".
15+
16+
##### Note: The sample retrieves user's group membership using Azure AD graph API which requires the registered app to have `Access the directory as the signed-in user` under `Delegated Permissions`. You need AAD admin privilege to be able to grant the permission in API ACCESS -> Required permission.
17+
18+
19+
#### Angular JS
20+
In `app.js`, make following changes. The client leverages Azure AD library for JS to handle AAD authentication in single page application. The following snippet of code configures adal provider for your registered app.
21+
```
22+
adalProvider.init(
23+
{
24+
instance: 'https://login.microsoftonline.com/',
25+
tenant: 'your-aad-tenant',
26+
clientId: 'your-application-id',
27+
extraQueryParameter: 'nux=1',
28+
cacheLocation: 'localStorage',
29+
},
30+
$httpProvider
31+
);
32+
33+
```
34+
35+
### Give it a run
36+
* Go to `path-to-azure-spring-boot-starters`, run `mvn clean package`.
37+
* `cd activedirectory\azure-ad-integration-spring-boot-autoconfigure`
38+
* `mvn install`
39+
* `cd activedirectory\azure-ad-integration-spring-boot-autoconfigure-sample`
40+
* `mvn spring-boot:run`
41+
* If running locally, browse to `http://localhost:8080` and click `Login` or `Todo List`, your brower will be redirected to `https://login.microsoftonline.com/` for authentication.
42+
* Upon successful login, `Todo List` will give you a default item and you can perform add, update or delete operation. The backend RESTful API will accept or deny your request based on authenticated user roles.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>org.springframework.boot</groupId>
8+
<artifactId>spring-boot-starter-parent</artifactId>
9+
<version>1.5.4.RELEASE</version>
10+
<relativePath/> <!-- lookup parent from repository -->
11+
</parent>
12+
13+
<groupId>com.microsoft.azure</groupId>
14+
<artifactId>azure-ad-integration-spring-boot-autoconfigure-sample</artifactId>
15+
<version>0.0.1-SNAPSHOT</version>
16+
<packaging>jar</packaging>
17+
18+
<name>Azure AD Spring Security Integration Spring Boot Autoconfigure Sample</name>
19+
<description>Azure AD Spring Security Integration Spring Boot Autoconfigure Sample</description>
20+
<url>https://github.com/Microsoft/azure-spring-boot-starters</url>
21+
22+
<licenses>
23+
<license>
24+
<name>MIT</name>
25+
<url>https://github.com/Microsoft/azure-spring-boot-starters/blob/master/LICENSE</url>
26+
<distribution>repo</distribution>
27+
</license>
28+
</licenses>
29+
30+
<developers>
31+
<developer>
32+
<id>yaweiw</id>
33+
<name>Yawei Wang</name>
34+
<email>yaweiw@microsoft.com</email>
35+
</developer>
36+
</developers>
37+
38+
<scm>
39+
<connection>scm:git:git://github.com/Microsoft/azure-spring-boot-starters.git</connection>
40+
<developerConnection>scm:git:ssh://github.com:Microsoft/azure-spring-boot-starters.git</developerConnection>
41+
<url>https://github.com/Microsoft/azure-spring-boot-starters/tree/master</url>
42+
</scm>
43+
44+
<dependencyManagement>
45+
<dependencies>
46+
<dependency>
47+
<groupId>com.microsoft.azure</groupId>
48+
<artifactId>azure-spring-boot-starter-bom</artifactId>
49+
<version>0.1.5</version>
50+
<type>pom</type>
51+
<scope>import</scope>
52+
</dependency>
53+
</dependencies>
54+
</dependencyManagement>
55+
56+
<dependencies>
57+
<dependency>
58+
<groupId>org.springframework.boot</groupId>
59+
<artifactId>spring-boot-starter-web</artifactId>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.springframework.boot</groupId>
63+
<artifactId>spring-boot-starter</artifactId>
64+
</dependency>
65+
<dependency>
66+
<groupId>org.springframework.security</groupId>
67+
<artifactId>spring-security-config</artifactId>
68+
</dependency>
69+
<dependency>
70+
<groupId>org.springframework.security.oauth</groupId>
71+
<artifactId>spring-security-oauth2</artifactId>
72+
</dependency>
73+
<dependency>
74+
<groupId>com.microsoft.azure</groupId>
75+
<artifactId>azure-ad-integration-spring-boot-autoconfigure</artifactId>
76+
</dependency>
77+
<dependency>
78+
<groupId>com.fasterxml.jackson.core</groupId>
79+
<artifactId>jackson-core</artifactId>
80+
</dependency>
81+
</dependencies>
82+
83+
<properties>
84+
<java.version>1.8</java.version>
85+
<project.rootdir>${project.basedir}/../..</project.rootdir>
86+
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
87+
<maven-checkstyle-plugin.version>2.17</maven-checkstyle-plugin.version>
88+
<findbugs-maven-plugin.version>3.0.0</findbugs-maven-plugin.version>
89+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
90+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
91+
</properties>
92+
93+
<build>
94+
<pluginManagement>
95+
<plugins>
96+
<plugin>
97+
<groupId>org.apache.maven.plugins</groupId>
98+
<artifactId>maven-compiler-plugin</artifactId>
99+
<version>${maven-compiler-plugin.version}</version>
100+
</plugin>
101+
<plugin>
102+
<groupId>org.apache.maven.plugins</groupId>
103+
<artifactId>maven-checkstyle-plugin</artifactId>
104+
<version>${maven-checkstyle-plugin.version}</version>
105+
</plugin>
106+
<plugin>
107+
<groupId>org.codehaus.mojo</groupId>
108+
<artifactId>findbugs-maven-plugin</artifactId>
109+
<version>${findbugs-maven-plugin.version}</version>
110+
</plugin>
111+
</plugins>
112+
</pluginManagement>
113+
<plugins>
114+
<plugin>
115+
<groupId>org.apache.maven.plugins</groupId>
116+
<artifactId>maven-compiler-plugin</artifactId>
117+
<configuration>
118+
<source>${java.version}</source>
119+
<target>${java.version}</target>
120+
</configuration>
121+
</plugin>
122+
<plugin>
123+
<groupId>org.apache.maven.plugins</groupId>
124+
<artifactId>maven-checkstyle-plugin</artifactId>
125+
<executions>
126+
<execution>
127+
<id>validate</id>
128+
<phase>validate</phase>
129+
<configuration>
130+
<configLocation>${project.rootdir}/common/config/checkstyle.xml</configLocation>
131+
<encoding>UTF-8</encoding>
132+
<consoleOutput>true</consoleOutput>
133+
<failsOnError>true</failsOnError>
134+
<failOnViolation>true</failOnViolation>
135+
<includeTestSourceDirectory>true</includeTestSourceDirectory>
136+
</configuration>
137+
<goals>
138+
<goal>check</goal>
139+
</goals>
140+
</execution>
141+
</executions>
142+
<configuration>
143+
<linkXRef>false</linkXRef>
144+
</configuration>
145+
<inherited>true</inherited>
146+
</plugin>
147+
<plugin>
148+
<groupId>org.codehaus.mojo</groupId>
149+
<artifactId>findbugs-maven-plugin</artifactId>
150+
<configuration>
151+
<effort>Max</effort>
152+
<threshold>Low</threshold>
153+
<xmlOutput>true</xmlOutput>
154+
<findbugsXmlOutputDirectory>${project.build.directory}/findbugs
155+
</findbugsXmlOutputDirectory>
156+
<excludeFilterFile>${project.rootdir}/common/config/findbugs-exclude.xml</excludeFilterFile>
157+
</configuration>
158+
<dependencies>
159+
<dependency>
160+
<groupId>org.apache.ant</groupId>
161+
<artifactId>ant</artifactId>
162+
<version>1.9.4</version>
163+
</dependency>
164+
</dependencies>
165+
<executions>
166+
<execution>
167+
<phase>compile</phase>
168+
<goals>
169+
<goal>check</goal>
170+
</goals>
171+
</execution>
172+
</executions>
173+
</plugin>
174+
<plugin>
175+
<groupId>org.springframework.boot</groupId>
176+
<artifactId>spring-boot-maven-plugin</artifactId>
177+
<executions>
178+
<execution>
179+
<goals>
180+
<goal>repackage</goal>
181+
</goals>
182+
</execution>
183+
</executions>
184+
</plugin>
185+
</plugins>
186+
</build>
187+
188+
189+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
package com.microsoft.azure.autoconfigure.aad;
7+
8+
import org.springframework.boot.SpringApplication;
9+
import org.springframework.boot.autoconfigure.SpringBootApplication;
10+
11+
@SpringBootApplication
12+
public class AzureAdIntegrationSpringBootAutoconfigureSampleApplication {
13+
14+
public static void main(String[] args) {
15+
SpringApplication.run(AzureAdIntegrationSpringBootAutoconfigureSampleApplication.class, args);
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
package com.microsoft.azure.autoconfigure.aad.controller;
7+
8+
import com.microsoft.azure.autoconfigure.aad.UserGroup;
9+
import com.microsoft.azure.autoconfigure.aad.UserPrincipal;
10+
import com.microsoft.azure.autoconfigure.aad.model.TodoItem;
11+
import org.springframework.http.HttpStatus;
12+
import org.springframework.http.MediaType;
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.security.access.prepost.PreAuthorize;
15+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
16+
import org.springframework.web.bind.annotation.*;
17+
18+
import java.util.*;
19+
import java.util.stream.Collectors;
20+
21+
@RestController
22+
public class TodolistController {
23+
private final List<TodoItem> todoList = new ArrayList<TodoItem>();
24+
25+
public TodolistController() {
26+
todoList.add(0, new TodoItem(2398, "anything", "whoever"));
27+
}
28+
29+
@RequestMapping("/home")
30+
public Map<String, Object> home() {
31+
final Map<String, Object> model = new HashMap<String, Object>();
32+
model.put("id", UUID.randomUUID().toString());
33+
model.put("content", "home");
34+
return model;
35+
}
36+
37+
/**
38+
* HTTP GET
39+
*/
40+
@RequestMapping(value = "/api/todolist/{index}",
41+
method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
42+
public ResponseEntity<?> getTodoItem(@PathVariable("index") int index) {
43+
if (index > todoList.size() - 1) {
44+
return new ResponseEntity<Object>(new TodoItem(-1, "index out of range", null), HttpStatus.NOT_FOUND);
45+
}
46+
return new ResponseEntity<TodoItem>(todoList.get(index), HttpStatus.OK);
47+
}
48+
49+
/**
50+
* HTTP GET ALL
51+
*/
52+
@RequestMapping(value = "/api/todolist", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
53+
public ResponseEntity<List<TodoItem>> getAllTodoItems() {
54+
return new ResponseEntity<List<TodoItem>>(todoList, HttpStatus.OK);
55+
}
56+
57+
@PreAuthorize("hasRole('ROLE_group1')")
58+
@RequestMapping(value = "/api/todolist", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
59+
public ResponseEntity<String> addNewTodoItem(@RequestBody TodoItem item) {
60+
item.setID(todoList.size() + 1);
61+
todoList.add(todoList.size(), item);
62+
return new ResponseEntity<String>("Entity created", HttpStatus.CREATED);
63+
}
64+
65+
/**
66+
* HTTP PUT
67+
*/
68+
@PreAuthorize("hasRole('ROLE_group1')")
69+
@RequestMapping(value = "/api/todolist", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
70+
public ResponseEntity<String> updateTodoItem(@RequestBody TodoItem item) {
71+
final List<TodoItem> find =
72+
todoList.stream().filter(i -> i.getID() == item.getID()).collect(Collectors.toList());
73+
if (!find.isEmpty()) {
74+
todoList.set(todoList.indexOf(find.get(0)), item);
75+
return new ResponseEntity<String>("Entity is updated", HttpStatus.OK);
76+
}
77+
return new ResponseEntity<String>("Entity not found", HttpStatus.OK);
78+
}
79+
80+
/**
81+
* HTTP DELETE
82+
*/
83+
@RequestMapping(value = "/api/todolist/{id}", method = RequestMethod.DELETE)
84+
public ResponseEntity<String> deleteTodoItem(@PathVariable("id") int id,
85+
PreAuthenticatedAuthenticationToken authToken) {
86+
final UserPrincipal current = (UserPrincipal) authToken.getPrincipal();
87+
88+
if (current.isMemberOf(
89+
new UserGroup("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "group1"))) {
90+
final List<TodoItem> find = todoList.stream().filter(i -> i.getID() == id).collect(Collectors.toList());
91+
if (!find.isEmpty()) {
92+
todoList.remove(todoList.indexOf(find.get(0)));
93+
return new ResponseEntity<String>("OK", HttpStatus.OK);
94+
}
95+
return new ResponseEntity<String>("Entity not found", HttpStatus.OK);
96+
} else {
97+
return new ResponseEntity<String>("Access is denied", HttpStatus.OK);
98+
}
99+
100+
}
101+
}

0 commit comments

Comments
 (0)