使用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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM