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

Support conditional access policy in obo flow. #18354

Merged
merged 35 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b18f250
fix failureHandle not error info
Dec 25, 2020
0254e88
fix failureHandle not error info
Dec 31, 2020
b2b72f7
Merge branch 'master' of git://github.com/Azure/azure-sdk-for-java in…
Jan 11, 2021
72654ff
Merge branch 'master' of git://github.com/Azure/azure-sdk-for-java in…
Jan 11, 2021
230ca9d
fix failureHandle not error info
Jan 11, 2021
726a95e
code format for checkStyle
Jan 11, 2021
33ac445
code format for checkStyle
Jan 12, 2021
76b9b15
add webClient handle conditional access policy.
Jan 12, 2021
24a7bfd
add dependency to azure-spring-boot-starter-active-directory-pom
Jan 13, 2021
6f3250c
add webflux to external_dependency.
Jan 13, 2021
6942393
Modify note.
Jan 13, 2021
df4f6e2
add svg for ConditionalAccessException
Jan 19, 2021
34bd03c
add notes for ConditionalAccess.
Jan 19, 2021
0d3ec7b
update sample for ConditionalAccess
Jan 19, 2021
502384f
resolve conversation
Jan 21, 2021
8a9e6bc
resolve conversation
Jan 21, 2021
14ffa21
update webapp sample and webclient filter
Jan 25, 2021
96a4e27
add filter for conditionalAccess.
Feb 2, 2021
7f1945c
resolve conflicts.
Feb 2, 2021
fc4da85
Merge branch 'master' of git://github.com/Azure/azure-sdk-for-java in…
Feb 2, 2021
162d5ac
update ConditionalAccessException.java
Feb 2, 2021
f103bad
fix code style.
Feb 3, 2021
47daa01
when re-authentication, update all clients.
Feb 4, 2021
2396408
remove ConditionalAccessException.java
Feb 9, 2021
809661d
Merge branch 'master' of git://github.com/Azure/azure-sdk-for-java in…
Feb 10, 2021
bd0c5cd
resolve conflicts.
Feb 10, 2021
9da07e2
Solve pipeline problems.
Feb 10, 2021
6ff1f95
Solve pipeline problems.
Feb 18, 2021
707c22d
resolve conversation.
Feb 20, 2021
6aade06
Merge branch 'master' of git://github.com/Azure/azure-sdk-for-java in…
Feb 20, 2021
3b7bf5a
Merge branch 'master' of git://github.com/Azure/azure-sdk-for-java in…
Feb 22, 2021
d2259e8
add web-flux dependency to aad stater
Feb 22, 2021
ab6be45
add web-flux dependency to aad stater
Feb 22, 2021
bfb52b5
resolve conversation.
Feb 22, 2021
713d0db
Merge branch 'master' of git://github.com/Azure/azure-sdk-for-java in…
Feb 25, 2021
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
15 changes: 12 additions & 3 deletions sdk/spring/azure-spring-boot-starter-active-directory/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
<artifactId>spring-web</artifactId>
<version>5.2.10.RELEASE</version> <!-- {x-version-update;org.springframework:spring-web;external_dependency} -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.2.10.RELEASE</version> <!-- {x-version-update;org.springframework:spring-webflux;external_dependency} -->
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
Expand Down Expand Up @@ -96,12 +101,16 @@
<include>com.microsoft.azure:msal4j:[1.8.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.nimbusds:nimbus-jose-jwt:[8.19]</include> <!-- {x-include-update;com.nimbusds:nimbus-jose-jwt;external_dependency} -->
<include>io.projectreactor.netty:reactor-netty:[0.9.15.RELEASE]</include> <!-- {x-include-update;io.projectreactor.netty:reactor-netty;external_dependency} -->
<include>org.springframework.boot:spring-boot-starter-validation:[2.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.boot:spring-boot-starter-validation;external_dependency} -->
<include>org.springframework.boot:spring-boot-starter-webflux:[2.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.boot:spring-boot-starter-webflux;external_dependency} -->
<include>org.springframework.boot:spring-boot-starter-validation:[2.3.5.RELEASE]
</include> <!-- {x-include-update;org.springframework.boot:spring-boot-starter-validation;external_dependency} -->
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
<include>org.springframework.boot:spring-boot-starter-webflux:[2.3.5.RELEASE]
</include> <!-- {x-include-update;org.springframework.boot:spring-boot-starter-webflux;external_dependency} -->
<include>org.springframework.boot:spring-boot-starter:[2.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.boot:spring-boot-starter;external_dependency} -->
<include>org.springframework.security:spring-security-config:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-config;external_dependency} -->
<include>org.springframework.security:spring-security-config:[5.3.5.RELEASE]
</include> <!-- {x-include-update;org.springframework.security:spring-security-config;external_dependency} -->
<include>org.springframework.security:spring-security-core:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-core;external_dependency} -->
<include>org.springframework.security:spring-security-web:[5.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.security:spring-security-web;external_dependency} -->
<include>org.springframework:spring-webflux:[5.2.10.RELEASE]</include> <!-- {x-include-update;org.springframework:spring-webflux;external_dependency} -->
<include>org.springframework:spring-web:[5.2.10.RELEASE]</include> <!-- {x-include-update;org.springframework:spring-web;external_dependency} -->
</includes>
</bannedDependencies>
Expand Down
7 changes: 7 additions & 0 deletions sdk/spring/azure-spring-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@
<artifactId>spring-core</artifactId>
<version>5.2.10.RELEASE</version> <!-- {x-version-update;org.springframework:spring-core;external_dependency} -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
<version>5.2.10.RELEASE</version> <!-- {x-version-update;org.springframework:spring-webflux;external_dependency} -->
<optional>true</optional>
</dependency>
</dependencies>

<build>
Expand All @@ -296,6 +302,7 @@
<include>org.springframework:spring-core:[5.2.10.RELEASE]</include> <!-- {x-include-update;org.springframework:spring-core;external_dependency} -->
<include>org.springframework:spring-web:[5.2.10.RELEASE]</include> <!-- {x-include-update;org.springframework:spring-web;external_dependency} -->
<include>org.springframework:spring-jms:[5.2.10.RELEASE]</include> <!-- {x-include-update;org.springframework:spring-jms;external_dependency} -->
<include>org.springframework:spring-webflux:[5.2.10.RELEASE]</include> <!-- {x-include-update;org.springframework:spring-webflux;external_dependency} -->
<include>org.springframework.boot:spring-boot-actuator-autoconfigure:[2.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.boot:spring-boot-actuator-autoconfigure;external_dependency} -->
<include>org.springframework.boot:spring-boot-autoconfigure-processor:[2.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.boot:spring-boot-autoconfigure-processor;external_dependency} -->
<include>org.springframework.boot:spring-boot-autoconfigure:[2.3.5.RELEASE]</include> <!-- {x-include-update;org.springframework.boot:spring-boot-autoconfigure;external_dependency} -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,37 @@

package com.azure.spring.aad.webapi;

import com.azure.spring.aad.webapp.ConditionalAccessException;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IClientSecret;
import com.microsoft.aad.msal4j.MsalInteractionRequiredException;
import com.microsoft.aad.msal4j.OnBehalfOfParameters;
import com.microsoft.aad.msal4j.UserAssertion;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Date;
import java.util.Optional;

/**
* <p>
Expand Down Expand Up @@ -87,6 +96,29 @@ public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String registra
request.setAttribute(oboAuthorizedClientAttributeName, (T) oAuth2AuthorizedClient);
return (T) oAuth2AuthorizedClient;
} catch (Throwable throwable) {
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
// Handle conditional access policy for obo flow.
String claims = Optional.of(throwable)
.map(Throwable::getCause)
.filter(e -> e instanceof MsalInteractionRequiredException)
.map(e -> (MsalInteractionRequiredException) e)
.map(MsalInteractionRequiredException::claims)
.orElse(null);

if (claims != null) {
ServletRequestAttributes attr =
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletResponse response = attr.getResponse();
assert response != null;
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
response.setStatus(HttpStatus.FORBIDDEN.value());
try {
ServletOutputStream outputStream = response.getOutputStream();
String result = ConditionalAccessException.claimsToHttpBody(claims);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
} catch (IOException e) {
LOGGER.error("An exception occurred while operating the responseOutputStream.", e);
}
}
LOGGER.error("Failed to load authorized client.", throwable);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,4 @@ public final String getSubError() {
public final String getClaims() {
return claims;
}


@Override
public String toString() {

return "AADAuthenticationException{"
+ ", error_codes='" + errorCodes + '\''
+ ", timestamp='" + timestamp + '\''
+ ", trace_id='" + traceId + '\''
+ ", correlation_id='" + correlationId + '\''
+ ", suberror='" + subError + '\''
+ ", claims='" + claims + '\''
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
package com.azure.spring.aad.webapp;

import com.azure.spring.aad.AADAuthorizationServerEndpoints;
import com.azure.spring.aad.webapi.AADOAuth2OboAuthorizedClientRepository;
import com.azure.spring.autoconfigure.aad.AADAuthenticationProperties;
import com.azure.spring.autoconfigure.aad.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
Expand All @@ -17,14 +21,22 @@
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import reactor.core.publisher.Mono;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
Expand All @@ -48,6 +60,8 @@
@EnableConfigurationProperties(AADAuthenticationProperties.class)
public class AADWebAppConfiguration {

private static final Logger LOGGER = LoggerFactory.getLogger(AADOAuth2OboAuthorizedClientRepository.class);
chenrujun marked this conversation as resolved.
Show resolved Hide resolved

@Autowired
private AADAuthenticationProperties properties;

Expand Down Expand Up @@ -121,6 +135,30 @@ private Set<String> accessTokenScopes() {
return result;
}

/**
* Handle conditional access error in obo flow.
*/
@ControllerAdvice
public static class GlobalExceptionAdvice {
@ExceptionHandler(ConditionalAccessException.class)
public void handleUserNotFound(HttpServletRequest request,
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
HttpServletResponse response, Exception exception) {
Optional.of(exception)
.map(e -> (ConditionalAccessException) e)
.ifPresent(aadConditionalAccessException -> {
response.setStatus(302);
SecurityContextHolder.clearContext();
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
request.getSession().setAttribute(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS,
aadConditionalAccessException.getClaims());
try {
response.sendRedirect(request.getRequestURL().toString());
} catch (IOException e) {
LOGGER.error("An exception occurred while redirecting url.", e);
}
});
}
}

private Set<String> openidScopes() {
Set<String> result = new HashSet<>();
result.add("openid");
Expand Down Expand Up @@ -196,4 +234,16 @@ protected void configure(HttpSecurity http) throws Exception {
}
}


public static ExchangeFilterFunction conditionalAccessExchangeFilterFunction() {
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
return ExchangeFilterFunction.ofResponseProcessor(clientResponse ->
clientResponse.bodyToMono(String.class)
.flatMap(httpBody -> {
if (ConditionalAccessException.isConditionAccessException(httpBody)) {
return Mono.error(ConditionalAccessException.fromHttpBody(httpBody));
}
return Mono.just(clientResponse);
})
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
Expand All @@ -19,15 +20,16 @@
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.Arrays;

/**
* Abstract configuration class, used to make AzureClientRegistrationRepository
* and AuthzCodeGrantRequestEntityConverter take effect.
* Abstract configuration class, used to make AzureClientRegistrationRepository and AuthzCodeGrantRequestEntityConverter
* take effect.
*/
public abstract class AADWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

Expand All @@ -52,6 +54,8 @@ protected void configure(HttpSecurity http) throws Exception {
.oidcUserService(oidcUserService)
.and()
.and()
.apply(new AADHttpConfigurer())
.and()
.logout()
.logoutSuccessHandler(oidcLogoutSuccessHandler())
.and();
Expand Down Expand Up @@ -87,4 +91,16 @@ protected OAuth2AuthorizationRequestResolver requestResolver() {
protected AuthenticationFailureHandler failureHandler() {
return new AADAuthenticationFailureHandler();
}

/**
* Fix the default error info not displayed when the setting failureHandler in configure
*/
private static final class AADHttpConfigurer extends AbstractHttpConfigurer<AADHttpConfigurer, HttpSecurity> {
@Override
public void init(HttpSecurity http) {
DefaultLoginPageGeneratingFilter filter =
http.getSharedObject(DefaultLoginPageGeneratingFilter.class);
filter.setFailureUrl("/login?error");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.aad.webapp;

import com.azure.spring.autoconfigure.aad.Constants;

/**
* handle ConditionalAccess from obo flow.
*/
public final class ConditionalAccessException extends RuntimeException {
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
private final String claims;

protected ConditionalAccessException(String claims) {
this.claims = claims;
}

public String getClaims() {
return claims;
}

public static ConditionalAccessException fromHttpBody(String httpBody) {
return new ConditionalAccessException(httpBodyToClaims(httpBody));
}

public static String httpBodyToClaims(String httpBody) {
return httpBody.split(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS)[1];
}

public static String claimsToHttpBody(String claims) {
return Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS + claims + Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS;
}

public static boolean isConditionAccessException(String httpBody) {
return httpBody.startsWith(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS);
}
}