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