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

Commit

Permalink
aad filter sample (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaweiw authored and ZhijunZhao committed Aug 18, 2017
1 parent 3d92eb6 commit d3a887f
Show file tree
Hide file tree
Showing 37 changed files with 2,348 additions and 166 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Overview
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.

### Get started
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.

#### Application.properties
You need to have an registered app in your Azure AD tenant and create a security key.
Put Application ID and Key in `clientId` and `clientSecret` respectively e.g.
`azure.activedirectory.clientId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
`azure.activedirectory.clientSecret=ABCDEFGHIJKLMNOOPQRSTUVWXYZABCDEFGHIJKLMNOPQ`
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.
`azure.activedirectory.ActiveDirectoryGroups=group1,group2`
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".

##### 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.


#### Angular JS
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.
```
adalProvider.init(
{
instance: 'https://login.microsoftonline.com/',
tenant: 'your-aad-tenant',
clientId: 'your-application-id',
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage',
},
$httpProvider
);
```

### Give it a run
* Go to `path-to-azure-spring-boot-starters`, run `mvn clean package`.
* `cd activedirectory\azure-ad-integration-spring-boot-autoconfigure`
* `mvn install`
* `cd activedirectory\azure-ad-integration-spring-boot-autoconfigure-sample`
* `mvn spring-boot:run`
* 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.
* 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 number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.microsoft.azure</groupId>
<artifactId>azure-ad-integration-spring-boot-autoconfigure-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Azure AD Spring Security Integration Spring Boot Autoconfigure Sample</name>
<description>Azure AD Spring Security Integration Spring Boot Autoconfigure Sample</description>
<url>https://github.com/Microsoft/azure-spring-boot-starters</url>

<licenses>
<license>
<name>MIT</name>
<url>https://github.com/Microsoft/azure-spring-boot-starters/blob/master/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<id>yaweiw</id>
<name>Yawei Wang</name>
<email>[email protected]</email>
</developer>
</developers>

<scm>
<connection>scm:git:git://github.com/Microsoft/azure-spring-boot-starters.git</connection>
<developerConnection>scm:git:ssh://github.com:Microsoft/azure-spring-boot-starters.git</developerConnection>
<url>https://github.com/Microsoft/azure-spring-boot-starters/tree/master</url>
</scm>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-spring-boot-starter-bom</artifactId>
<version>0.1.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-ad-integration-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
</dependencies>

<properties>
<java.version>1.8</java.version>
<project.rootdir>${project.basedir}/../..</project.rootdir>
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
<maven-checkstyle-plugin.version>2.17</maven-checkstyle-plugin.version>
<findbugs-maven-plugin.version>3.0.0</findbugs-maven-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven-checkstyle-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>${findbugs-maven-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<configuration>
<configLocation>${project.rootdir}/common/config/checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<failOnViolation>true</failOnViolation>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<linkXRef>false</linkXRef>
</configuration>
<inherited>true</inherited>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<xmlOutput>true</xmlOutput>
<findbugsXmlOutputDirectory>${project.build.directory}/findbugs
</findbugsXmlOutputDirectory>
<excludeFilterFile>${project.rootdir}/common/config/findbugs-exclude.xml</excludeFilterFile>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>


</project>
Original file line number Diff line number Diff line change
@@ -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 com.microsoft.azure.autoconfigure.aad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AzureAdIntegrationSpringBootAutoconfigureSampleApplication {

public static void main(String[] args) {
SpringApplication.run(AzureAdIntegrationSpringBootAutoconfigureSampleApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* 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.autoconfigure.aad.controller;

import com.microsoft.azure.autoconfigure.aad.UserGroup;
import com.microsoft.azure.autoconfigure.aad.UserPrincipal;
import com.microsoft.azure.autoconfigure.aad.model.TodoItem;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.web.bind.annotation.*;

import java.util.*;
import java.util.stream.Collectors;

@RestController
public class TodolistController {
private final List<TodoItem> todoList = new ArrayList<TodoItem>();

public TodolistController() {
todoList.add(0, new TodoItem(2398, "anything", "whoever"));
}

@RequestMapping("/home")
public Map<String, Object> home() {
final Map<String, Object> model = new HashMap<String, Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "home");
return model;
}

/**
* HTTP GET
*/
@RequestMapping(value = "/api/todolist/{index}",
method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<?> getTodoItem(@PathVariable("index") int index) {
if (index > todoList.size() - 1) {
return new ResponseEntity<Object>(new TodoItem(-1, "index out of range", null), HttpStatus.NOT_FOUND);
}
return new ResponseEntity<TodoItem>(todoList.get(index), HttpStatus.OK);
}

/**
* HTTP GET ALL
*/
@RequestMapping(value = "/api/todolist", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<List<TodoItem>> getAllTodoItems() {
return new ResponseEntity<List<TodoItem>>(todoList, HttpStatus.OK);
}

@PreAuthorize("hasRole('ROLE_group1')")
@RequestMapping(value = "/api/todolist", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> addNewTodoItem(@RequestBody TodoItem item) {
item.setID(todoList.size() + 1);
todoList.add(todoList.size(), item);
return new ResponseEntity<String>("Entity created", HttpStatus.CREATED);
}

/**
* HTTP PUT
*/
@PreAuthorize("hasRole('ROLE_group1')")
@RequestMapping(value = "/api/todolist", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> updateTodoItem(@RequestBody TodoItem item) {
final List<TodoItem> find =
todoList.stream().filter(i -> i.getID() == item.getID()).collect(Collectors.toList());
if (!find.isEmpty()) {
todoList.set(todoList.indexOf(find.get(0)), item);
return new ResponseEntity<String>("Entity is updated", HttpStatus.OK);
}
return new ResponseEntity<String>("Entity not found", HttpStatus.OK);
}

/**
* HTTP DELETE
*/
@RequestMapping(value = "/api/todolist/{id}", method = RequestMethod.DELETE)
public ResponseEntity<String> deleteTodoItem(@PathVariable("id") int id,
PreAuthenticatedAuthenticationToken authToken) {
final UserPrincipal current = (UserPrincipal) authToken.getPrincipal();

if (current.isMemberOf(
new UserGroup("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "group1"))) {
final List<TodoItem> find = todoList.stream().filter(i -> i.getID() == id).collect(Collectors.toList());
if (!find.isEmpty()) {
todoList.remove(todoList.indexOf(find.get(0)));
return new ResponseEntity<String>("OK", HttpStatus.OK);
}
return new ResponseEntity<String>("Entity not found", HttpStatus.OK);
} else {
return new ResponseEntity<String>("Access is denied", HttpStatus.OK);
}

}
}
Loading

0 comments on commit d3a887f

Please sign in to comment.