Alex Man
Alex Man

Reputation: 4886

Spring OAuth2.0: Getting User Roles based on Client Id

I have multiple clients registered for my oauth2 auth server. lets say user1 have roles such as ROLE_A, ROLE_B for client1, same user has roes such as ROLE_C, ROLE_D for client2. now when the user logins either using client1 or client2 he is able to see all the four roles ie. ROLE_A, ROLE_B, ROLE_C and ROLE_D.

My requirement was when the user1 logins to client1 it should return only the roles ROLE_A and ROLE_B. when he logins using client2 it should return only ROLE_C and ROLE_D

For achieving this, what I planned is within the authenticate function, I need to get the clientId. so using the clientId and the username I can find the corresponding roles allocated to the user from the db (client-user-roles-mapping table). .But the issue is I don't know how to get the clientId within the authenticate function

 @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
        String userName = ((String) authentication.getPrincipal()).toLowerCase();
        String password = (String) authentication.getCredentials();
        if (userName != null && authentication.getCredentials() != null) {
                String clientId = // HERE HOW TO GET THE CLIENT ID 
                Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
                Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
                Authentication token =  new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
                return token;
            } else {
                throw new BadCredentialsException("Authentication Failed!!!");
            }
         } else {
             throw new BadCredentialsException("Username or Password cannot be empty!!!");
         }         
    }

Can anyone please help me on this

UPDATE 1

CustomAuthenticationProvider.java

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private LDAPAuthenticationProvider ldapAuthentication;

    @Autowired
    private AuthRepository authRepository;

    public CustomAuthenticationProvider() {
        super();
    }

    @Override
        public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
            String userName = ((String) authentication.getPrincipal()).toLowerCase();
            String password = (String) authentication.getCredentials();
            if (userName != null && authentication.getCredentials() != null) {
                    String clientId = // HERE HOW TO GET THE CLIENT ID 
                    Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
                    Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
                    Authentication token =  new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
                    return token;
                } else {
                    throw new BadCredentialsException("Authentication Failed!!!");
                }
             } else {
                 throw new BadCredentialsException("Username or Password cannot be empty!!!");
             }         
    }


    public boolean invokeAuthentication(String username, String password, Boolean isClientValidation) {
        try {
            Map<String, Object> userDetails = ldapAuthentication.authenticateUser(username, password);
            if(Boolean.parseBoolean(userDetails.get("success").toString())) {
                return true;
            }
        } catch (Exception exception) {
            log.error("Exception in invokeAuthentication::: " + exception.getMessage());
        }
        return false;
    }

    @Override
    public boolean supports(Class<? extends Object> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }

    private Collection<SimpleGrantedAuthority> fillUserAuthorities(Set<String> roles) {
        Collection<SimpleGrantedAuthority> authorties = new ArrayList<SimpleGrantedAuthority>();
        for(String role : roles) {
            authorties.add(new SimpleGrantedAuthority(role));
        }
        return authorties;
    }
}

Upvotes: 6

Views: 7125

Answers (3)

CGS
CGS

Reputation: 2872

For your requirement, since you want to just access additional parameters from the request, you could try out the following in your CustomAuthenticationProvider class

@Autowired
    private HttpServletRequest request;

Add the following logic to read the httpRequest parameters and add your logic to access the authorization key

@Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    Enumeration<String> headerNames = request.getHeaderNames();
    while(headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement();
        System.out.println("Header Name - " + headerName + ", Value - " + request.getHeader(headerName));
   }
}

Now, you will have the encode Basic Authentication field which you can decode like the one below

if (authorization != null && authorization.startsWith("Basic")) {
        // Authorization: Basic base64credentials
        String base64Credentials = authorization.substring("Basic".length()).trim();
        String credentials = new String(Base64.getDecoder().decode(base64Credentials),
                Charset.forName("UTF-8"));
        // client/secret = clientId:secret
        final String[] values = credentials.split(":",2);

Upvotes: 2

Shadi
Shadi

Reputation: 183

Here is you code after modification

@Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
    String userName = ((String) authentication.getPrincipal()).toLowerCase();
    String password = (String) authentication.getCredentials();
    if (userName != null && authentication.getCredentials() != null) {
            String clientId = getClientId();
            // validate client ID before use
            Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
            Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
            Authentication token =  new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
            return token;
        } else {
            throw new BadCredentialsException("Authentication Failed!!!");
        }
     } else {
         throw new BadCredentialsException("Username or Password cannot be empty!!!");
     }         


private  String getClientId(){
    final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

    final String authorizationHeaderValue = request.getHeader("Authorization");
    final String base64AuthorizationHeader = Optional.ofNullable(authorizationHeaderValue)
            .map(headerValue->headerValue.substring("Basic ".length())).orElse("");

    if(StringUtils.isNotEmpty(base64AuthorizationHeader)){
        String decodedAuthorizationHeader = new String(Base64.getDecoder().decode(base64AuthorizationHeader), Charset.forName("UTF-8"));
        return decodedAuthorizationHeader.split(":")[0];
    }

    return "";
}

more info about RequestContextHolder

Upvotes: 6

manish
manish

Reputation: 20135

Extend UsernamePasswordAuthenticationToken


A POJO is required to hold not just the username and the password but also the client identifier.

public ExtendedUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
  private final String clientId;

  public ExtendedUsernamePasswordAuthenticationToken(Object principal
                                                    , Object credentials
                                                    , String clientId) {
    super(principal, credentials);

    this.clientId = clientId;
  }

  public String getClientId() { return clientId; }
}

Extend UsernamePasswordAuthenticationFilter


The authentication process needs to be tweaked so that the client identifier is passed to the authentication code in addition to the username and password.

public class ExtendedUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
  public ExtendedUsernamePasswordAuthenticationFilter () { super(); }

  @Override
  public public Authentication attemptAuthentication(HttpServletRequest request
                                                    , HttpServletResponse response)
                                                    throws AuthenticationException {
    // See the source code of UsernamePasswordAuthenticationFilter
    // to implement this. Instead of creating an instance of
    // UsernamePasswordAuthenticationToken, create an instance of
    // ExtendedUsernamePasswordAuthenticationToken, something along
    // the lines of:

    final String username = obtainUsername(request);
    final String password = obtainPassword(request);
    final String clientId = obtainClientId(request);

    ...

    final Authentication authentication = new ExtendedUsernamePasswordAuthenticationToken(username, password, clientId);

    return getAuthenticationManager().authenticate(authentication);
  }
}

Use the extra information available for logging in


public CustomAuthenticationProvider implements AuthenticationProvider {
  ...

  @Override
  public boolean supports(final Class<?> authentication) {
    return authentication.isAssignableFrom(ExtendedUsernamePasswordAuthenticationToken.class);
  }


  @Override
  public Authentication authenticate(final Authentication authentication)
                                     throws AuthenticationException {
  }
}

Force Spring Security to use the custom filter


<bean class="com.path.to.filter.ExtendedUsernamePasswordAuthenticationFilter" id="formAuthenticationFilter">
  <property name="authenticationManager" ref="authenticationManager"/>
</bean>

<http ... >
  <security:custom-filter position="FORM_LOGIN_FILTER" ref="formAuthenticationFilter"/>

  ...
</http>

or, if using Java configuration:

@Bean
public ExtendedUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter(final AuthenticationManager authenticationManager) {
  final ExtendedUsernamePasswordAuthenticationFilter filter = new ExtendedUsernamePasswordAuthenticationFilter();

  filter.setAuthenticationManager(authenticationManager);

  return filter;
}

protected void configure(HttpSecurity http) throws Exception {
  http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
      ...
}

Upvotes: 2

Related Questions