-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Support jwt client authentication for Spring Cloud Azure AD Starter #29471
Conversation
…/support-jwt-client-authentication
Thank you for your contribution moarychan! We will review the pull request and get back to you soon. |
{ | ||
"code": "java.annotation.attributeValueChanged", | ||
"old": "class com.azure.spring.cloud.autoconfigure.aad.AadAutoConfiguration", | ||
"new": "class com.azure.spring.cloud.autoconfigure.aad.AadAutoConfiguration", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "new" necessary here?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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".
|
||
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FunctionalInterface | ||
public interface OAuth2ClientAuthenticationJwkResolver { | ||
|
||
/** | ||
* @param clientRegistration the client registration. | ||
* @return a a {@link JWK}. | ||
*/ | ||
JWK resolve(ClientRegistration clientRegistration); | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
...in/java/com/azure/spring/cloud/autoconfigure/aad/properties/AadAuthenticationProperties.java
Show resolved
Hide resolved
{ | ||
"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." |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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." |
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getOrDefault?
There was a problem hiding this comment.
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 " |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we return null?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Description
Fixes #29137 #27032
Changelog:
Deprecated
, useAadAuthorizationGrantType
AuthorizationGrantType
instead.Deprecated
,AadOAuth2AuthenticatedPrincipal
, use the default converterAadJwtBearerTokenAuthenticationConverter
JwtAuthenticationConverter
instead inAadResourceServerWebSecurityConfigurerAdapter
, it's required when the authorization grant type is JWT_BEARER.Deprecated
, useAadOboOAuth2AuthorizedClientProvider
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 toNimbusJwtClientAuthenticationParametersConverter
, but it's not suitable for our scenario, we can not custom theJwsHeader
; we can deprecate it when upgrading the spring-security-oauth2-client version to 5.7.0, it can be done by theConsumer
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
Replace the authorization grant type to
AuthorizationGrantType
inAuthorizationClientProperties
class.The authorization grant type
ON_BEHALF_OF
will be replaced withJWT_BEARER
type automatically.How to make it still available to use
AadOboOAuth2AuthorizedClientProvider
flow for developers?All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines