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 jwt client authentication for Spring Cloud Azure AD Starter #29471

Merged

Conversation

moarychan
Copy link
Member

@moarychan moarychan commented Jun 15, 2022

Description

Fixes #29137 #27032

Changelog:

  • Deprecated AadAuthorizationGrantType, use AuthorizationGrantType instead.

  • Deprecated AadOAuth2AuthenticatedPrincipal, AadJwtBearerTokenAuthenticationConverter, use the default converter JwtAuthenticationConverter instead in AadResourceServerWebSecurityConfigurerAdapter, it's required when the authorization grant type is JWT_BEARER.

  • Deprecated AadOboOAuth2AuthorizedClientProvider, use JwtBearerOAuth2AuthorizedClientProvider instead.

  • Update validation to allow to override the attributes for azure client registration, such as only override the client authentication method for azure, spring.cloud.azure.active-directory.authorization-clients.azure.client-authentication-method=private_key_jwt.

  • Add default implementation OAuth2ClientAuthenticationJWKResolver to support loading a local certificate to create the client assertion.
    NOTE:
    The AadJwtClientAuthenticationParametersConverter implementation is similar to NimbusJwtClientAuthenticationParametersConverter, but it's not suitable for our scenario, we can not custom the JwsHeader; we can deprecate it when upgrading the spring-security-oauth2-client version to 5.7.0, it can be done by the Consumer function to custom the jws header and claims.

    https://github.com/spring-projects/spring-security/blob/428216b322e1591357d8f7450d6f1045cd7c48d5/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusJwtClientAuthenticationParametersConverter.java#L150-L152

    image

  • Replace the authorization grant type to AuthorizationGrantType in AuthorizationClientProperties class.

  • The authorization grant type ON_BEHALF_OF will be replaced with JWT_BEARER type automatically.

    How to make it still available to use AadOboOAuth2AuthorizedClientProvider flow for developers?

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

@ghost ghost added azure-spring All azure-spring related issues customer-reported Issues that are reported by GitHub users external to the Azure organization. labels Jun 15, 2022
@ghost
Copy link

ghost commented Jun 15, 2022

Thank you for your contribution moarychan! We will review the pull request and get back to you soon.

@moarychan moarychan marked this pull request as ready for review June 15, 2022 02:59
@saragluna saragluna closed this Jun 15, 2022
@saragluna saragluna reopened this Jun 15, 2022
@moarychan moarychan marked this pull request as draft June 20, 2022 08:39
@moarychan moarychan marked this pull request as ready for review June 21, 2022 07:00
{
"code": "java.annotation.attributeValueChanged",
"old": "class com.azure.spring.cloud.autoconfigure.aad.AadAutoConfiguration",
"new": "class com.azure.spring.cloud.autoconfigure.aad.AadAutoConfiguration",

Choose a reason for hiding this comment

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

Is "new" necessary here?

Copy link

@chenrujun chenrujun Jun 24, 2022

Choose a reason for hiding this comment

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

Is it possible to "support JWT Client Authentication" without this change?

Choose a reason for hiding this comment

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

Same to all other changes in current file.
If breaking change is necessary, please add more description in "justification".

Comment on lines 42 to +51

private Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
if (StringUtils.hasText(properties.getPrincipalClaimName())) {
converter.setPrincipalClaimName(properties.getPrincipalClaimName());
}
converter.setJwtGrantedAuthoritiesConverter(
new AadJwtGrantedAuthoritiesConverter(properties.getClaimToAuthorityPrefixMap()));
return converter;
}
Copy link
Member

Choose a reason for hiding this comment

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

Comment on lines 13 to 21
@FunctionalInterface
public interface OAuth2ClientAuthenticationJwkResolver {

/**
* @param clientRegistration the client registration.
* @return a a {@link JWK}.
*/
JWK resolve(ClientRegistration clientRegistration);
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we expect users to provide this bean? If not, can we move it to the impl package?

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved to impl.oauth2 pkg.

{
"code": "java.annotation.attributeValueChanged",
"new": "class com.azure.spring.cloud.autoconfigure.aad.AadAutoConfiguration",
"justification": "The use of AadOboOAuth2AuthorizedClientProvider is not recommended, and there is no need to retain consideration of the different situations brought by this OBO provider."
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you the not recommended but still supported? Is this an API exposed to users or not?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, users can override the bean OAuth2AuthorizedClientManager to take the OBO provider ability.

{
"code": "java.annotation.added",
"new": "class com.azure.spring.cloud.autoconfigure.aad.configuration.AadOAuth2ClientConfiguration",
"justification": "The use of AadOboOAuth2AuthorizedClientProvider is not recommended, and there is no need to retain consideration of the different situations brought by this OBO provider."
Copy link
Contributor

Choose a reason for hiding this comment

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

How about the "justification" focus on the "new code" instead of "xx is no need"?

Copy link
Member Author

Choose a reason for hiding this comment

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

The old code is to meet the different situations of OBO provider usage, as this is deprecated, then it can be removed no matter which grant type is used.


#### Breaking Changes
+ Deprecated classes and properties type changed [#29471](https://github.com/Azure/azure-sdk-for-java/pull/29471).
+ Deprecated ~~AadAuthorizationGrantType~~, use `AuthorizationGrantType` instead.
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we just @deprecate an interface or changed the implementation? If we use deprecate, we mean the api is @deprecated and add a new one.

It seems this place should be "removed" and replaced with a new one.
Do we mean this?

Copy link
Member Author

Choose a reason for hiding this comment

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

This @deprecated is to tell users not to use the AadAuthorizationGrantType, and still can be used for some users that depend on this type when upgrading the AAD starter to 4.3.0. It will be kept until 5.0.

#### Breaking Changes
+ Deprecated classes and properties type changed [#29471](https://github.com/Azure/azure-sdk-for-java/pull/29471).
+ Deprecated *~~AadAuthorizationGrantType~~*, use `AuthorizationGrantType` instead.
+ The type of property *authorizationGrantType* is changed to `AuthorizationGrantType` in `AuthorizationClientProperties` class.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this duplicate with the above one? Or they sit in different packages?

Copy link
Member Author

Choose a reason for hiding this comment

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

One is for AAD starter, another is for AAD B2C starter.

import static com.azure.spring.cloud.autoconfigure.aad.properties.AadAuthorizationGrantType.AUTHORIZATION_CODE;
import static com.azure.spring.cloud.autoconfigure.aad.properties.AadAuthorizationGrantType.AZURE_DELEGATED;
import static com.azure.spring.cloud.autoconfigure.aad.implementation.constants.Constants.AZURE_DELEGATED;
import static com.azure.spring.cloud.autoconfigure.aad.implementation.constants.Constants.ON_BEHALF_OF;
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we use this const instead of the original enum one?

Copy link
Member Author

Choose a reason for hiding this comment

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

They are all the same type AuthorizationGrantType.

allClients.put(AZURE_CLIENT_REGISTRATION_ID, azureClient);
}

private ClientAuthenticationMethod getAzureDefaultClientAuthenticationMethod() {
if (this.allClients.containsKey(AZURE_CLIENT_REGISTRATION_ID)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

getOrDefault?

Copy link
Member Author

@moarychan moarychan Jun 28, 2022

Choose a reason for hiding this comment

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

This means getting the client authentication method of the default client(azure), there might be some ambiguity.


if (ON_BEHALF_OF.equals(authorizationGrantType)) {
authorizationGrantType = JWT_BEARER;
LOGGER.warn("The grant type 'on_behalf_of' of the client '{}' is not recommended, it will be "
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we warn it here?

Copy link
Member Author

Choose a reason for hiding this comment

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

It means the type value is changed by our logic, I will improve the log content.

LOGGER.error("Resolve RSAKey exception.", e);
}
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

should we return null?

Copy link
Member Author

Choose a reason for hiding this comment

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

Currently, only supports the PRIVATE_KEY_JWT.

.build();
} catch (KeyStoreException | IOException | NoSuchAlgorithmException
| CertificateException | UnrecoverableKeyException e) {
LOGGER.error("Resolve RSAKey exception.", e);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we only log this error?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the null will be cached in com.azure.spring.cloud.autoconfigure.aad.implementation.jwt.AadJwtClientAuthenticationParametersConverter#convert.

JWK jwk = this.jwkResolver.apply(clientRegistration);
  if (jwk == null) {
      OAuth2Error oauth2Error = new OAuth2Error(INVALID_KEY_ERROR_CODE,
          "Failed to resolve JWK signing key for client registration '"
              + clientRegistration.getRegistrationId() + "'.",
          null);
      throw new OAuth2AuthorizationException(oauth2Error);
  }

}

private byte[] getX5t(X509Certificate cert)
throws NoSuchAlgorithmException, CertificateEncodingException {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we throw this EX?

Copy link
Member Author

Choose a reason for hiding this comment

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

same as above

return getSHA1Byte(cert.getEncoded());
}

private byte[] getSHA1Byte(byte[] data) throws NoSuchAlgorithmException {
Copy link
Contributor

@jialigit jialigit Jun 28, 2022

Choose a reason for hiding this comment

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

How about encodeWithSha1 or the like?

// @formatter:off
}

private Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we define a bean of this object?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not necessary, this adapter is built-in in our starter, users can re-define a new one to custom the adapter without this converter.

@@ -114,9 +122,14 @@ protected LogoutSuccessHandler oidcLogoutSuccessHandler() {
protected OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient result = new DefaultAuthorizationCodeTokenResponseClient();
if (repo instanceof AadClientRegistrationRepository) {
result.setRequestEntityConverter(
AadOAuth2AuthorizationCodeGrantRequestEntityConverter converter =
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use a more OO way in this place?

Copy link
Member Author

Choose a reason for hiding this comment

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

Please provide your proposal for reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
azure-spring All azure-spring related issues customer-reported Issues that are reported by GitHub users external to the Azure organization.
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

[FEATURE REQ] Support JWT Client Authentication for Spring Cloud Azure AD Starters
4 participants