使用SpringSocial开发微信登录

⒈编写微信用户对应的数据结构

  1 package cn.coreqi.social.weixin.entities;
  2 
  3 /**
  4  * 微信用户实体类
  5  */
  6 public class WeixinUserInfo {
  7     /**
  8      * 普通用户的标识,对当前开发者账号唯一
  9      */
 10     private String openid;
 11     /**
 12      * 普通用户昵称
 13      */
 14     private String nickname;
 15 
 16     /**
 17      * 语言
 18      */
 19     private String language;
 20 
 21     /**
 22      * 普通用户性别,1为男性,2为女性
 23      */
 24     private String sex;
 25     /**
 26      * 普通用户个人资料填写的省份
 27      */
 28     private String province;
 29     /**
 30      * 普通用户个人资料填写的城市
 31      */
 32     private String city;
 33     /**
 34      * 国家,如中国为CN
 35      */
 36     private String country;
 37     /**
 38      * 用户头像,最后一个数值代表正方形头像大小(有0,46,64,96,132数值可选,0代表640*640正方形)
 39      */
 40     private String headimgurl;
 41     /**
 42      * 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
 43      */
 44     private String[] privilege;
 45     /**
 46      * 用户统一标识,针对一个微信开放平台账号下的应用,同一用户的unionid是唯一的。
 47      */
 48     private String unionid;
 49 
 50 
 51     public String getOpenid() {
 52         return openid;
 53     }
 54 
 55     public void setOpenid(String openid) {
 56         this.openid = openid;
 57     }
 58 
 59     public String getNickname() {
 60         return nickname;
 61     }
 62 
 63     public void setNickname(String nickname) {
 64         this.nickname = nickname;
 65     }
 66 
 67     public String getLanguage() {
 68         return language;
 69     }
 70 
 71     public void setLanguage(String language) {
 72         this.language = language;
 73     }
 74 
 75     public String getSex() {
 76         return sex;
 77     }
 78 
 79     public void setSex(String sex) {
 80         this.sex = sex;
 81     }
 82 
 83     public String getProvince() {
 84         return province;
 85     }
 86 
 87     public void setProvince(String province) {
 88         this.province = province;
 89     }
 90 
 91     public String getCity() {
 92         return city;
 93     }
 94 
 95     public void setCity(String city) {
 96         this.city = city;
 97     }
 98 
 99     public String getCountry() {
100         return country;
101     }
102 
103     public void setCountry(String country) {
104         this.country = country;
105     }
106 
107     public String getHeadimgurl() {
108         return headimgurl;
109     }
110 
111     public void setHeadimgurl(String headimgurl) {
112         this.headimgurl = headimgurl;
113     }
114 
115     public String[] getPrivilege() {
116         return privilege;
117     }
118 
119     public void setPrivilege(String[] privilege) {
120         this.privilege = privilege;
121     }
122 
123     public String getUnionid() {
124         return unionid;
125     }
126 
127     public void setUnionid(String unionid) {
128         this.unionid = unionid;
129     }
130 }

⒉编写一个微信API接口用于获取微信用户信息

 1 package cn.coreqi.social.weixin.api;
 2 
 3 
 4 import cn.coreqi.social.weixin.entities.WeixinUserInfo;
 5 
 6 /**
 7  * 微信API调用接口
 8  */
 9 public interface Weixin {
10 
11     /**
12      * 获取微信用户信息
13      * @param openId
14      * @return
15      */
16     WeixinUserInfo getUserInfo(String openId);
17 }

⒊编写一个微信API接口实现

 1 package cn.coreqi.social.weixin.api.impl;
 2 
 3 import cn.coreqi.social.weixin.api.Weixin;
 4 import cn.coreqi.social.weixin.entities.WeixinUserInfo;
 5 import com.fasterxml.jackson.databind.ObjectMapper;
 6 import org.apache.commons.lang.StringUtils;
 7 import org.springframework.http.converter.HttpMessageConverter;
 8 import org.springframework.http.converter.StringHttpMessageConverter;
 9 import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
10 import org.springframework.social.oauth2.TokenStrategy;
11 
12 import java.nio.charset.Charset;
13 import java.util.List;
14 
15 /**
16  * 微信API调用模板,scope为Request的SpringBean,根据当前用户的accessToken创建。
17  */
18 public class WeixinImpl extends AbstractOAuth2ApiBinding implements Weixin {
19 
20     /**
21      * 用于序列化Json数据
22      */
23     private ObjectMapper objectMapper = new ObjectMapper();
24 
25     /**
26      * 获取用户信息url
27      */
28     private static final String URL_GET_USER_INFO="https://api.weixin.qq.com/sns/userinfo?openid=";
29 
30     /**
31      * WeixinImpl构造器
32      * @param accessToken
33      */
34     public WeixinImpl(String accessToken){
35         super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
36     }
37 
38     /**
39      * 默认注册的HttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8,因此必须覆盖原来的方法
40      * @return
41      */
42     @Override
43     protected List<HttpMessageConverter<?>> getMessageConverters() {
44         List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
45         messageConverters.remove(0);    //删除StringHttpMessageConverter
46         messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
47         return messageConverters;
48     }
49 
50     /**
51      * 获取微信用户信息
52      * @param openId
53      * @return
54      */
55     @Override
56     public WeixinUserInfo getUserInfo(String openId) {
57         String url = URL_GET_USER_INFO + openId;
58         String response = getRestTemplate().getForObject(url,String.class);
59         if(StringUtils.contains(response,"errcode")){   //如果响应中存在错误码则返回null
60             return null;
61         }
62         WeixinUserInfo userInfo = null;
63         try {
64             userInfo = objectMapper.readValue(response,WeixinUserInfo.class);
65         }catch (Exception e){
66 
67         }
68         return userInfo;
69     }
70 }

⒋编写微信access_token类

 1 package cn.coreqi.social.weixin.connect;
 2 
 3 import org.springframework.social.oauth2.AccessGrant;
 4 
 5 /**
 6  * 对微信access_token信息的封装
 7  * 与标准的OAuth2协议不同,微信在获取access_token时会同时返回openId,并没有单独的通过accessToke换取openId的服务
 8  * 在此处继承标准AccessGrant(Spring提供的令牌封装类),添加openId字段
 9  */
10 public class WeixinAccessGrant extends AccessGrant {
11 
12     private String openId;
13 
14     public WeixinAccessGrant() {
15         super("");
16     }
17     public WeixinAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {
18         super(accessToken, scope, refreshToken, expiresIn);
19     }
20 
21     public String getOpenId() {
22         return openId;
23     }
24 
25     public void setOpenId(String openId) {
26         this.openId = openId;
27     }
28 }

⒌编写微信OAuth2认证流程模板类。

  1 package cn.coreqi.social.weixin.connect;
  2 
  3 import java.nio.charset.Charset;
  4 import java.util.Map;
  5 import org.apache.commons.collections4.MapUtils;
  6 import org.apache.commons.lang.StringUtils;
  7 import org.slf4j.Logger;
  8 import org.slf4j.LoggerFactory;
  9 import org.springframework.http.converter.StringHttpMessageConverter;
 10 import org.springframework.social.oauth2.AccessGrant;
 11 import org.springframework.social.oauth2.OAuth2Parameters;
 12 import org.springframework.social.oauth2.OAuth2Template;
 13 import org.springframework.util.MultiValueMap;
 14 import org.springframework.web.client.RestTemplate;
 15 import com.fasterxml.jackson.databind.ObjectMapper;
 16 
 17 /**
 18  * 完成微信的OAuth2认证流程的模板类。
 19  * 国内厂商实现的OAuth2方式不同, Spring默认提供的OAuth2Template无法完整适配,只能针对每个厂商调整。
 20  */
 21 public class WeixinOAuth2Template extends OAuth2Template {
 22 
 23     private Logger logger = LoggerFactory.getLogger(getClass());
 24 
 25     private String clientId;
 26 
 27     private String clientSecret;
 28 
 29     private String accessTokenUrl;
 30 
 31     private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
 32 
 33     public WeixinOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
 34         super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
 35         setUseParametersForClientAuthentication(true);  //请求中添加client_id和client_secret参数
 36         this.clientId = clientId;
 37         this.clientSecret = clientSecret;
 38         this.accessTokenUrl = accessTokenUrl;
 39     }
 40 
 41     /**
 42      * 微信变更了OAuth请求参数的名称,我们覆写相应的方法按照微信的文档改为微信请求参数的名字。
 43      * @param authorizationCode
 44      * @param redirectUri
 45      * @param parameters
 46      * @return
 47      */
 48     @Override
 49     public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,
 50                                          MultiValueMap<String, String> parameters) {
 51 
 52         StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);
 53 
 54         accessTokenRequestUrl.append("?appid="+clientId);
 55         accessTokenRequestUrl.append("&secret="+clientSecret);
 56         accessTokenRequestUrl.append("&code="+authorizationCode);
 57         accessTokenRequestUrl.append("&grant_type=authorization_code");
 58         accessTokenRequestUrl.append("&redirect_uri="+redirectUri);
 59 
 60         return getAccessToken(accessTokenRequestUrl);
 61     }
 62 
 63     /**
 64      * 微信变更了OAuth请求参数的名称,我们覆写相应的方法按照微信的文档改为微信请求参数的名字。
 65      * @param refreshToken
 66      * @param additionalParameters
 67      * @return
 68      */
 69     public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
 70 
 71         StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
 72 
 73         refreshTokenUrl.append("?appid="+clientId);
 74         refreshTokenUrl.append("&grant_type=refresh_token");
 75         refreshTokenUrl.append("&refresh_token="+refreshToken);
 76 
 77         return getAccessToken(refreshTokenUrl);
 78     }
 79 
 80     /**
 81      * 获取微信access_token信息
 82      * @param accessTokenRequestUrl
 83      * @return
 84      */
 85     @SuppressWarnings("unchecked")
 86     private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
 87 
 88         logger.info("获取access_token, 请求URL: "+accessTokenRequestUrl.toString());
 89 
 90         String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
 91 
 92         logger.info("获取access_token, 响应内容: "+response);
 93 
 94         Map<String, Object> result = null;
 95         try {
 96             result = new ObjectMapper().readValue(response, Map.class);
 97         } catch (Exception e) {
 98             e.printStackTrace();
 99         }
100 
101         //返回错误码时直接返回空
102         if(StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))){
103             String errcode = MapUtils.getString(result, "errcode");
104             String errmsg = MapUtils.getString(result, "errmsg");
105             throw new RuntimeException("获取access token失败, errcode:"+errcode+", errmsg:"+errmsg);
106         }
107 
108         WeixinAccessGrant accessToken = new WeixinAccessGrant(
109                 MapUtils.getString(result, "access_token"),
110                 MapUtils.getString(result, "scope"),
111                 MapUtils.getString(result, "refresh_token"),
112                 MapUtils.getLong(result, "expires_in"));
113 
114         accessToken.setOpenId(MapUtils.getString(result, "openid"));
115 
116         return accessToken;
117     }
118 
119     /**
120      * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
121      */
122     public String buildAuthenticateUrl(OAuth2Parameters parameters) {
123         String url = super.buildAuthenticateUrl(parameters);
124         url = url + "&appid="+clientId+"&scope=snsapi_login";
125         return url;
126     }
127 
128     public String buildAuthorizeUrl(OAuth2Parameters parameters) {
129         return buildAuthenticateUrl(parameters);
130     }
131 
132     /**
133      * 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。
134      */
135     protected RestTemplate createRestTemplate() {
136         RestTemplate restTemplate = super.createRestTemplate();
137         restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
138         return restTemplate;
139     }
140 
141 }

⒍编写微信API适配器,将从微信API拿到的用户数据模型转换为Spring Social的标准用户数据模型。

 1 package cn.coreqi.social.weixin.connect;
 2 
 3 import cn.coreqi.social.weixin.api.Weixin;
 4 import cn.coreqi.social.weixin.entities.WeixinUserInfo;
 5 import org.springframework.social.connect.ApiAdapter;
 6 import org.springframework.social.connect.ConnectionValues;
 7 import org.springframework.social.connect.UserProfile;
 8 
 9 /**
10  * 微信API适配器,将从微信API拿到的用户数据模型转换为Spring Social的标准用户数据模型。
11  * @author fanqi 
12  */
13 public class WeixinAdapter implements ApiAdapter<Weixin> {
14 
15     private String openId;
16 
17     public WeixinAdapter() {}
18 
19     public WeixinAdapter(String openId){
20         this.openId = openId;
21     }
22 
23     /**
24      * 用来测试当前的API是否可用
25      * @param api
26      * @return
27      */
28     @Override
29     public boolean test(Weixin api) {
30         return true;
31     }
32 
33     /**
34      * 将微信的用户信息映射到ConnectionValues标准的数据化结构上
35      * @param api
36      * @param values
37      */
38     @Override
39     public void setConnectionValues(Weixin api, ConnectionValues values) {
40         WeixinUserInfo profile = api.getUserInfo(openId);
41         values.setProviderUserId(profile.getOpenid());
42         values.setDisplayName(profile.getNickname());
43         values.setImageUrl(profile.getHeadimgurl());
44     }
45 
46     /**
47      *
48      * @param api
49      * @return
50      */
51     @Override
52     public UserProfile fetchUserProfile(Weixin api) {
53         return null;
54     }
55 
56     /**
57      *
58      * @param api
59      * @param message
60      */
61     @Override
62     public void updateStatus(Weixin api, String message) {
63         //do nothing
64     }
65 
66 }

⒎编写微信的OAuth2流程处理器的提供器

 1 package cn.coreqi.social.weixin.connect;
 2 
 3 import cn.coreqi.social.weixin.api.Weixin;
 4 import cn.coreqi.social.weixin.api.impl.WeixinImpl;
 5 import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
 6 
 7 /**
 8  * 微信的OAuth2流程处理器的提供器,供spring social的connect体系调用
 9  */
10 public class WeixinServiceProvider extends AbstractOAuth2ServiceProvider<Weixin> {
11 
12     /**
13      * 微信获取授权码的url
14      */
15     private static final String URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect";
16     /**
17      * 微信获取accessToken的url
18      */
19     private static final String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
20 
21     /**
22      * 
23      * @param appId
24      * @param appSecret
25      */
26     public WeixinServiceProvider(String appId, String appSecret) {
27         super(new WeixinOAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN));
28     }
29 
30     /**
31      * 
32      * @param accessToken
33      * @return
34      */
35     @Override
36     public Weixin getApi(String accessToken) {
37         return new WeixinImpl(accessToken);
38     }
39 
40 }

⒏创建微信连接工厂

 1 package cn.coreqi.social.weixin.connect;
 2 
 3 import cn.coreqi.social.weixin.api.Weixin;
 4 import org.springframework.social.connect.ApiAdapter;
 5 import org.springframework.social.connect.Connection;
 6 import org.springframework.social.connect.ConnectionData;
 7 import org.springframework.social.connect.support.OAuth2Connection;
 8 import org.springframework.social.connect.support.OAuth2ConnectionFactory;
 9 import org.springframework.social.oauth2.AccessGrant;
10 import org.springframework.social.oauth2.OAuth2ServiceProvider;
11 
12 /**
13  * 微信连接工厂
14  */
15 public class WeixinConnectionFactory extends OAuth2ConnectionFactory<Weixin> {
16 
17     /**
18      *
19      * @param providerId
20      * @param appId
21      * @param appSecret
22      */
23     public WeixinConnectionFactory(String providerId, String appId, String appSecret) {
24         super(providerId, new WeixinServiceProvider(appId, appSecret), new WeixinAdapter());
25     }
26 
27     /**
28      * 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
29      * @param accessGrant
30      * @return
31      */
32     @Override
33     protected String extractProviderUserId(AccessGrant accessGrant) {
34         if(accessGrant instanceof WeixinAccessGrant) {
35             return ((WeixinAccessGrant)accessGrant).getOpenId();
36         }
37         return null;
38     }
39 
40     /**
41      *
42      * @param accessGrant
43      * @return
44      */
45     public Connection<Weixin> createConnection(AccessGrant accessGrant) {
46         return new OAuth2Connection<Weixin>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(),
47                 accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));
48     }
49 
50     /**
51      *
52      * @param data
53      * @return
54      */
55     public Connection<Weixin> createConnection(ConnectionData data) {
56         return new OAuth2Connection<Weixin>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
57     }
58 
59     /**
60      *
61      * @param providerUserId
62      * @return
63      */
64     private ApiAdapter<Weixin> getApiAdapter(String providerUserId) {
65         return new WeixinAdapter(providerUserId);
66     }
67 
68     /**
69      *
70      * @return
71      */
72     private OAuth2ServiceProvider<Weixin> getOAuth2ServiceProvider() {
73         return (OAuth2ServiceProvider<Weixin>) getServiceProvider();
74     }
75 
76 
77 }

⒐创建微信登陆配置类

 1 package cn.coreqi.social.weixin.config;
 2 
 3 import cn.coreqi.social.weixin.connect.WeixinConnectionFactory;
 4 import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.social.connect.ConnectionFactory;
 7 
 8 /**
 9  * 微信登录配置
10  */
11 @Configuration
12 public class WeixinAutoConfiguration extends SocialAutoConfigurerAdapter {
13 
14     @Override
15     protected ConnectionFactory<?> createConnectionFactory() {
16         String providerId = "weixin";   //第三方id,用来决定发起第三方登录的url,默认是weixin
17         String appId = "";
18         String appSecret = "";
19         return new WeixinConnectionFactory(providerId, appId,
20                 appSecret);
21     }
22     
23 }

 

posted @ 2019-04-04 12:46  SpringCore  阅读(2412)  评论(0编辑  收藏  举报