Search code examples
spring-securityspring-authorization-server

How to pass the clientId the user is authenticating with using a custom AuthenticationProvider in Spring Authorization Server?


I am using Spring Authorization Server and implementing a custom AuthenticationProvider such as following when a user logs-in:

@Component
public class AuthenticationCallout implements AuthenticationProvider {
    private static final Logger LOG = LoggerFactory.getLogger(AuthenticationCallout.class);

    @Autowired
    private JpaOAuth2AuthorizationService jpaOAuth2AuthorizationService;

    private WebClient.Builder webClientBuilder;

    public AuthenticationCallout(WebClient.Builder webClientBuilder) {
        this.webClientBuilder = webClientBuilder;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        LOG.info("authenticate with username and password");

        final String name = authentication.getName();
        final String password = authentication.getCredentials().toString();


        LOG.info("authorities: {}, details: {}, credentials: {}", authentication.getAuthorities(),
                authentication.getDetails(), authentication.getCredentials());

        if (name.equals("admin") && password.equals("system")) {
            final List<GrantedAuthority> grantedAuths = new ArrayList<>();
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
            final UserDetails principal = new User(name, password, grantedAuths);
            LOG.info("returning using custom authenticator");
            final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
            return auth;
        } else {
            return null;
        }
    }

I also want to get the clientId with which this user is logging-in with. What is the best way to get the clientId? In this custom authentication I will be making a webclient call to another service that stores the user-password with the clientId for the system.


Solution

  • There are potentially quite a few ways to tackle this. For example, adding your own Filter to the Spring Security filter chain gives you direct access to the HttpServletRequest and HttpServletResponse. However, you can also access this through Spring's RequestContextHolder in a non-filter component.

    The simplest way to start would be to implement the UserDetailsService and access the SavedRequest used to initiate the flow via the RequestCache, like this:

    @Configuration
    public class SecurityConfig {
    
        // ...
    
        @Bean
        public RequestCache requestCache() {
            return new HttpSessionRequestCache();
        }
    
        @Bean
        public UserDetailsService userDetailsService(RequestCache requestCache) {
            return (username) -> {
                var clientId = getClientId(requestCache);
                // ...
            };
        }
    
        private static String getClientId(RequestCache requestCache) {
            var requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            var request = requestAttributes.getRequest();
            var response = requestAttributes.getResponse();
            var savedRequest = requestCache.getRequest(request, response);
            return getParameter(savedRequest, OAuth2ParameterNames.CLIENT_ID);
        }
    
        private static String getParameter(SavedRequest savedRequest, String parameterName) {
            var parameterValues = savedRequest.getParameterValues(parameterName);
            if (parameterValues.length != 1) {
                throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
            }
            return parameterValues[0];
        }
    
    }
    

    The UserDetailsService can then look up the user from the external service. This requires the hashed password to be part of the response from the microservice, so the password can be validated locally by the DaoAuthenticationProvider.