Skip to content

Authenticate SAML user by UserDetailService #8010

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

Closed
DEEPAKRDEEPS opened this issue Feb 23, 2020 · 13 comments
Closed

Authenticate SAML user by UserDetailService #8010

DEEPAKRDEEPS opened this issue Feb 23, 2020 · 13 comments
Labels
in: saml2 An issue in SAML2 modules status: ideal-for-contribution An issue that we actively are looking for someone to help us with type: enhancement A general enhancement

Comments

@DEEPAKRDEEPS
Copy link

DEEPAKRDEEPS commented Feb 23, 2020

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

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 23, 2020
@eleftherias eleftherias self-assigned this Feb 24, 2020
@fhanik
Copy link
Contributor

fhanik commented Feb 24, 2020

Implementation would ideally reflect the choices made when implementing
org.springframework.security.oauth2.client.userinfo.OAuth2UserService

@DEEPAKRDEEPS
Copy link
Author

@eleftherias Can I know whether this changes will be there for spring-security 5.3.x.

@eleftherias
Copy link
Contributor

@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.

@eleftherias eleftherias added in: saml2 An issue in SAML2 modules type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 25, 2020
@DEEPAKRDEEPS
Copy link
Author

DEEPAKRDEEPS commented Mar 3, 2020 via email

@rwinch
Copy link
Member

rwinch commented Mar 4, 2020

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; 
        })
    )

@eleftherias eleftherias removed their assignment Mar 12, 2020
@DEEPAKRDEEPS
Copy link
Author

Work around to customize the authentication works fine. Thanks

@chelseakohli
Copy link

@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.

@herrminni
Copy link

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 {
        }

}

@chelseakohli
Copy link

@herrminni Thanks a lot. Was really stuck at this for few days. It is working as expected now. Cheers!!!

@ryan13mt
Copy link

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

@jzheaux
Copy link
Contributor

jzheaux commented Jul 20, 2020

@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?

@ryan13mt
Copy link

@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 #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.

@jzheaux
Copy link
Contributor

jzheaux commented Jul 21, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: saml2 An issue in SAML2 modules status: ideal-for-contribution An issue that we actively are looking for someone to help us with type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

9 participants