赞
踩
微信登陆和QQ登陆大致流程一致,只是有些api不一样,主要是QQ的getUserInfo微信多了一个参数openId。这是因为微信互联文档中在OAuth2.0的认证流程示意图第五步时,微信的openid 同access_token一起返回。而Spring Social获取access_token的类AccessGrant.java中没有openid
微信暂时没有申请测试账户,先上代码后续网站备案下来再自行测试
这里也分三个模块进行开发 api connect config
- package com.rui.tiger.auth.core.social.wechat.api;
-
- /**
- * 微信用户api接口
- * @author CaiRui
- * @Date 2019-01-12 12:08
- */
- public interface WechatApi {
- /**
- * 获取微信用户信息
- * @param openId
- * @return
- */
- WechatUserInfo getUserInfo(String openId);
- }
- package com.rui.tiger.auth.core.social.wechat.api;
-
- import com.alibaba.fastjson.JSON;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang.StringUtils;
- import org.springframework.http.converter.HttpMessageConverter;
- import org.springframework.http.converter.StringHttpMessageConverter;
- import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
- import org.springframework.social.oauth2.TokenStrategy;
-
- import java.nio.charset.Charset;
- import java.util.List;
-
- /**
- * @author CaiRui
- * @Date 2019-01-12 12:24
- */
- @Slf4j
- public class WechatApiImpl extends AbstractOAuth2ApiBinding implements WechatApi {
-
- /**
- * 获取用户信息的url
- * https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
- * access_token 父类会帮我们拼接
- */
- private static final String URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid=";
-
- /**
- * @param accessToken
- */
- public WechatApiImpl(String accessToken) {
- super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
- }
-
- /**
- * 默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。
- */
- protected List<HttpMessageConverter<?>> getMessageConverters() {
- List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
- messageConverters.remove(0);
- messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
- return messageConverters;
- }
-
- @Override
- public WechatUserInfo getUserInfo(String openId) {
- String url = URL_GET_USER_INFO + openId;
- String response = getRestTemplate().getForObject(url, String.class);
- if(StringUtils.contains(response, "errcode")) {
- log.info("微信用户信息获取失败:"+response);
- return null;
- }
- WechatUserInfo profile = null;
- try {
- profile = JSON.parseObject(response, WechatUserInfo.class);
- } catch (Exception e) {
- log.info("微信用户信息json转换异常",e);
- }
- return profile;
- }
- }
微信社交用户信息封装
- package com.rui.tiger.auth.core.social.wechat.api;
-
- import lombok.Data;
-
- /**
- * 微信用户信息
- * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316518&token=&lang=zh_CN}
- * @author CaiRui
- * @Date 2019-01-12 12:13
- */
- @Data
- public class WechatUserInfo {
-
- /**
- * 普通用户的标识,对当前开发者帐号唯一
- */
- private String openid;
- /**
- * 普通用户昵称
- */
- private String nickname;
- /**
- * 语言
- */
- private String language;
- /**
- * 普通用户性别,1为男性,2为女性
- */
- private String sex;
- /**
- * 普通用户个人资料填写的省份
- */
- private String province;
- /**
- * 普通用户个人资料填写的城市
- */
- private String city;
- /**
- * 国家,如中国为CN
- */
- private String country;
- /**
- * 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
- */
- private String headimgurl;
- /**
- * 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
- */
- private String[] privilege;
- /**
- * 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
- */
- private String unionid;
-
- }
微信的返回比标准的多了个openId
- package com.rui.tiger.auth.core.social.wechat.connect;
-
- import org.springframework.social.oauth2.AccessGrant;
-
- /**
- * 微信的access_token信息。与标准OAuth2协议不同,微信在获取access_token时会同时返回openId,并没有单独的通过accessToke换取openId的服务
- * 所以在这里继承了标准AccessGrant,添加了openId字段,作为对微信access_token信息的封装。
- * @author CaiRui
- * @Date 2019-01-12 13:08
- */
- public class WechatAccessGrant extends AccessGrant {
-
- private String openId;
-
- public WechatAccessGrant() {
- super("");
- }
-
- public WechatAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {
- super(accessToken, scope, refreshToken, expiresIn);
- }
-
- /**
- * @return the openId
- */
- public String getOpenId() {
- return openId;
- }
-
- /**
- * @param openId the openId to set
- */
- public void setOpenId(String openId) {
- this.openId = openId;
- }
- }
api适配器
- package com.rui.tiger.auth.core.social.wechat.connect;
-
- import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
- import com.rui.tiger.auth.core.social.wechat.api.WechatUserInfo;
- import org.springframework.social.connect.ApiAdapter;
- import org.springframework.social.connect.ConnectionValues;
- import org.springframework.social.connect.UserProfile;
-
- /**
- * 微信 api适配器,将微信 api的数据模型转为spring social的标准模型。
- * @author CaiRui
- * @Date 2019-01-12 13:34
- */
- public class WechatApiAdapter implements ApiAdapter<WechatApi> {
- private String openId;
-
- public WechatApiAdapter() {}
-
- public WechatApiAdapter(String openId){
- this.openId = openId;
- }
-
- /**
- * @param api
- * @return
- */
- @Override
- public boolean test(WechatApi api) {
- return true;
- }
-
- /**
- * @param api
- * @param values
- */
- @Override
- public void setConnectionValues(WechatApi api, ConnectionValues values) {
- WechatUserInfo profile = api.getUserInfo(openId);
- values.setProviderUserId(profile.getOpenid());
- values.setDisplayName(profile.getNickname());
- values.setImageUrl(profile.getHeadimgurl());
- }
-
- /**
- * @param api
- * @return
- */
- @Override
- public UserProfile fetchUserProfile(WechatApi api) {
- return null;
- }
-
- /**
- * @param api
- * @param message
- */
- @Override
- public void updateStatus(WechatApi api, String message) {
- //do nothing
- }
- }
- package com.rui.tiger.auth.core.social.wechat.connect;
-
- import com.alibaba.fastjson.JSON;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.collections.MapUtils;
- import org.apache.commons.lang.StringUtils;
- import org.springframework.http.converter.StringHttpMessageConverter;
- import org.springframework.social.oauth2.AccessGrant;
- import org.springframework.social.oauth2.OAuth2Parameters;
- import org.springframework.social.oauth2.OAuth2Template;
- import org.springframework.util.MultiValueMap;
- import org.springframework.web.client.RestTemplate;
-
- import java.nio.charset.Charset;
- import java.util.Map;
-
- /**
- * 完成微信的OAuth2认证流程的模板类。国内厂商实现的OAuth2每个都不同,
- * spring默认提供的OAuth2Template适应不了,只能针对每个厂商自己微调。
- * @author CaiRui
- * @Date 2019-01-12 13:23
- */
- @Slf4j
- public class WechatOAuth2Template extends OAuth2Template {
-
- private String clientId;
-
- private String clientSecret;
-
- private String accessTokenUrl;
- /**
- * 获取token
- * https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
- */
- private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
-
- public WechatOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
- super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
- setUseParametersForClientAuthentication(true);
- this.clientId = clientId;
- this.clientSecret = clientSecret;
- this.accessTokenUrl = accessTokenUrl;
- }
-
- /* (non-Javadoc)
- * @see org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(java.lang.String, java.lang.String, org.springframework.util.MultiValueMap)
- */
- @Override
- public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,
- MultiValueMap<String, String> parameters) {
-
- StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);
-
- accessTokenRequestUrl.append("?appid="+clientId);
- accessTokenRequestUrl.append("&secret="+clientSecret);
- accessTokenRequestUrl.append("&code="+authorizationCode);
- accessTokenRequestUrl.append("&grant_type=authorization_code");
- accessTokenRequestUrl.append("&redirect_uri="+redirectUri);
-
- return getAccessToken(accessTokenRequestUrl);
- }
-
- public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
-
- StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
-
- refreshTokenUrl.append("?appid="+clientId);
- refreshTokenUrl.append("&grant_type=refresh_token");
- refreshTokenUrl.append("&refresh_token="+refreshToken);
-
- return getAccessToken(refreshTokenUrl);
- }
-
- @SuppressWarnings("unchecked")
- private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
-
- log.info("获取access_token, 请求URL: "+accessTokenRequestUrl.toString());
-
- String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
-
- log.info("获取access_token, 响应内容: "+response);
-
- Map<String, Object> result = null;
- try {
- result = JSON.parseObject(response, Map.class);
- } catch (Exception e) {
- log.error("微信获取token解析json异常",e);
- }
-
- //返回错误码时直接返回空
- if(StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))){
- String errcode = MapUtils.getString(result, "errcode");
- String errmsg = MapUtils.getString(result, "errmsg");
- throw new RuntimeException("获取access token失败, errcode:"+errcode+", errmsg:"+errmsg);
- }
-
- WechatAccessGrant accessToken = new WechatAccessGrant(
- MapUtils.getString(result, "access_token"),
- MapUtils.getString(result, "scope"),
- MapUtils.getString(result, "refresh_token"),
- MapUtils.getLong(result, "expires_in"));
-
- accessToken.setOpenId(MapUtils.getString(result, "openid"));
-
- return accessToken;
- }
-
- /**
- * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
- * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN
- */
- public String buildAuthenticateUrl(OAuth2Parameters parameters) {
- String url = super.buildAuthenticateUrl(parameters);
- url=url.replace("client_id","appid");
- //url = url + "&appid="+clientId+"&scope=snsapi_login";
- url = url + "&scope=snsapi_login";
- log.info("微信获取授权码地址url:"+url);
- return url;
- }
-
- public String buildAuthorizeUrl(OAuth2Parameters parameters) {
- return buildAuthenticateUrl(parameters);
- }
-
- /**
- * 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。
- */
- protected RestTemplate createRestTemplate() {
- RestTemplate restTemplate = super.createRestTemplate();
- restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
- return restTemplate;
- }
-
- }
- package com.rui.tiger.auth.core.social.wechat.connect;
-
- import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
- import com.rui.tiger.auth.core.social.wechat.api.WechatApiImpl;
- import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
-
- /**
- * 微信的OAuth2流程处理器的提供器,供spring social的connect体系调用
- * @author CaiRui
- * @Date 2019-01-12 13:40
- */
- public class WechatServiceProvider extends AbstractOAuth2ServiceProvider<WechatApi> {
-
- /**
- * 微信获取授权码的url
- *
- * https://open.weixin.qq.com/connect/qrconnect?
- * appid=APPID&
- * redirect_uri=REDIRECT_URI&
- * response_type=code&
- * scope=SCOPE&
- * state=STATE#wechat_redirect
- */
- private static final String URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect";
- /**
- * 微信获取accessToken的url
- */
- private static final String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
-
- /**
- * @param appId
- * @param appSecret
- */
- public WechatServiceProvider(String appId, String appSecret) {
- super(new WechatOAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN));
- }
-
-
- /* (non-Javadoc)
- * @see org.springframework.social.oauth2.AbstractOAuth2ServiceProvider#getApi(java.lang.String)
- */
- @Override
- public WechatApi getApi(String accessToken) {
- return new WechatApiImpl(accessToken);
- }
-
- }
- package com.rui.tiger.auth.core.social.wechat.connect;
-
- import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
- import org.springframework.social.connect.ApiAdapter;
- import org.springframework.social.connect.Connection;
- import org.springframework.social.connect.ConnectionData;
- import org.springframework.social.connect.support.OAuth2Connection;
- import org.springframework.social.connect.support.OAuth2ConnectionFactory;
- import org.springframework.social.oauth2.AccessGrant;
- import org.springframework.social.oauth2.OAuth2ServiceProvider;
-
- /**
- * @author CaiRui
- * @Date 2019-01-12 13:32
- */
- public class WechatConnectionFactory extends OAuth2ConnectionFactory<WechatApi> {
-
- /**
- * @param appId
- * @param appSecret
- */
- public WechatConnectionFactory(String providerId, String appId, String appSecret) {
- super(providerId, new WechatServiceProvider(appId, appSecret), new WechatApiAdapter());
- }
-
- /**
- * 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
- */
- @Override
- protected String extractProviderUserId(AccessGrant accessGrant) {
- if(accessGrant instanceof WechatAccessGrant) {
- return ((WechatAccessGrant)accessGrant).getOpenId();
- }
- return null;
- }
-
- /* (non-Javadoc)
- * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.oauth2.AccessGrant)
- */
- public Connection<WechatApi> createConnection(AccessGrant accessGrant) {
- return new OAuth2Connection<WechatApi>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(),
- accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));
- }
-
- /* (non-Javadoc)
- * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.connect.ConnectionData)
- */
- public Connection<WechatApi> createConnection(ConnectionData data) {
- return new OAuth2Connection<WechatApi>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
- }
-
- private ApiAdapter<WechatApi> getApiAdapter(String providerUserId) {
- return new WechatApiAdapter(providerUserId);
- }
-
- private OAuth2ServiceProvider<WechatApi> getOAuth2ServiceProvider() {
- return (OAuth2ServiceProvider<WechatApi>) getServiceProvider();
- }
-
- }
- package com.rui.tiger.auth.core.social.wechat.config;
-
- import com.rui.tiger.auth.core.properties.SecurityProperties;
- import com.rui.tiger.auth.core.properties.WechatProperties;
- import com.rui.tiger.auth.core.social.wechat.connect.WechatConnectionFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.core.env.Environment;
- import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
- import org.springframework.social.config.annotation.SocialConfigurerAdapter;
- import org.springframework.social.connect.ConnectionFactory;
- import org.springframework.social.connect.ConnectionFactoryLocator;
- import org.springframework.social.connect.UsersConnectionRepository;
-
- /**
- * 微信登陆配置
- * @author CaiRui extends SocialConfigurerAdapter
- * @Date 2019-01-12 13:57
- */
- //@Configuration
- //@ConditionalOnProperty(prefix = "tiger.auth.social.wechat", name = "app-id")
- public class WechatAutoConfiguration extends SocialConfigurerAdapter {
- /* @Autowired
- private SecurityProperties securityProperties;
- @Override
- public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
- connectionFactoryConfigurer.addConnectionFactory(createConnectionFactory());
- }
- private ConnectionFactory<?> createConnectionFactory() {
- WechatProperties weixinConfig = securityProperties.getSocial().getWechat();
- return new WechatConnectionFactory(weixinConfig.getProviderId(), weixinConfig.getAppId(),
- weixinConfig.getAppSecret());
- }
- // 后补:做到处理注册逻辑的时候发现的一个bug:登录完成后,数据库没有数据,但是再次登录却不用注册了
- // 就怀疑是否是在内存中存储了。结果果然发现这里父类的内存ConnectionRepository覆盖了SocialConfig中配置的jdbcConnectionRepository
- // 这里需要返回null,否则会返回内存的 ConnectionRepository
- @Override
- public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
- return null;
- }*/
-
- }
配置文件调整
- package com.rui.tiger.auth.core.properties;
-
- /**
- * 微信配置文件
- * @author CaiRui
- * @Date 2019-01-12 14:00
- */
- public class WechatProperties {
-
- /**
- * 第三方id,用来决定发起第三方登录的url,默认是 weixin。
- */
- private String providerId = "wechat";
-
- private String appId;//应用id
-
- private String appSecret;//应用密匙
-
- public String getProviderId() {
- return providerId;
- }
-
- public void setProviderId(String providerId) {
- this.providerId = providerId;
- }
-
- public String getAppId() {
- return appId;
- }
-
- public void setAppId(String appId) {
- this.appId = appId;
- }
-
- public String getAppSecret() {
- return appSecret;
- }
-
- public void setAppSecret(String appSecret) {
- this.appSecret = appSecret;
- }
- }
放到社交配置中
添加微信相关配置
- social:
- filterProcessesUrl: /auth
- qq:
- app-id: ***
- app-secret: ***
- wechat:
- app-id: ***
- app-secret: ***
前台界面添加微信登陆 tiger-login.html
- <html>
- <head>
- <meta charset="UTF-8">
- <title>登录</title>
- </head>
- <body>
- <h2>标准登录页面</h2>
- <h3>表单登录</h3>
- <form action="/authentication/form" method="post">
- <table>
- <tr>
- <td>用户名:</td>
- <td><input type="text" name="username"></td>
- </tr>
- <tr>
- <td>密码:</td>
- <td><input type="password" name="password"></td>
- </tr>
- <tr>
- <td>图形验证码:</td>
- <td>
- <input type="text" name="imageCode">
- <img src="/captcha/image">
- </td>
- </tr>
- <tr>
- <td colspan="2"><input type="checkbox" name="remember-me" value="true"/>记住我</td>
- </tr>
- <tr>
- <td colspan="2">
- <button type="submit">登录</button>
- </td>
- </tr>
- </table>
- </form>
-
- <h3>短信登录</h3>
- <form action="/authentication/mobile" method="post">
- <table>
- <tr>
- <td>手机号:</td>
- <td><input type="text" name="mobile" value="15026929536"></td>
- </tr>
- <tr>
- <td>短信验证码:</td>
- <td>
- <input type="text" name="smsCode">
- <a href="/captcha/sms?mobile=13012345678">发送验证码</a>
- </td>
- </tr>
- <tr>
- <td colspan="2"><button type="submit">登录</button></td>
- </tr>
- </table>
- </form>
-
- <!--<h3>社交登录</h3>
- <!–不支持get请求 /auth/qq 默认是这个请求 /login/qq –>
- <form action="/auth/qq" method="post">
- <button type="submit">QQ登录</button>
-
- <a href="/qqLogin/weixin">微信登录</a>
- </form>-->
-
- <h3>社交登录</h3>
- <a href="/auth/qq">QQ登录</a>
-
- <a href="/auth/wechat">微信登录</a>
-
- </body>
- </html>
ok 微信登陆的代码都写完了 ,等待后续测试
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。