admin管理员组文章数量:1558098
相信你在使用微信的时候,打开某些网页时,经常会弹出一个“xxx申请获取你的信息,是否同意?”的提示。类似这样:
当你点击"同意"时,网页应用便能获取你微信的用户信息。这个流程也是通过OAuth2实现的。
在专栏第15篇 OAuth2登录中我们提到,OAuth2登录
的实现原理就是 Client获取用户授权,得到令牌,通过令牌获取用户信息(资源)。再在本地构建用户登录认证信息,维持用户会话状态,以此达到登录的目的。
同理,我们也可以借助微信OAuth2网页授权实现登录。在微信OAuth2网页授权中,我们的项目就相当于是Client,通过微信用户授权,最终取到用户信息,在本地构建用户登录认证信息,维持用户会话状态,达到登录的目的。
so,本文便尝试使用 SpringSecurity OAuth2 Client模块 接入 微信OAuth2网页授权,实现登录。
微信网页授权 接入文档:https://developers.weixin.qq/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
准备
- 微信网页授权需要使用 公众号,门槛较高,一般在开发阶段我们就使用测试号。
申请接口测试号(沙盒号) https://developers.weixin.qq/doc/offiaccount/Basic_Information/Requesting_an_API_Test_Account.html
由于用户体验和安全性方面的考虑,微信公众号的注册有一定门槛,某些高级接口的权限需要微信认证后才可以获取。
所以,为了帮助开发者快速了解和上手微信公众号开发,熟悉各个接口的调用,推出了微信公众帐号测试号,通过手机微信扫描二维码即可获得测试号。
得到测试号appID和appsecret,在后续配置中会用到。
接着在测试号页面配置下回调地址:
- 微信网页授权,需要在微信客户端(APP)打开对应的网页,而我们开发是在本地,APP怎么能访问我们本地的网页服务呢?
这里有两种方法,1.微信开发者工具,类似在本地运行APP。 2.本地穿透,将本地的服务暴露到外网,这需要通过特定软件实现。比如 Natapp。
本文使用微信开发者工具来进行测试。其下载地址为: https://developers.weixin.qq/doc/offiaccount/OA_Web_Apps/Web_Developer_Tools.html
为帮助开发者更方便、更安全地开发和调试基于微信的网页,推出了 web 开发者工具。它是一个桌面应用,通过模拟微信客户端的表现,使得开发者可以使用这个工具方便地在 PC 或者 Mac 上进行开发和调试工作。
实战开发
参看 微信网页授权文档
- 授权请求
https://open.weixin.qq/connect/oauth2/authorize
?appid=wx0fd9ac25fab159eb
&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080
&response_type=code
&state=001
&scope=snsapi_base#wechat_redirect
- code->token 请求
https://api.weixin.qq/sns/oauth2/access_token
?grant_type=authorization_code
&appid=wx0fd9ac25fab159eb
&secret=2d128f682d3fd525a9dc4d6ce755a39d
&code=061EhN000PhwPO1ctC000pawTC3EhN0x
- token->userinfo 请求
https://api.weixin.qq/sns/userinfo
?access_token=62_s0SK3CpWAHn1n4DqUUFJKqfUkYYmX62TI5DBrmRjHg3jeNeP-T_gOhw_uW9RSElNJKLILcoTi_IxkL6sR3ES7vRPx4aBOGz6aoY_StMfwtg
&openid=oQHEX6mZRvnrWVWg8EmvNhCkhWq8
&lang=zh_CN
可以看出,微信OAuth2网页授权和标准OAuth2有很多出入,比如client_id 变成了appid,client_secret变成了secret,还有响应内容不同等等差别,这些差异致使我们无法直接使用 SpringSecurity OAuth2 Client 模块,所以我们需要对原有的实现进行改造兼容。
搭建示例
我们在 专栏第15篇 OAuth2登录 中示例的基础上,进行改造兼容。
- 常量类
public interface WechatConstants {
/**
* 配置文件中的 registration id
*/
String REG_ID = "weixin-app";
String PARAM_APP_ID = "appid";
String PARAM_SECRET = "secret";
String PARAM_SUFFIX = "#wechat_redirect";
String PARAM_OPENID = "openid";
String PARAM_LANG = "lang";
}
- 授权请求的参数处理器。兼容微信网页授权
public class WeiXinOAuth2AuthorizationRequestCustomizer implements Consumer<OAuth2AuthorizationRequest.Builder> {
@Override
public void accept(OAuth2AuthorizationRequest.Builder builder) {
builder.authorizationRequestUri(CustomUriFunction.INS);
}
private static class CustomUriFunction implements Function<UriBuilder, URI> {
private static final CustomUriFunction INS = new CustomUriFunction();
@Override
public URI apply(UriBuilder uriBuilder) {
URI uri = uriBuilder.build();
String query = uri.getQuery();
if(query.contains(WechatConstants.REG_ID)) {
// 特殊处理 weixin 的授权请求。
// 将 client_id 改为 appid
// url 末尾增加 #wechat_redirect
String reqUri = uri.toString()
.replaceAll(OAuth2ParameterNames.CLIENT_ID, WechatConstants.PARAM_APP_ID)
.concat(WechatConstants.PARAM_SUFFIX);
uri = URI.create(reqUri);
}
return uri;
}
}
}
- code->token 请求的参数处理器。兼容微信网页授权
public class WeiXinOAuth2AuthorizationCodeGrantRequestEntityConverter extends OAuth2AuthorizationCodeGrantRequestEntityConverter {
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
if(WechatConstants.REG_ID.equals(clientRegistration.getRegistrationId())){
// 微信的请求特殊处理
MultiValueMap<String, String> queryParameters = this.buildWechatQueryParameters(authorizationCodeGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri())
.queryParams(queryParameters)
.build()
.toUri();
return RequestEntity.get(uri).build();
}
return super.convert(authorizationCodeGrantRequest);
}
private MultiValueMap<String, String> buildWechatQueryParameters(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue());
parameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
// 微信特定参数:appid、secret
parameters.add(WechatConstants.PARAM_APP_ID, clientRegistration.getClientId());
parameters.add(WechatConstants.PARAM_SECRET, clientRegistration.getClientSecret());
return parameters;
}
}
- code->token 请求的响应处理器。兼容微信网页授权
public class WeiXinMapOAuth2AccessTokenResponseConverter implements Converter<Map<String, String>, OAuth2AccessTokenResponse> {
private static final Converter<Map<String, String>, OAuth2AccessTokenResponse> DELEGATE = new MapOAuth2AccessTokenResponseConverter();
@Override
public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
// 兼容微信响应中没有 tokenType 值
if(tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE) == null){
tokenResponseParameters.put(OAuth2ParameterNames.TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue());
}
return DELEGATE.convert(tokenResponseParameters);
}
}
- token->userinfo 请求的参数处理器。兼容微信网页授权
public class WeiXinOAuth2UserRequestEntityConverter extends OAuth2UserRequestEntityConverter{
@Override
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
ClientRegistration clientRegistration = userRequest.getClientRegistration();
if(WechatConstants.REG_ID.equals(clientRegistration.getRegistrationId())){
// 微信的请求特殊处理
MultiValueMap<String, String> queryParameters = this.buildWechatQueryParameters(userRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.queryParams(queryParameters)
.build()
.toUri();
return RequestEntity.get(uri).build();
}
return super.convert(userRequest);
}
private MultiValueMap<String, String> buildWechatQueryParameters(OAuth2UserRequest userRequest) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add(OAuth2ParameterNames.ACCESS_TOKEN, userRequest.getAccessToken().getTokenValue());
// 微信特定参数:openid、lang
parameters.add(WechatConstants.PARAM_OPENID, userRequest.getAdditionalParameters().get(WechatConstants.PARAM_OPENID).toString());
parameters.add(WechatConstants.PARAM_LANG, "zh_CN");
return parameters;
}
}
- 改造原 SecurityConfiguration 的 configure 方法
上述的兼容性改造类,最终都需要配置进对应的调用类才能生效。配置方法如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
// 支持 OAuth2 登录
.oauth2Login()
.authorizationEndpoint(t -> {
DefaultOAuth2AuthorizationRequestResolver requestResolver = new DefaultOAuth2AuthorizationRequestResolver(
getClientRegistrationRepository(http), OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
// 授权请求的自定义处理逻辑!
requestResolver.setAuthorizationRequestCustomizer(new WeiXinOAuth2AuthorizationRequestCustomizer());
t.authorizationRequestResolver(requestResolver);
})
.tokenEndpoint(t -> {
DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
// token请求的自定义处理逻辑!
tokenResponseClient.setRequestEntityConverter(new WeiXinOAuth2AuthorizationCodeGrantRequestEntityConverter());
OAuth2AccessTokenResponseHttpMessageConverter converter = new OAuth2AccessTokenResponseHttpMessageConverter();
// 兼容 微信的 text/plain 响应类型
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, new MediaType("application", "*+json")));
// 兼容 微信的 响应内容
converter.setTokenResponseConverter(new WeiXinMapOAuth2AccessTokenResponseConverter());
RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), converter));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
tokenResponseClient.setRestOperations(restTemplate);
t.accessTokenResponseClient(tokenResponseClient);
})
.userInfoEndpoint(t -> {
DefaultOAuth2UserService userService = new DefaultOAuth2UserService();
// userinfo请求的自定义处理逻辑!
userService.setRequestEntityConverter(new WeiXinOAuth2UserRequestEntityConverter());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 兼容 微信的 text/plain 响应类型
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, new MediaType("application", "*+json")));
RestTemplate restTemplate = new RestTemplate(Arrays.asList(converter));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
userService.setRestOperations(restTemplate);
t.userService(userService);
})
.and()
.csrf().disable()
;
}
- application.yml
server:
port: 8080
spring:
security:
oauth2:
client:
registration: # 定义应用信息
weixin-app:
clientName: 微信帐号登录
client-id: xxx #测试号的appid
client-secret: xxx #测试号的secret
clientAuthenticationMethod: basic
authorizationGrantType: authorization_code
redirectUri: 'http://127.0.0.1:8080/login/oauth2/code/{registrationId}'
scope: snsapi_userinfo
provider: weixin
provider: # 定义授权服务器信息
weixin:
authorizationUri: https://open.weixin.qq/connect/oauth2/authorize
tokenUri: https://api.weixin.qq/sns/oauth2/access_token
userInfoUri: https://api.weixin.qq/sns/userinfo
userNameAttribute: nickname
ok,搞定。启动服务。
测试
-
打开 微信开发者工具,登录,点击 公众号网页
-
在地址栏输入
http://localhost:8080
,点击 微信帐号登录
-
点击同意
-
登录成功
ok,大功告成。我们成功利用 微信网页授权 实现我们项目的登录。
reference:
集成微信公众号OAuth2.0授权
整合企业微信扫码登录
什么是内网穿透?
NATAPP1分钟快速新手图文教程
用Natapp(ngrok)进行微信本地开发调试
本文标签:
版权声明:本文标题:17 微信OAuth2授权登录 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dianzi/1727388340a1112469.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论