Skip to content

Commit

Permalink
Update aad samples (#223)
Browse files Browse the repository at this point in the history
* Update aad sample due to conditional access removal
* Make the aad reference classes not in implementation package
  • Loading branch information
moarychan authored Mar 21, 2022
1 parent 7a0487a commit 298c724
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 12 deletions.
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

0 comments on commit 298c724

Please sign in to comment.