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.