-
Notifications
You must be signed in to change notification settings - Fork 0
JWT Client Authentication support design for Spring Cloud Azure AD
- The client authentication method
PRIVATE_KEY_JWT
is not supported
The Azure AD supports the below client authentication methods, see the Client Authentication, rfc7523:
- CLIENT_SECRET_BASIC
- CLIENT_SECRET_POST
- PRIVATE_KEY_JWT
Spring Security OAuth2 library already supports these client authentication methods, but the method 'PRIVATE_KEY_JWT' is not yet supported by Spring Cloud Azure Starter Active Directory. Currently, users must use the client id and client secret to finish the OAuth2 client authentication.
- The Authorization grant type
JWT_BEARER
is not supported
Spring Cloud Azure Starter Active Directory already supports similar functionality to grant type JWT_BEARER
via ON_BEHALF_OF
, it does not follow the Spring Security OAuth2 pattern, so it's necessary to change to use the authorization grant type JWT_BEARER
to support both grant types, but in order to better solve the first problem, this authorization type must also be supported together.
- Support JWT Client Authentication in Azure AD starters.
- Support
JWT_BEARER
Authorization grant type
Users can configure the client authentication method private_key_jwt
to enable this feature, and the certificate path and certificate password are also required.
The below properties should be added together:
- Add client-authentication-method property to configure client authentication method
It will be set to client_secret_basic
by default.
Users can only set the client authentication method for the default OAuth2 client azure.
spring.cloud.azure.active-directory.authorization-clients.azure.client-authentication-method=private_key_jwt
Users can set the client authentication method for the other OAuth2 clients.
spring.cloud.azure.active-directory.authorization-clients.xxx.client-authentication-method=private_key_jwt
- Add client-certificate-path and client-certificate-password properties to configure local certificate info.
User can use the global properties:
spring.cloud.azure.credential.client-certificate-path=/etc/xxx
spring.cloud.azure.credential.client-certificate-password=xxx
User can use the azure active-directory service properties:
spring.cloud.azure.active-directory.credential.client-certificate-path=/etc/xxx
spring.cloud.azure.active-directory.credential.client-certificate-password=xxx
NOTE:
Only files with the '.pfx' or '.p12' extension are supported., the certificate requires a password.
The old usage of the client secret:
spring:
cloud:
azure:
# Properties like spring.cloud.azure.credential.client-id are global properties.
# Properties like spring.cloud.azure.active-directory.credential.client-id are AAD properties.
# If AAD properties is not configured, global properties will be used.
# credential:
# client-id:
# client-secret:
# profile:
# tenant-id:
active-directory:
enabled: true
credential:
client-id: ${AZURE_CLIENT_ID}
client-secret: ${AZURE_CLIENT_SECRET}
profile:
tenant-id: ${AZURE_TENANT_ID}
user-group:
allowed-group-names: group1,group2
allowed-group-ids: <group1-id>,<group2-id>
post-logout-redirect-uri: http://localhost:8080
authorization-clients:
arm:
on-demand: true
scopes: https://management.core.windows.net/user_impersonation
graph:
scopes:
- https://graph.microsoft.com/User.Read
- https://graph.microsoft.com/Directory.Read.All
webapiA:
scopes:
- ${WEB_API_A_APP_ID_URL}/Obo.WebApiA.ExampleScope
webapiB:
scopes:
- ${WEB_API_B_APP_ID_URL}/.default
authorization-grant-type: client_credentials
The new usage of the client certificate:
# WebapiA is an optional client, we can access obo resource servers.
# We can also access a custom server according to the webapiA client.
spring:
cloud:
azure:
# Properties like spring.cloud.azure.credential.client-id are global properties.
# Properties like spring.cloud.azure.active-directory.credential.client-id are AAD properties.
# If AAD properties is not configured, global properties will be used.
# credential:
# client-id:
# client-certificate-path: ${AZURE_CERTIFICATE_PATH}
# client-certificate-password: ${AZURE_CERTIFICATE_PASSWORD}
# profile:
# tenant-id:
active-directory:
enabled: true
credential:
client-id: ${AZURE_CLIENT_ID}
client-certificate-path: ${AZURE_CERTIFICATE_PATH}
client-certificate-password: ${AZURE_CERTIFICATE_PASSWORD}
profile:
tenant-id: ${AZURE_TENANT_ID}
user-group:
allowed-group-names: group1,group2
allowed-group-ids: <group1-id>,<group2-id>
post-logout-redirect-uri: http://localhost:8080
authorization-clients:
azure:
client-authentication-method: private_key_jwt
arm:
client-authentication-method: private_key_jwt
on-demand: true
scopes: https://management.core.windows.net/user_impersonation
graph:
client-authentication-method: private_key_jwt
scopes:
- https://graph.microsoft.com/User.Read
- https://graph.microsoft.com/Directory.Read.All
webapiA:
client-authentication-method: private_key_jwt
scopes:
- ${WEB_API_A_APP_ID_URL}/Obo.WebApiA.ExampleScope
webapiB:
client-authentication-method: private_key_jwt
scopes:
- ${WEB_API_B_APP_ID_URL}/.default
authorization-grant-type: client_credentials
It's recommended to use the authorization grant type urn:ietf:params:oauth:grant-type:jwt-bearer
in Spring Cloud Azure Active Directory Starter 4.3 and above, the legacy authorization grant type ON_BEHALF_OF
will be deprecated.
If you still configure the authorization grant type on_behalf_of
, it will be replaced with urn:ietf:params:oauth:grant-type:jwt-bearer
automatically.
Compatible usage:
spring.cloud.azure.active-directory.authorization-clients.xxx.authorization-grant-type=on_behalf_of
New usage:
spring.cloud.azure.active-directory.authorization-clients.xxx.authorization-grant-type=urn:ietf:params:oauth:grant-type:jwt-bearer
- Microsoft identity platform application authentication certificate credentials
- Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow
- Microsoft identity platform and OAuth 2.0 authorization code flow
Spring security provides the base implementation for each authorization grant type and client authentication.
You can follow this method stack:
org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter#attemptAuthentication
org.springframework.security.authentication.ProviderManager#authenticate
org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider#authenticate
org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider#getResponse
org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient#getTokenResponse
it will convert the request to the authorization server, invoke the HTTP request, and return the token response.
Each authorization type flow will exchange for an access token from the Azure authorization server using its own response client.
The unity process is required to call the method OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest)
to return the access token.
Each authorization type has its own token response client implementation:
Each token response client provides a method to receive a request entity converter.
void setRequestEntityConverter(
Converter<T extends AbstractOAuth2AuthorizationGrantRequest, RequestEntity<?>> requestEntityConverter)
Before the token request is sent, the request entity converter will be called to enhance the header and parameter.
The base implementation of the request entity converter is OAuth2AuthorizationCodeGrantRequestEntityConverter
, which provides the below method to custom the request data.
public final void addParametersConverter(Converter<T, MultiValueMap<String, String>> parametersConverter) {
Assert.notNull(parametersConverter, "parametersConverter cannot be null");
Converter<T, MultiValueMap<String, String>> currentParametersConverter = this.parametersConverter;
this.parametersConverter = (authorizationGrantRequest) -> {
// Append parameters using a Composite Converter
MultiValueMap<String, String> parameters = currentParametersConverter.convert(authorizationGrantRequest);
if (parameters == null) {
parameters = new LinkedMultiValueMap<>();
}
MultiValueMap<String, String> parametersToAdd = parametersConverter.convert(authorizationGrantRequest);
if (parametersToAdd != null) {
parameters.addAll(parametersToAdd);
}
return parameters;
};
}
Each request entity converter of authorization grant type has a special implementation, the extensions are the header converter and parameter converter, these converters can be used to custom additional information for your own authorization server requirement.
Spring Security provides a default implementation for JWT Client Authentication NimbusJwtClientAuthenticationParametersConverter
, this will be our functional prototype.
The grant type ON_BEHALF_OF
is the grant type org.springframework.security.oauth2.core.AuthorizationGrantType#JWT_BEARER
, it's available to replace the default implementation of Spring Security since the version 5.5.8.
It will be set to client_secret_basic
by default. The built-in method client_secret_jwt
is not supported in our Azure AD starter.
I create an issue to ask MS doc about this feature https://github.com/MicrosoftDocs/azure-docs/issues/93819
It's necessary to follow the implementation NimbusJwtClientAuthenticationParametersConverter
, although Spring Security does not provide such an interface.
/**
* Resolver interface to resolve a function that returns a {@link JWK} implementation through a {@link ClientRegistration}.
*/
public interface OAuth2ClientAuthenticationJWKResolver {
/**
* @return a resolver function.
*/
Function<ClientRegistration, JWK> resolve();
}
public NimbusJwtClientAuthenticationParametersConverter(Function<ClientRegistration, JWK> jwkResolver) {
Assert.notNull(jwkResolver, "jwkResolver cannot be null");
this.jwkResolver = jwkResolver;
}
Spring Cloud Azure 4.3.0 provides the default implementation AadOAuth2ClientAuthenticationJWKResolver
, which will load the local certificate to create the JWT token for client authentication. This resolver will be used for all the authorization grant types, except ON_BEHALF_OF
processes, so it's necessary to replace the ON_BEHALF_OF
flow with JWT_BEARER
flow.
Due to the inconsistency between the previously implemented OBO provider process and Spring Security, there is no way for us to add JWT token generation logic in common.
so we may use the below 2 solutions:
-
Enhance the current
ON_BEHALF_OF
flow to follow the Spring Security pattern.Tried, but it's not available to create a new authorization grant type based on the process provided by Spring Security. I created an issue https://github.com/spring-projects/spring-security/issues/11350
-
Replace the current
ON_BEHALF_OF
withJWT_BEARER
, and enhance it if necessary.Yes, we will do it this way.
Legacy issues:
-
The
AadOboOAuth2AuthorizedClientProvider
should be deprecated?Yes, part of the Conditional Access functionality will also be deprecated too because it is customized based on the MSAL4J library.
-
How do the users want to use the old usage of
ON_BEHALF_OF
?The configuration of the grant type
ON_BEHALF_OF
is still exposed to users, it's more readable than the value ofJWT_BEARER
, but it's not meant to enableAadOboOAuth2AuthorizedClientProvider
's functionality.
-
Redesign a class AadJwtClientAuthenticationParametersConverter
against NimbusJwtClientAuthenticationParametersConverter
- The default implementation does not meet the Azure AD authorization parameters requirement.
- Simplify the JWT encoder(AadJwtEncoder), because there are some compatibility issues when the user uses the Spring Boot 2.5.14 version.
Aditional parameter is required for authorization grant type JWT_BEARER
.
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#first-case-access-token-request-with-a-shared-secret
Due to enabling the default authorization grant type JWT_BEARER
, the JwtBearerOAuth2AuthorizedClientProvider
limits the principal type must be Jwt
, so we must change the default jwt authentication converter, and use the default converter JwtAuthenticationConverter
instead in AadResourceServerWebSecurityConfigurerAdapter
, it's required when the authorization grant type is JWT_BEARER
; so we should deprecate the AadOAuth2AuthenticatedPrincipal, and AadJwtBearerTokenAuthenticationConverter classes.
The built-in user flows do not support using JWT client authentication, my test failed, and I create the below issues to ask MS doc:
- https://github.com/MicrosoftDocs/azure-docs/issues/94274
- https://github.com/MicrosoftDocs/azure-docs/issues/94275
After my research, this feature must be turned on through a custom policy, the custom is an advanced usage of Azure AD B2C that requires further study. So I and Rujun suggest not to do it now. The spring-cloud-azure-starter-active-directory-b2c
should be supported later and currently, there are no customers asking.