-
Notifications
You must be signed in to change notification settings - Fork 41.5k
Description
Recently, I have tried to make my application as wechat OAuth 2.0 client like GitHub, Google, etc.
But the wechat not strict follows the specification, so the following configuration does not work.
spring:
oauth2.client:
provider:
wechat:
authorization-uri: https://open.weixin.qq.com/connect/oauth2/authorize
token-uri: https://api.weixin.qq.com/sns/oauth2/access_token
user-info-authentication-method: query
user-name-attribute: nickname
That because wechat requires the parameters' order of authorization request URL strictly follows their requirement (for security reason even if there is no any helps.)
To make it works, I have to customize the URL building in org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest$Builder
, but there is no convenient and graceful way to do it.
As a workaround, I added additional properties on the provider, then using javassist
to instrument buildAuthorizationRequestUri
method.
A sample of modified properties.
spring:
security:
oauth2.client:
provider:
wechat:
authorization-uri: https://open.weixin.qq.com/connect/oauth2/authorize
token-uri: https://api.weixin.qq.com/sns/oauth2/access_token
user-info-authentication-method: query
user-name-attribute: nickname
nonstandard:
client-id-parameter: appid
ordered-parameters: appid,redirect_uri,response_type,scope,state
fragment: wechat_redirect
A javassist
sample for how to instrument the private method.
/**
* @see OAuth2AuthorizationInstrumentRunListener#instrumentOAuth2AuthorizationRequestBuilder()
*/
public class OAuth2AuthorizationRequestBuilder {
private String authorizationUri;
private AuthorizationGrantType authorizationGrantType;
private OAuth2AuthorizationResponseType responseType;
private String clientId;
private String redirectUri;
private Set<String> scopes;
private String state;
private Map<String, Object> additionalParameters;
private String authorizationRequestUri;
public String buildAuthorizationRequestUri() {
NonstandardOAuth2ClientProperties nonstandardProperties = NonstandardOAuth2ClientProperties.getInstance();
checkState(nonstandardProperties != null, "NonstandardOAuth2ClientProperties not initialized.");
String registrationId = (String) this.additionalParameters.get(OAuth2ParameterNames.REGISTRATION_ID);
checkState(!isNullOrEmpty(registrationId), "Can not detect the OAuth 2.0 client registration ID.");
NonstandardOAuth2ClientProperties.NonstandardProvider nonstandardProvider = nonstandardProperties.getProvider(registrationId);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, this.responseType.getValue());
String clientIdParameterName = isNullOrEmpty(nonstandardProvider.getClientIdParameter()) ?
OAuth2ParameterNames.CLIENT_ID : nonstandardProvider.getClientIdParameter();
parameters.set(clientIdParameterName, this.clientId);
if (!CollectionUtils.isEmpty(this.scopes)) {
parameters.set(OAuth2ParameterNames.SCOPE,
StringUtils.collectionToDelimitedString(this.scopes, " "));
}
if (this.state != null) {
parameters.set(OAuth2ParameterNames.STATE, this.state);
}
if (this.redirectUri != null) {
parameters.set(OAuth2ParameterNames.REDIRECT_URI, this.redirectUri);
}
if (!CollectionUtils.isEmpty(this.additionalParameters)) {
// Do not using the lambda expression because of the javassist bug.
// https://github.com/jboss-javassist/javassist/issues/262
Set<Map.Entry<String, Object>> entries = this.additionalParameters.entrySet();
for (Map.Entry<String, Object> entry : entries) {
String key = entry.getKey();
if (!key.equals(OAuth2ParameterNames.REGISTRATION_ID)) {
parameters.set(key, entry.getValue().toString());
}
}
}
// Re-order the parameters
List<String> orderedKeys = nonstandardProvider.getOrdered();
Set<String> parameterKeys = new LinkedHashSet<>(parameters.size());
parameterKeys.addAll(orderedKeys);
parameterKeys.addAll(parameters.keySet());
MultiValueMap<String, String> orderedParameters = new LinkedMultiValueMap<>();
for (String parameterKey : parameterKeys) {
orderedParameters.set(parameterKey, parameters.getFirst(parameterKey));
}
return UriComponentsBuilder.fromHttpUrl(this.authorizationUri)
.queryParams(orderedParameters)
.fragment(nonstandardProvider.getFragment())
.encode(StandardCharsets.UTF_8)
.build()
.toUriString();
}
}
// http://www.labouisse.com/quicky/2015/09/23/javassisting-spring-boot
public void instrumentOAuth2AuthorizationRequestBuilder() {
try {
String classname = "org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest$Builder";
String replacerClassname = "com.github.zhangyanwei.wechat.wcms.instrument.replacer.OAuth2AuthorizationRequestBuilder";
String methodName = "buildAuthorizationRequestUri";
ClassPool cp = ClassPool.getDefault();
cp.appendClassPath(new LoaderClassPath(application.getClassLoader()));
// Find the target method.
CtClass cc = cp.get(classname);
CtMethod cm = cc.getDeclaredMethod(methodName);
// Replace with the template method.
CtClass rcc = cp.get(replacerClassname);
CtMethod rcm = rcc.getDeclaredMethod(methodName);
cm.setBody(rcm, null);
cc.toClass();
cc.detach();
rcc.detach();
} catch (NotFoundException | CannotCompileException e) {
throw new RuntimeException(e);
}
}