I have been trying to add a request parameter to the AuthorizationCode request, spring oauth2 filter makes to google, as part of the oauth2 authentication flow. Specifically, I need to add a login_hint parameter to prevent google from directing users to pick their accounts when the email address is already known.
This is my initial configuration:
@Configuration
@EnableOAuth2Client
@RequiredArgsConstructor
public class OAuthSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String LOGIN_PATH = "/oauth/login";
private static final int OAUTH2_CLIENT_FILTER_ORDER = -100;
static {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
private final OAuth2ClientContext oauth2ClientContext;
private final OAuth2ClientContextFilter oAuth2ClientContextFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
final OAuth2ClientAuthenticationProcessingFilter ssoFilter = new OAuth2ClientAuthenticationProcessingFilter(LOGIN_PATH);
ssoFilter.setRestTemplate(googleRestTemplate());
ssoFilter.setTokenServices(tokenServices());
ssoFilter.setAuthenticationManager(oAuth2AuthenticationManager());
final OAuth2AuthenticationProcessingFilter clientFilter = new OAuth2AuthenticationProcessingFilter();
clientFilter.setAuthenticationManager(oAuth2AuthenticationManager());
clientFilter.setStateless(false);
// @formatter:off
http
.csrf().disable()
.cors().disable()
.headers()
.frameOptions().sameOrigin()
.cacheControl().disable()
.and()
.antMatcher("/**")
.csrf().disable()
.httpBasic().disable()
.rememberMe().disable()
.addFilterBefore(ssoFilter, BasicAuthenticationFilter.class)
.addFilterBefore(clientFilter, OAuth2ClientAuthenticationProcessingFilter.class)
.authorizeRequests()
.antMatchers("/api/**").fullyAuthenticated()
.anyRequest().permitAll()
.and()
.logout().logoutUrl("/logout")
.and() .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());
// @formatter:on
}
The Only way I managed to do this is as follows:
@Bean
public OAuth2RestTemplate googleRestTemplate() {
MyAuthorizationCodeAccessTokenProvider myAuthorizationCodeAccessTokenProvider =
new MyAuthorizationCodeAccessTokenProvider();
AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(
Arrays.<AccessTokenProvider>asList(
myAuthorizationCodeAccessTokenProvider, new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(googleClient(), oauth2ClientContext);
oAuth2RestTemplate.setAccessTokenProvider(accessTokenProvider);
return oAuth2RestTemplate;
}
static class MyAuthorizationCodeAccessTokenProvider extends AuthorizationCodeAccessTokenProvider {
private static String EMAIL_PARAM_NAME = "email";
@Override
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException,
OAuth2AccessDeniedException {
try {
return super.obtainAccessToken(details, request);
} catch (UserRedirectRequiredException ex) {
String email = request.containsKey(EMAIL_PARAM_NAME) ? request.get(EMAIL_PARAM_NAME).get(0) : null;
if (email != null) {
ex.getRequestParams().put("login_hint", email);
}
throw ex;
}
}
}
}
Is this the best way to customize the spring oauth2 implementation to set the login_hint parameter on the authorization request?
I needed to solve the exact same problem with Okta as the IdP. I know this is an old post, but my post answers the question with a general approach that can be used for any additional parameters (not just login_hint) that need to be appended to the authorization URL. I think my answer fits well into how Spring expects developers to solve this. My main resource for a solution is found here (https://github.com/spring-projects/spring-security/issues/5244). Another good resource is here (https://github.com/spring-projects/spring-security/issues/5521). Here's how it works: Spring uses a filter called OAuth2AuthorizationRequestRedirectFilter which is responsible for building the OAuth2 authorization request. The authorization request (OAuth2AuthorizationRequest) contains the authorization request URI. The authorization request URI needs to be altered by adding additional parameters (i.e. login_hint). Spring uses an OAuth2AuthorizationRequestResolver implementation that creates the OAuth2AuthorizationRequest. By providing your own OAuth2AuthorizationRequestResolver, you can add additional parameters to the authorization URL. So for my implementation, I created a class called ConfigurableOAuth2AuthorizationRequestResolver that wraps Spring's DefaultOAuth2AuthorizationRequestResolver. Here is some of my code.
// Credit to Joe Grandja for providing test cases found here:
// https://raw.githubusercontent.com/spring-projects/spring-security/779597af2a6ed777707f08ae8106818e0b8e299e/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java
// Much of the below code comes from his test examples.
public class ConfigurableOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver
{
private final OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
/**
* Wraps a OAuth2AuthorizationRequestResolver so that calls to the resolve method can be delegated.
* In our implementation, the value passed here will be a DefaultOAuth2AuthorizationRequestResolver object.
* @param oAuth2AuthorizationRequestResolver usually the default resolver from Spring.
*/
public ConfigurableOAuth2AuthorizationRequestResolver(OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver)
{
this.oAuth2AuthorizationRequestResolver = oAuth2AuthorizationRequestResolver;
}
/**
* Adds our custom code to check for extra parameters in the session. We use the session because there
* are several redirects to the browser causing any request variable to be lost. Don't really like
* storing anything in the HTTP session but OK as long as it is removed immediately after use.
*
* @param request needed for resolve method delegation and to retrieve the HTTP session.
* @return the <code>OAuth2AuthorizationRequest</code>
*/
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request)
{
OAuth2AuthorizationRequest authorizationRequest = this.oAuth2AuthorizationRequestResolver.resolve(request);
return processAdditionalParameters(request, authorizationRequest);
}
/**
* Required method for implementation but not used in the standard use case.
*
* @param request needed for resolve method delegation and to retrieve the HTTP session.
* @param clientRegistrationId (e. g. google, okta)
* @return the <code>OAuth2AuthorizationRequest</code>
*/
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId)
{
OAuth2AuthorizationRequest authorizationRequest = this.oAuth2AuthorizationRequestResolver.resolve(request, clientRegistrationId);
return processAdditionalParameters(request, authorizationRequest);
}
/**
* This method does the important task of appending any special query string parameters to the authorization
* request. For now, we are only looking for login_hint in the session. This method can be changed to
* support more parameters. We expect the login_hint key to be found in the session.
*
* @param request needed to retrieve the HTTP session.
* @param authorizationRequest can be null as a valid scenario. Not null when registrationId matches Okta (or whatever).
* @return the <code>OAuth2AuthorizationRequest</code>
*/
private OAuth2AuthorizationRequest processAdditionalParameters(HttpServletRequest request, OAuth2AuthorizationRequest authorizationRequest)
{
if (authorizationRequest == null)
{
return null;
}
// NOTE: this can be improved to support multiple parameters by storing a list instead of a single param
Map<String, Object> additionalParameters = new HashMap<>(authorizationRequest.getAdditionalParameters());
HttpSession session = request.getSession();
additionalParameters.put(SSO_OAUTH2_AUTH_PARAM_LOGIN_HINT, session.getAttribute(SSO_OAUTH2_AUTH_PARAM_LOGIN_HINT));
session.removeAttribute(SSO_OAUTH2_AUTH_PARAM_LOGIN_HINT); // remove immediately after use
String customAuthorizationRequestUri = UriComponentsBuilder
.fromUriString(authorizationRequest.getAuthorizationRequestUri())
.queryParam(SSO_OAUTH2_AUTH_PARAM_LOGIN_HINT, additionalParameters.get(SSO_OAUTH2_AUTH_PARAM_LOGIN_HINT))
.build(true).toUriString();
return OAuth2AuthorizationRequest.from(authorizationRequest)
.additionalParameters(additionalParameters)
.authorizationRequestUri(customAuthorizationRequestUri)
.build();
}
}
The next bit of code shows how to integrate your resolver instead of Spring's default.
@Override
protected void configure(HttpSecurity http) throws Exception
{
logger.info("Configuring OAuth/OIDC HTTP security ...");
http
.oauth2Login()
.authorizationEndpoint()
.authorizationRequestResolver(this.authorizationRequestResolver()) // adds custom resolver
}
@Bean
public OAuth2AuthorizationRequestResolver authorizationRequestResolver()
{
OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(
yourClientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
return new ConfigurableOAuth2AuthorizationRequestResolver(defaultAuthorizationRequestResolver);
}
Is this the "best" approach, IDK. As of 5.1, this is the accepted approach IMHO.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With