Robin Hermans
Robin Hermans

Reputation: 1599

Spring Security Ouath2 : Extended UserDetails not returned by the Principal object

Last week I started on extending the UserDetails class to be able to support a custom field. The special thing about this field is that it gets filled with a value that depends an a request parameter. I managed to implement this correctly (so the question does not focus on that).

Now the thing is that after a successfull login the UserDetails object gets filled correctly (I was able to see this using a AuthenticationSuccessHandler) and client recieves a JWT token from the OAuth2 provider. The client then tries to fetch more details on the user by visiting the "/uaa/user" endpoint. This is set to return the Principal object. But after checking the contents of the Principal object I was supprised that the UserDetails object was missing. The method getPrincipal() only returned the username instead of the UserDetails object.

According to this question this is the result of a failed login. The AuthenticationToken (in this case a UsernamePasswordAuthenticationToken) gets rejected by the AuthenticationManager. I have no idea why it should do such a thing. The authentication with the default UserDetails object seems to work just fine. Can someone help me solve this problem?

Some details on the implemented classes (as mentioned above). Some code has been left out here for reasons.

CustomUserDetails

public class CustomUserDetails extends User {

  private final Integer custom;

  public CustomUserDetails (...default params..., Integer custom) {
    super(...default params...);
    this.custom = custom;
  }
}

CustomUserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {
  @Override
  public CustomUserDetails loadUserByUsername(String username) throw UsernameNotFoundException {
    return new CustomUserDetails(...default params..., 12345);
  }
}

Configuration

@Autowired
private CustomUserDetails userDetails;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(userDetails);
}

User Endpoint

@RequestMapping(value = "/user", method = RequestMethod.GET)
@ResponseBody
public Principal getDetails(Principal user) {
  return user;
}

The Principal object returned here should have the UserDetails object inside of it and should return this to the client. But instead of that it only returns a String with the username when you call getPrincipal();

In the end I want the JSON returned by the User endpoint (which returns the Principle object) to contain the custom field I added to the UserDetails.

Thanks in advance.

Upvotes: 10

Views: 1911

Answers (2)

Maleen Abewardana
Maleen Abewardana

Reputation: 14572

You can use this method, in any case to get extended user details object, in controller or anywhere you need. There can be cons in this method, but its effective.

 Authentication auth = SecurityContextHolder.getContext().getAuthentication();
 MyUserDetails myUser = (MyUserDetails) auth.getPrincipal();

public class MyUserDetails implements 
org.springframework.security.core.userdetails.UserDetails {

private User user; //This is the user domain.
private String firstName;
private String lastName;

public Collection<? extends GrantedAuthority> getAuthorities() {
    List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
    authList.add(new SimpleGrantedAuthority(user.getRole().getName()));
    return authList;
}


public String getPassword() {
    return user.getPassword();
}

public String getUsername() {
    return user.getEmail();
}

public boolean isAccountNonExpired() {
    return ((user.getAccountState() == AccountState.InActive) || (user.getAccountState() == AccountState.Blocked) ? false : true);
}

public boolean isAccountNonLocked() {
    return (user.getAccountState() == AccountState.Locked) ? false : true;
}

public boolean isCredentialsNonExpired() {
    return true;
}

public boolean isEnabled() {
    return ((user.getAccountState() == AccountState.Active)
            || (user.getAccountState() == AccountState.PasswordReset)
            || (user.getAccountState() == AccountState.UnVerified) ? true
            : false);
}

public User getUser() {
    return user;
}

public void setUser(User user) {
    this.user = user;
}


public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

public String getLastName() {
    return lastName;
}


public void setLastName(String lastName) {
    this.lastName = lastName;
}


}

Upvotes: -1

Jaiwo99
Jaiwo99

Reputation: 10017

Generally, you need the annotation @AuthenticationPrincipal, but I will suggest you to build your own annotation, something like this:

/**
 * Our own {@link AuthenticationPrincipal} annotation as suggested by
 * http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#mvc-authentication-principal
 *
 */
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {}

Then you can have this Principal in this way:

@RequestMapping(..)
public Principal test(@CurrentUser Principal principal){..}

BUT, IMHO you should have your own Impl of Principal, or rather extends the existing impl. something like this:

public MyPrincipal extends org.springframework.security.core.userdetails.User {..}

In this case you can return values whatever you want to.

Upvotes: 6

Related Questions