Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update aad samples #223

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,18 @@
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</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-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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</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-web</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>

<!-- spring boot starter dependencies. -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand All @@ -36,6 +35,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>

<!-- spring boot starter dependencies. -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.sample.aad.security;

import com.azure.spring.cloud.autoconfigure.aad.implementation.constants.Constants;
import com.azure.spring.cloud.autoconfigure.aad.implementation.oauth2.AadClientRegistrationRepository;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.reactive.function.client.WebClientResponseException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Handle the {@link WebClientResponseException} in On-Behalf-Of flow.
*
* <p>
* When the resource-server needs re-acquire token(The request requires higher privileges than provided by the access
* token in On-Behalf-Of flow.), it can send a 403 with information in the WWW-Authenticate header to web client ,web
* client will throw {@link WebClientResponseException}, web-application can handle this exception to challenge the
* user.
*
* @see OncePerRequestFilter
*/
public class AadHandleConditionalAccessFilter extends OncePerRequestFilter {

/**
* Do filter.
*
* @param request the HttpServletRequest
* @param response the HttpServletResponse
* @param filterChain the FilterChain
* @throws IOException if an I/O related error has occurred during the processing
* @throws ServletException if an exception has occurred that interferes with the
* filterChain's normal operation
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
// Handle conditional access policy, step 2.
try {
filterChain.doFilter(request, response);
} catch (Exception exception) {
Map<String, String> authParameters =
Optional.of(exception)
.map(Throwable::getCause)
.filter(e -> e instanceof WebClientResponseException)
.map(e -> (WebClientResponseException) e)
.map(WebClientResponseException::getHeaders)
.map(httpHeaders -> httpHeaders.get(HttpHeaders.WWW_AUTHENTICATE))
.map(list -> list.get(0))
.map(this::parseAuthParameters)
.orElse(null);
if (authParameters != null && authParameters.containsKey(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS)) {
request.getSession().setAttribute(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS,
authParameters.get(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS));
// OAuth2AuthorizationRequestRedirectFilter will catch this exception to re-authorize.
throw new ClientAuthorizationRequiredException(AadClientRegistrationRepository.AZURE_CLIENT_REGISTRATION_ID);
}
throw exception;
}
}

/**
* Get claims filed form the header to re-authorize.
*
* @param wwwAuthenticateHeader httpHeader
* @return authParametersMap
*/
private Map<String, String> parseAuthParameters(String wwwAuthenticateHeader) {
return Stream.of(wwwAuthenticateHeader)
.filter(StringUtils::hasText)
.filter(header -> header.startsWith(Constants.BEARER_PREFIX))
.map(str -> str.substring(Constants.BEARER_PREFIX.length() + 1, str.length() - 1))
.map(str -> str.split(", "))
.flatMap(Stream::of)
.map(parameter -> parameter.split("="))
.filter(parameter -> parameter.length > 1)
.collect(Collectors.toMap(
parameters -> parameters[0],
parameters -> parameters[1]));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import javax.servlet.Filter;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AadWebApplicationAndResourceServerConfig {
Expand All @@ -37,5 +39,14 @@ protected void configure(HttpSecurity http) throws Exception {
.anyRequest().authenticated();
// @formatter:on
}

/**
* This method is only used for AAD conditional access support and can be removed if this feature is not used.
* @return
*/
@Override
protected Filter handleConditionalAccessFilter() {
return new AadHandleConditionalAccessFilter();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# OAuth 2.0 Sample for Azure AD Spring Boot Starter client library for Java

## Key concepts
This sample illustrates how to use `azure-spring-boot-starter-active-directory` package to work with OAuth 2.0 and OpenID Connect protocols on Auzre. This sample will use Microsoft Graph API to retrieve user information.
This sample illustrates how to use `azure-spring-boot-starter-active-directory` package to work with OAuth 2.0 and OpenID Connect protocols on Azure. This sample will use Microsoft Graph API to retrieve user information.

## Getting started

Expand Down Expand Up @@ -108,6 +108,9 @@ spring:
```shell
cd azure-spring-boot-samples/aad/azure-spring-boot-starter-active-directory/aad-web-application
mvn spring-boot:run

# Or use the below command to the AAD conditional access filter.
mvn spring-boot:run -Dspring-boot.run.profiles=default,conditional-access

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to use a new sample to demonstrate how to enable conditional access feature.
Combine many features in one sample may make customer confused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's enough because this function is used less, there is no need to spend too much time introducing it.

```

### Check the authentication and authorization
Expand Down Expand Up @@ -147,4 +150,4 @@ In Azure portal, app registration manifest page, configure `oauth2AllowImplicitF
[this issue]: https://github.com/MicrosoftDocs/azure-docs/issues/8121#issuecomment-387090099
[Resource Server]: https://github.com/Azure-Samples/azure-spring-boot-samples/blob/main/aad/azure-spring-boot-starter-active-directory/aad-resource-server
[Resource Server Obo]: https://github.com/Azure-Samples/azure-spring-boot-samples/blob/main/aad/azure-spring-boot-starter-active-directory/aad-resource-server-obo
[config for resource server obo]: https://github.com/Azure-Samples/azure-spring-boot-samples/blob/main/aad/azure-spring-boot-starter-active-directory/aad-resource-server-obo#configure-your-middle-tier-web-api-a
[config for resource server obo]: https://github.com/Azure-Samples/azure-spring-boot-samples/blob/main/aad/azure-spring-boot-starter-active-directory/aad-resource-server-obo#configure-your-middle-tier-web-api-a
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
Expand All @@ -44,6 +43,10 @@
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.sample.aad.security;

import com.azure.spring.cloud.autoconfigure.aad.implementation.constants.Constants;
import com.azure.spring.cloud.autoconfigure.aad.implementation.oauth2.AadClientRegistrationRepository;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.reactive.function.client.WebClientResponseException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Handle the {@link WebClientResponseException} in On-Behalf-Of flow.
*
* <p>
* When the resource-server needs re-acquire token(The request requires higher privileges than provided by the access
* token in On-Behalf-Of flow.), it can send a 403 with information in the WWW-Authenticate header to web client ,web
* client will throw {@link WebClientResponseException}, web-application can handle this exception to challenge the
* user.
*
* @see OncePerRequestFilter
*/
public class AadHandleConditionalAccessFilter extends OncePerRequestFilter {

/**
* Do filter.
*
* @param request the HttpServletRequest
* @param response the HttpServletResponse
* @param filterChain the FilterChain
* @throws IOException if an I/O related error has occurred during the processing
* @throws ServletException if an exception has occurred that interferes with the
* filterChain's normal operation
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
// Handle conditional access policy, step 2.
try {
filterChain.doFilter(request, response);
} catch (Exception exception) {
Map<String, String> authParameters =
Optional.of(exception)
.map(Throwable::getCause)
.filter(e -> e instanceof WebClientResponseException)
.map(e -> (WebClientResponseException) e)
.map(WebClientResponseException::getHeaders)
.map(httpHeaders -> httpHeaders.get(HttpHeaders.WWW_AUTHENTICATE))
.map(list -> list.get(0))
.map(this::parseAuthParameters)
.orElse(null);
if (authParameters != null && authParameters.containsKey(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS)) {
request.getSession().setAttribute(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS,
authParameters.get(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS));
// OAuth2AuthorizationRequestRedirectFilter will catch this exception to re-authorize.
throw new ClientAuthorizationRequiredException(AadClientRegistrationRepository.AZURE_CLIENT_REGISTRATION_ID);
}
throw exception;
}
}

/**
* Get claims filed form the header to re-authorize.
*
* @param wwwAuthenticateHeader httpHeader
* @return authParametersMap
*/
private Map<String, String> parseAuthParameters(String wwwAuthenticateHeader) {
return Stream.of(wwwAuthenticateHeader)
.filter(StringUtils::hasText)
.filter(header -> header.startsWith(Constants.BEARER_PREFIX))
.map(str -> str.substring(Constants.BEARER_PREFIX.length() + 1, str.length() - 1))
.map(str -> str.split(", "))
.flatMap(Stream::of)
.map(parameter -> parameter.split("="))
.filter(parameter -> parameter.length > 1)
.collect(Collectors.toMap(
parameters -> parameters[0],
parameters -> parameters[1]));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.sample.aad.security;

import com.azure.spring.cloud.autoconfigure.aad.AadWebSecurityConfigurerAdapter;
import org.springframework.context.annotation.Profile;
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 javax.servlet.Filter;

@Profile("conditional-access")
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AadWebApplicationConfig extends AadWebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
// @formatter:off
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated();
// @formatter:on
}

@Override
protected Filter handleConditionalAccessFilter() {
return new AadHandleConditionalAccessFilter();
}
}