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 all commits
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 @@ -3,7 +3,7 @@

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

import com.azure.spring.cloud.autoconfigure.aad.implementation.webapi.AadJwtBearerTokenAuthenticationConverter;
import com.azure.spring.cloud.autoconfigure.aad.AadJwtBearerTokenAuthenticationConverter;
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;
Expand Down
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 @@ -35,6 +35,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- This application is still a Web MVC application. Including the spring-boot-starter-webflux only to use the WebClient. -->
<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,102 @@
// 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.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 AadConditionalAccessFilter extends OncePerRequestFilter {

/**
* Bearer prefix
*/
private static final String BEARER_PREFIX = "Bearer "; // Whitespace at the end is necessary.

/**
* Conditional access policy claims
*/
private static final String CONDITIONAL_ACCESS_POLICY_CLAIMS = "CONDITIONAL_ACCESS_POLICY_CLAIMS";

/**
* 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(CONDITIONAL_ACCESS_POLICY_CLAIMS)) {
request.getSession().setAttribute(CONDITIONAL_ACCESS_POLICY_CLAIMS,
authParameters.get(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(BEARER_PREFIX))
.map(str -> str.substring(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,15 @@ 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.
* {@inheritDoc}
* @return the conditional access filter
*/
@Override
protected Filter conditionalAccessFilter() {
return new AadConditionalAccessFilter();
}
}
}
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,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- This application is still a Web MVC application. Including the spring-boot-starter-webflux only to use the WebClient. -->
<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
@@ -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
```

### 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,11 @@
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<!-- This application is still a Web MVC application. Including the spring-boot-starter-webflux only to use the WebClient. -->
<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,102 @@
// 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.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 AadConditionalAccessFilter extends OncePerRequestFilter {

/**
* Bearer prefix
*/
private static final String BEARER_PREFIX = "Bearer "; // Whitespace at the end is necessary.

/**
* Conditional access policy claims
*/
private static final String CONDITIONAL_ACCESS_POLICY_CLAIMS = "CONDITIONAL_ACCESS_POLICY_CLAIMS";

/**
* 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(CONDITIONAL_ACCESS_POLICY_CLAIMS)) {
request.getSession().setAttribute(CONDITIONAL_ACCESS_POLICY_CLAIMS,
authParameters.get(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(BEARER_PREFIX))
.map(str -> str.substring(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]));
}
}
Loading