Summary

in china, some oauth provider's implementation is not standard enough for example wechat, it use appid instead of client_id and must add url hash #wechat_redirect can we add more customization for OAuth2AuthorizationRequest and OAuth2AuthorizationRequestResolver

Actual Behavior

Expected Behavior

Configuration

Version

Sample

Comment From: jgrandja

@okhowang You can customize the OAuth2AuthorizationRequest by supplying a custom OAuth2AuthorizationRequestResolver. See the reference doc for an example.

You can also customize the Token Request.

I'm going to close this issue as I feel I answered your question.

Comment From: okhowang

OAuth2AuthorizationRequestResolver has many common logic, for example pkcs and so on. i just want use OAuth2AuthorizationRequestResolver and customizer OAuth2AuthorizationRequest.Builder

Comment From: jgrandja

@okhowang I'm not sure I understand your request. Can you provide a more detailed explanation or even a code sample of what you would like to accomplish?

Comment From: okhowang

for example, when call with a standard oauth provider spring security will redirect to url like https://some.domain.com/authorize?client_id=foo&scope=bar&response_type=code&redirect_uri=https://some.url/ but in wechat it will be https://some.domain.com/authorize?appid=foo&scope=bar&response_type=code&redirect_uri=https://some.url/#wechat_redirect

of course, i can customize OAuth2AuthorizationRequestResolver to do this. but i also need other standard oauth provider I want reuse spring's OAuth2AuthorizationRequestResolver class, and just customize OAuth2AuthorizationRequest.Builder's logic which determine redirect url directly. I don't want modify any in OAuth2AuthorizationRequestResolver except OAuth2AuthorizationRequest.Builder

Comment From: jgrandja

Ok I understand now.

So let's say we add DefaultOAuth2AuthorizationRequestResolver.setAuthorizationRequestConsumer(Consumer<OAuth2AuthorizationRequest.Builder> builderConsumer). This will allow you to supply a Consumer and hook into the build process.

However, it still will not let you change the parameter names. You need to change client_id to appid but the Consumer will only allow you to override the parameter values. If you want to change the parameter names than you need to use the delegation-based strategy. Does this make sense or am I missing something?

Comment From: okhowang

final redirect uri is make by OAuth2AuthorizationRequest.Builder#buildAuthorizationRequestUri I write a builder which use a parameterNameMap to map oauth2 original parameterName to provider's parametersName now

    private fun set(map: MultiValueMap<String, String>, name: String, value: String) {
        map[parameterNameMap[name] ?: name] = value
    }
    private fun buildAuthorizationRequestUri(): String {
        val parameters = LinkedMultiValueMap<String, String>()
        set(parameters, OAuth2ParameterNames.RESPONSE_TYPE, this.responseType.value)
        set(parameters, OAuth2ParameterNames.CLIENT_ID, this.clientId)
        scopes?.let { set(parameters, OAuth2ParameterNames.SCOPE, it.joinToString(" ")) }
        state?.let { set(parameters, OAuth2ParameterNames.STATE, it) }
        redirectUri?.let { set(parameters, OAuth2ParameterNames.REDIRECT_URI, it) }
        additionalParameters?.let { it.forEach { (key, value) -> parameters.set(key, value.toString()) } }

        return UriComponentsBuilder.fromHttpUrl(this.authorizationUri)
            .queryParams(parameters)
            .fragment(clientRegistration.authorizationHash()) // add custom fragment
            .encode(StandardCharsets.UTF_8)
            .build()
            .toUriString()
    }

Comment From: jgrandja

@okhowang I just submitted #7748. Please see this test, which should handle your use case. Please let me know if this will work for you.

Comment From: okhowang

In parameters, it looks like ok but how to it for url segment, aka hash? or more? we may not need a Consumer for OAuth2AuthorizationRequest.Builder a interface for OAuth2AuthorizationRequest.Builder may be better? and we can set a customize implementation of RequestBuilder for DefaultOAuth2AuthorizationRequestResolver

Comment From: jgrandja

@okhowang

but how to it for url segment, aka hash?

You can update like this:

this.resolver.setAuthorizationRequestCustomizer(customizer -> {
    // TODO Append #wechat_redirect (Option 1)
    customizer.redirectUri(...);

    // TODO Append #wechat_redirect (Option 2)
    customizer.parameters(parameters -> {
        String redirectUri = (String) parameters.get(OAuth2ParameterNames.REDIRECT_URI);
        redirectUri += "#wechat_redirect";
        parameters.put(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
    });
});

a interface for OAuth2AuthorizationRequest.Builder may be better?

I'm not sure if an interface would be better. I believe a Consumer is simpler.

Do you see any limitations with the Consumer<OAuth2AuthorizationRequest.Builder> approach? If so, please provide specific details so I can understand and improve.

Comment From: okhowang

#wechat_redirect need be add on authorizationUri not redirectUri or builder add a hash optional like below

this.resolver.setAuthorizationRequestCustomizer(customizer -> {
    customizer.hash("wechat_redirect");
});

Comment From: jgrandja

I thought you need to add to redirect_uri?

but in wechat it will be https://some.domain.com/authorize?appid=foo&scope=bar&response_type=code&redirect_uri=https://some.url/#wechat_redirect

Comment From: okhowang

I'm sorry for bad case in wechat doc it's like https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

Comment From: jgrandja

ok so #wechat_redirect is appended at the end of URI. Let me see what I can come up with to support this.

Comment From: jgrandja

@okhowang Take a look at this test. This should fulfill your requirement. Let me know what you think.

Comment From: okhowang

LGTM

Comment From: qmmm61

请问你是如何在authorizationRequestCustomizer中区分标准oauth2 provider与非标准的oauth2 provider?

Comment From: okhowang

请问你是如何在authorizationRequestCustomizer中区分标准oauth2 provider与非标准的oauth2 provider?

这个是在OAuth2AuthorizationRequest构造之前就要在业务逻辑里自己确定,然后调用不同的Customizer处理

Comment From: NotFound403

@jgrandja i have done it,but something not good here,OAuth2AuthorizationRequest.Builder need to expose a method to get registrationId for ensure the right provider. this customizer must not executed when it is google or okta etc .

import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;
import org.springframework.web.util.UriBuilder;

import java.net.URI;
import java.util.Map;
import java.util.function.Consumer;

/**
 * customizer {@link OAuth2AuthorizationRequest}
 * <p>
 * client_id to appid ,add fragment wechat_redirect
 * @see   DefaultOAuth2AuthorizationRequestResolver#setAuthorizationRequestCustomizer(Consumer) 
 * @author felord.cn
 */
public class WechatOAuth2AuthorizationRequestCustomizer {
    private static final String WECHAT_APP_ID = "appid";
    private static final String WECHAT_FRAGMENT = "wechat_redirect";
    private final String wechatRegistrationId;

    public WechatOAuth2AuthorizationRequestCustomizer(String wechatRegistrationId) {
        Assert.notNull(wechatRegistrationId, "wechat registrationId flag must not be null");
        this.wechatRegistrationId = wechatRegistrationId;
    }


    public void customize(OAuth2AuthorizationRequest.Builder builder) {
           //todo  i  need  a  method  to  get registrationId   here      So that the following code logic can be executed
            builder.parameters(WechatOAuth2AuthorizationRequestCustomizer::wechatParametersConsumer);
            builder.authorizationRequestUri(WechatOAuth2AuthorizationRequestCustomizer::authorizationRequestUriFunction);

    }

    private static void wechatParametersConsumer(Map<String, Object> parameters) {
        //   client_id replace into appid here
        LinkedHashMap<String, Object> linkedParameters =  new LinkedHashMap<>();
        //  k v  must be ordered
        parameters.forEach((k,v)->{
          if (OAuth2ParameterNames.CLIENT_ID.equals(k)){
              linkedParameters.put(WECHAT_APP_ID,v);
          }else {
              linkedParameters.put(k,v);
          }
        });

        parameters.clear();
        parameters.putAll(linkedParameters);
    }

    private static URI authorizationRequestUriFunction(UriBuilder builder) {
        //  add  wechat fragment here 
        return builder.fragment(WECHAT_FRAGMENT).build();
    }
}