1

I have a user that has logged in through AD and now I want to get some of their information. Here's a sample test endpoint I am playing with:

@RequestMapping(value={"/secure/test"}, method=RequestMethod.GET)
public ResponseEntity<?> getSecureTest(HttpServletRequest request) {
    String str = "Test Response";

    request.getSession().setAttribute("testVar", "SessionVariable");

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    if (!(authentication instanceof AnonymousAuthenticationToken)) {
        String currentUserName = authentication.getName();
        str = str + "\n -- " + currentUserName + "\n\n";
        str = str + userDetails.getUsername(); // matches authentication.getName()
        return new ResponseEntity<>(str, HttpStatus.OK);
    } else {
        str = str + "failed auth";
        return new ResponseEntity<>(str, HttpStatus.UNAUTHORIZED);
    }
}

I can get the authentication and from that, a UserDetails, but the implementation that comes out I believe is a LdapUserDetailsImpl

https://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/ldap/userdetails/LdapUserDetailsImpl.html

This doesn't appear to have any methods to like "getAttribute" or whatever. If I wanted to get one of the AD attributes such as "mail" or "telephoneNumber", how can I get it?

Edit:

So, just to try to extract the "title" attribute I extended the LdapUserDetailsImpl:

public class CustomUserDetails extends LdapUserDetailsImpl {

    private String title;

    public void setTitle(String title) {
        this.title = title;
    }
    public String getTitle() {
        return this.title;
    }
}

And I extended the LdapUserDetailsMapper:

public class CustomDetailsContextMapper extends LdapUserDetailsMapper {

    @Override
    public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
        LdapUserDetailsImpl ldapUserDetailsImpl = (LdapUserDetailsImpl) super.mapUserFromContext(ctx, username, authorities);
        CustomUserDetails customUserDetails = new CustomUserDetails();
        customUserDetails.setTitle(ctx.getStringAttribute("title"));
        return customUserDetails;
    }
}

In my controller, I try to get this object:

CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();

but this gives me a casting error... What am I missing?

WebSecurityConfigurerAdapter has this stuff in it:

@Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
    authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
    authManagerBuilder.userDetailsService(userDetailsService());
}

@Bean
public AuthenticationManager authenticationManager() {
    return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProviderES()));
}
@Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
    ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapdomain, ldapurl);
    provider.setConvertSubErrorCodesToExceptions(true);
    provider.setUseAuthenticationRequestCredentials(true);

    return provider;
}

2 Answers 2

3

First set your provider by adding below in your SecurityConfiguration. If not set, defaults to a simple LdapUserDetailsMapper which doesn't have all attributes.

provider.setUserDetailsContextMapper(userDetailsContextMapper());

@Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
    ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapdomain, ldapurl);
    provider.setConvertSubErrorCodesToExceptions(true);
    provider.setUseAuthenticationRequestCredentials(true);
    provider.setUserDetailsContextMapper(userDetailsContextMapper());
    return provider;
}

@Bean
public UserDetailsContextMapper userDetailsContextMapper() {
     return new CustomUserMapper();
}

Then create a custom mapper extending LdapUserDetailsMapper

public class CustomUserMapper extends LdapUserDetailsMapper{

    @Override
    public CustomUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities){

        // set from userDetails 
        UserDetails details = super.mapUserFromContext(ctx, username, authorities);

        // set directly from ctx 
        CustomUserDetails customUserDetails = new CustomUserDetails();
        customUserDetails.setFirstName(ctx.getStringAttribute("givenName"));
        customUserDetails.setLastName(ctx.getStringAttribute("sn"));

        return customUserDetails;
    }

}
Sign up to request clarification or add additional context in comments.

Comments

2

You can implement your own user details mapper by extending springs ldap one.

package example.active.directory.authentication;

import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collection;

public class CustomUserMapper extends LdapUserDetailsMapper{

    @Override
    public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities){

        UserDetails details = super.mapUserFromContext(ctx, username, authorities);
        String[] changedValues = ctx.getStringAttributes("whenchanged");
        /// Do something here, like map to your custom UserDetails object.
        return details;        
    }
}

If you set a breakpoint in that method, you should be able to explore all the different attributes available to you in your debugger.

This is similar to another answer I gave: Update users informations during login against LDAP AD using Spring

9 Comments

In this case, you would need to create some additional fields on CustomUserMapper and then assign those fields to the stuff you get out of ctx.getStringAttribute or whatever, right? How does this get used by the controller later? Just by casting to this particular type?
You will need to add the additional fields (as required) to your custom 'UserDetails' object. Then your mapper which extends the LDAP UserDetails mapper will populate the fields in your UserDetails object as required by you. You will then be able to grab your version of the UserDetails from the SecurityContext as needed from the Controller.
Can you see my update? I extended all of the UserDetails classes to try to capture the attributes but it's still not working.
No additional fields are needed. Any data you want to extract from the context would be added to the UserDetails object and returned. If you create your own custom UserDetails then you could add the fields there. As for where to put the mapper, that goes in you security configuration not in your controller. Then when you get the principal you would cast it to your custom userdetails object.,
Clarification, no additional fields on the controller. This is all in your security configuration.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.