Authenticating a SAML user by UserDetailsService. Like LDAP and spring saml extension does I need to plugin my authorisation part in userdetail service and map roles and authorities from database Spring Security 5.2.2. RELEASE
Comment From: fhanik
Implementation would ideally reflect the choices made when implementing
org.springframework.security.oauth2.client.userinfo.OAuth2UserService
Comment From: DEEPAKRDEEPS
@eleftherias Can I know whether this changes will be there for spring-security 5.3.x.
Comment From: eleftherias
@DEEPAKRDEEPS This will not be part of 5.3.0, since that release is scheduled for next week. Once we have prioritized this, we will add a milestone to this GitHub issue indicating the release that it will be included in.
Comment From: DEEPAKRDEEPS
Currently we using getting the response from IDP and using that I'd we will query the user detail part from database. Can I know whether in spring security 5 SAML this feature available or not?
In spring security saml extension we have saml user details interface it is easy to implement it and load the details in loadbyusername method
Regards
On Wed, Feb 26, 2020, 1:39 AM Eleftheria Stein-Kousathana notifications@github.com wrote:
@DEEPAKRDEEPS https://github.com/DEEPAKRDEEPS This will not be part of 5.3.0, since that release is scheduled for next week. Once we have prioritized this, we will add a milestone to this GitHub issue indicating the release that it will be included in.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spring-projects/spring-security/issues/8010?email_source=notifications&email_token=ACZ45KU4MP2545GIU5UNJMTREV3HRA5CNFSM4KZ4HLN2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEM5KCKA#issuecomment-591044904, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACZ45KVN7ZQPITBUXS3U72DREV3HRANCNFSM4KZ4HLNQ .
Comment From: rwinch
We haven't yet scheduled this for a release. In the meantime, you can do something like this to customize the authentication
OpenSamlAuthenticationProvider p = new OpenSamlAuthenticationProvider();
http
.saml2Login(saml2 -> saml2
.authenticationManager(a -> {
Saml2Authentication result = p.authenticate(a);
// transform the result however you want
return result;
})
)
Comment From: DEEPAKRDEEPS
Work around to customize the authentication works fine. Thanks
Comment From: chelseakohli
@DEEPAKRDEEPS i am also facing the same issue to map roles and authorities from database. Can you please explain how you were able to do it.
Comment From: herrminni
Hi @chelseakohli,
I suppose you already have a custom UserDetailsService, that looks up a user by username and delivers roles.
Additionally, you need to write a custom AuthenticationManager for SAML and set it in a WebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/* UserDetailsService implementation, that looks for the user by userName in your database, etc. and sets roles */
@Autowired
private CustomUserDetailsService userDetailsServiceImp;
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.saml2Login(saml2 -> {
saml2.authenticationManager(new Saml2UserDetailsAuthenticationManager(userDetailsServiceImp));
})
}
The Saml2UserDetailsAuthenticationManager looks like this:
public class Saml2UserDetailsAuthenticationManager implements AuthenticationManager {
/** The user details service imp. */
private CustomUserDetailsService userDetailsServiceImp;
/** The open saml auth provider. */
private OpenSamlAuthenticationProvider openSamlAuthProvider = new OpenSamlAuthenticationProvider();
public Saml2UserDetailsAuthenticationManager(CustomUserDetailsService userDetailsServiceImp) {
this.userDetailsServiceImp = userDetailsServiceImp;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Saml2Authentication saml2AuthenticationResult = (Saml2Authentication) openSamlAuthProvider.authenticate(authentication);
AuthenticatedPrincipal principal = (AuthenticatedPrincipal) saml2AuthenticationResult.getPrincipal();
// load SAML authenticated user form local user details
UserDetails userDetails = userDetailsServiceImp.loadUserByUsername(principal.getName());
return new Saml2WithUserDetailsAuthentication(saml2AuthenticationResult, userDetails);
}
}
And the Saml2WithUserDetailsAuthentication can look like this:
public class Saml2WithUserDetailsAuthentication implements Authentication {
private UserDetails userDetails = null;
private Saml2Authentication saml2Authentication = null;
public Saml2WithUserDetailsAuthentication(Saml2Authentication saml2Authentication, UserDetails userDetails) {
this.saml2Authentication = saml2Authentication;
this.userDetails = userDetails;
}
@Override
public String getName() {
return this.userDetails.getUsername();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.userDetails.getAuthorities();
}
@Override
public Object getCredentials() {
return this.saml2Authentication;
}
@Override
public Object getDetails() {
return this.userDetails;
}
@Override
public Object getPrincipal() {
return this.userDetails;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
}
Comment From: chelseakohli
@herrminni Thanks a lot. Was really stuck at this for few days. It is working as expected now. Cheers!!!
Comment From: ryan13mt
Hi everyone i have a similar issue when migrating from the deprecate library to the new Spring-Security.
Basically what we used to do before was loadUserBySAML(SAMLCredential credential). Here we used to do a lot of custom business logic related with the remoteEntityId and also retrieve any custom attributes sent in the assertion. How can this be done using the new library? Using your example above i would also need to get the encrypted assertion and parse, validate and decrypt it again which kind of defeats the purpose.
I am trying to avoid having a lot of custom classes just copy-paste from the library. The most frustrating i am finding is that the OpenSamlAuthenticationProvider class is final and a lot of methods are private meaning i cannot use any of them like the following methods: parse() validateResponse() decrypt()
Removing the class from final and changing the access level of some methods will help developers having to avoid a whole re-write of the OpenSamlAuthenticationProvider just to change one small thing.
Is there any another way how i can retrieve the decrypted assertion after we do OpenSamlAuthenticationProvider.authenticate() without having to parse and validate it again?
@jzheaux @herrminni @rwinch
Thanks
Comment From: jzheaux
@ryan13mt great points. OpenSamlAuthenticationProvider's configurability is something that should be enhanced.
I'm thinking that one way we could do this is introduce a setter:
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
provider.setResponseAuthenticationConverter(token -> response -> {
Saml2AuthenticatedPrincipal principal = // ... get custom instance
Collection<GrantedAuthority> authorities = // ... get custom authorities
return new Saml2Authentication(principal, token.getSaml2Response(), authorities);
});
Of course, you could return a completely custom Authentication instance as well.
With the above setup, response would already be decrypted and validated instance of OpenSAML's Response class.
I like this because it's similar to how it works with the OAuth 2.0 support in JwtAuthenticationProvider#setJwtAuthenticationConverter.
How well would that approach address your use case?
Comment From: ryan13mt
@jzheaux the response would include the Idp Entity ID (remoteEntityId) as well i assume right?
Just another enhancement suggestion, if you include another setter for the decrypter it would solve https://github.com/spring-projects/spring-security/issues/8349 where we have the private keys stored in an external vault for decryption. That way we can pass a custom Decrypter that does not need to have access to the private key directly.
That way the getDecrypter() method will return the default decrypter or the one you set.
Sorry for going off-topic but these issues all stem from the OpenSamlAuthenticationProvider limited access level.
Comment From: jzheaux
Yes, I believe the OpenSAML Response object contains the remote entity id via the Issuer.
Also, though, you can retrieve those kinds of configuration values in the token, e.g. token.getRelyingPartyRegistration().getAssertingPartyDetails().getEntityId().
Would you be able to submit a PR adding the setter? Also, since this would be broader than the granted authorities setters, I think it would be nice to clean that up and deprecate those methods.
if you include another setter for the decrypter
I think that would be something good to do; there's still a bit of research needed to understand what the correct contract is - if you have time to generate a sample that uses a proposed setter, that would go a long way.