-
Notifications
You must be signed in to change notification settings - Fork 6k
Improve API for delegation-based strategy #12282
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
Improve API for delegation-based strategy #12282
Conversation
- Make OidcUserService#getUser(OidcUserRequest, OidcUserInfo, Set) protected - OidcReactiveOAuth2UserService: Abstract out logic to set DefaultOidcUser nameAttributeKey from OidcUserRequest into a new protected getUser(OidcUserRequest, OidcUserInfo, Set) method
Any update on this? Its been awhile |
Thanks for the ping @daniel-shuy! I have not had time to look at this recently, but I will add it to my TODO list for after the 6.1 release next week. |
@daniel-shuy, thanks for the PR! However, I don't find myself in favor of introducing a I understand that the docs demonstrate using delegation and use the example of mapping authorities as the use case for the customization. However, the previous example of Using a GrantedAuthoritiesMapper would suffice in most cases. Do you truly have an advanced scenario that requires delegation? Or were you led that way by the docs only due to the example? Can you describe your use case for the customization here? |
Yes, to configure Keycloak stores the roles in the access token payload as e.g. {
// ...
"realm_access": {
"roles": [
// Roles
]
},
"resource_access": {
"client": {
"roles": [
// Authorities
]
},
// ...
},
// ...
} Therefore to map Keycloak roles to Spring Security authorities, a custom @Bean
public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();
return (userRequest) -> {
// Delegate to the default implementation for loading a user
return delegate.loadUser(userRequest)
.flatMap((oidcUser) -> {
OAuth2AccessToken accessToken = userRequest.getAccessToken();
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
// map Keycloak roles in token to Spring Security authorities
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
if (StringUtils.hasText(userNameAttributeName)) {
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(), userNameAttributeName);
} else {
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
}
return Mono.just(oidcUser);
});
};
} The current API is very hard to understand (requires understanding the inner workings of OidcUserService/OidcReactiveOAuth2UserService) and error prone (the bug in the documentation still isn't fixed, see gh-12281). |
What about adding a setter (e.g. |
Thanks for the use case @daniel-shuy, that's helpful. First, I would suggest using Keycloak's mappers to map claims to the In any case, because the
We could do something very similar in |
Yes it is possible, but I would prefer not having to change the Keycloak configuration as much as possible, just to get it working with Spring Security, as I am trying to write an open source library that can be reused by others, to replace keycloak-spring-boot-starter, which is now deprecated (https://www.keycloak.org/2022/02/adapter-deprecation.html).
Ah, I was thinking to simply map them to public interface OidcUserRequestAuthoritiesConverter implements Converter<OidcUserRequest, Collection<String>> {
}
public interface OAuth2UserRequestAuthoritiesConverter implements Converter<OAuth2UserRequest, Collection<String>> {
} Then a setter (e.g.
Both |
I see. Thanks!
I'm not sure this will work. As I mentioned, the We could introduce a Also, Lastly, I think any new hook we add here should extend from the hook we add for |
Oops, sorry, I meant to write: public interface OidcUserRequestAuthoritiesConverter {
Collection<String> toAuthorities(OidcUserRequest userRequest, OidcUserInfo userInfo);
}
public interface OAuth2UserRequestAuthoritiesConverter {
Collection<String> toAuthorities(OAuth2UserRequest userRequest, Map<String, Object> userAttributes);
}
I have a question - I see
I don't think that is possible, as |
That's looking better for sure! However, I don't think they should return
I think there would need to be an advantage to changing it, which I'm not sure about right now. If there is, we'd probably want to address that separately. Currently, we return a That's why I'm excited about introducing this new option in
That's a fair point. I was just thinking, if possible the hooks should be closely related. But you're right, probably not a hierarchy. |
I'm kind of on the fence about this. If the mapper should follow OidcUserService#loadUser(OidcUserRequest)/DefaultOAuth2UserService(OAuth2UserRequest), to create a single Another option is to simply map everything as What is the purpose/advantage of using |
I'm not 100% sure the background of the existing feature, but I believe it's purpose is to allow the
We will need to enhance the docs along with this change. But if we add this capability, I think the inner workings of the class become less important for users, because usually you would choose one of: a) customize using this new option, so you don't usually require additional mapping and don't need the special authority Does that make sense?
I want to be careful not to expand the scope of this enhancement. While we could add mappings to a collection containing that existing authority, as stated above I think there's better flexibility and a simpler contract by allowing the user to fully override the mapped collection. We just need to document that is what the user is doing by customizing. |
Sounds good! I'm in favor of giving users the flexibility while enhancing the docs |
@daniel-shuy just checking in. Are you able to make updates to this PR based on my comment above? |
@daniel-shuy I'm going to close this PR in favor of the approach I outlined above. I've created a branch with changes for the servlet side to demonstrate. I will proceed with the reactive side shortly so we can try for 6.3.0-M3. Please let me know if you have any thoughts. |
The current API for delegation-based strategy with OidcUserService/OidcReactiveOAuth2UserService is error-prone because it requires understanding the inner workings of OidcUserService/OidcReactiveOAuth2UserService (see gh-12275 and gh-12281 for the original discussion).
This improves the API by:
OidcUserService#getUser(OidcUserRequest, OidcUserInfo, Set)
protected
OidcReactiveOAuth2UserService
: Abstract out logic to setDefaultOidcUser
nameAttributeKey
fromOidcUserRequest
into a newprotected
getUser(OidcUserRequest, OidcUserInfo, Set)
methodThis allows the delegation-based strategy to be massively simplified (especially for reactive).
Using the example in the doc, the configuration can be simplified from:
to: