微信開放平台開發系列文章:
微信開放平台開發第三方授權登陸(一):開發前期準備
微信開放平台開發第三方授權登陸(二):PC網頁端
微信開放平台開發第三方授權登陸(三):Android用戶端
微信開放平台開發第三方授權登陸(四):微信公衆号
微信開放平台開發第三方授權登陸(五):微信小程式
目錄
一、需求
二、開發流程
1.網站應用:(微信用戶端掃碼授權登陸)
三、開發使用的技術及工具
四、具體實作步驟
1、網站應用
1)請求擷取Code
2)使用者同意授權與否
3)擷取access_token
4)通過access_token調用接口擷取使用者個人資訊(UnionID機制)
5)重新整理access_token
五、測試結果
1、網站應用
六、應用關鍵參數位置
微信開放平台第三方授權登陸開發文檔(PC網頁端)
當微信開放平台開發第三方授權登陸(一):開發前期準備完成後,已經擷取到應用的AppID和AppSecret、且已經成功申請到微信登陸功能。可以進行第三方登陸授權開發。
網站應用微信登入是基于OAuth2.0協定标準建構的微信OAuth2.0授權登入系統。
一、需求
根據需求,需要擁有第三方微信登入功能,并擷取到使用者資訊。
二、開發流程
1.網站應用:(微信用戶端掃碼授權登陸)
1)第三方發起微信授權登入請求,微信使用者允許授權第三方應用後,微信會拉起應用或重定向到第三方網站,并且帶上授權臨時票據code參數;
2)通過code參數加上AppID和AppSecret等,通過API換取access_token;
3)通過access_token進行接口調用,擷取使用者基本資料資源或幫助使用者實作基本操作。
網站應用第三方授權登陸擷取使用者資訊
三、開發使用的技術及工具
1、使用IDEA2017.2進行開發
2、使用SpringBoot進行快速開發
3、使用redis進行緩存。
4、使用fastJson對json資料進行處理
5、使用Maven對項目進行管理
四、具體實作步驟
1、網站應用
建立工程
打開IDEA,建立一個工程,選擇Spring Initializr,Next。然後填寫工程基本資訊
選擇SpringBoot的版本已經需要添加的依賴,也可以直接跳過,手動添加依賴。由于是web工程,需要添加web相關依賴。模闆引擎采用springboot推薦的thymeleaf。最後點選确認,進入工程。
添加相關依賴:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
<!-- 添加httpclient支援 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
<type>jar</type>
</dependency>
添加配置檔案
SpringBoot預設會将resources下的application名字開頭的properties作為配置檔案。是以隻需要添加application.properties就可以了
由于是使用thymeleaf作為模闆引擎。可以添加相應的配置:
#thymelea模闆配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
為了讓系統更具有通用性,分别建立application-dev.properties、application-prod.properties對不同的環境進行配置。
在application.properties中,使用spring.profiles.active進行配置環境的選擇。
如:spring.profiles.active = prod
如:application-prod.properties
## 微信開放平台
# APPID
wechat.open.appid =
# APPSECRET
wechat.open.appsecret =
# 回調位址
wechat.open.redirect_uri =
建立javaBean:WeChatUserInfo.java
@Getter @Setter @ToString
public class WeChatUserInfo {
String openid;
String nickname;
Integer sex;
String province;
String city;
String country;
String headimgurl;
String privilege;
String unionid;
}
建立WeChatOpenLoginController.java實作微信開放平台第三方登入的主要邏輯。
根據文檔進行編寫代碼。
1)請求擷取Code
前提:應用已經擷取相應的網頁授權作用域(scope=snsapi_login)
開發:第三方網站引導使用者打開連結
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
參數說明:
參數 | 必須 | 說明 |
appid | 是 | 應用唯一辨別 |
redirect_uri | 是 | 請使用urlEncode對連結進行處理 |
response_type | 是 | 填code |
scope | 是 | 應用授權作用域,擁有多個作用域用逗号(,)分隔,網頁應用目前僅填寫snsapi_login即可 |
state | 否 | 用于保持請求和回調的狀态,授權請求後原樣帶回給第三方。該參數可用于防止csrf攻擊(跨站請求僞造攻擊),建議第三方帶上該參數,可設定為簡單的随機數加session進行校驗 |
注意:若提示“該連結無法通路”,請檢查參數是否填寫錯誤,如redirect_uri的域名與稽核時填寫的授權域名不一緻或scope不為snsapi_login。
@RequestMapping("/login")
public String openWeChatLogin(HttpServletRequest httpServletRequest) {
// 防止csrf攻擊(跨站請求僞造攻擊)
String state = UUID.randomUUID().toString().replaceAll("-", "");
// 采用redis等進行緩存state 使用sessionId為key 30分鐘後過期,可配置
RedisPoolUtil.setEx("wechat-open-state-" + httpServletRequest.getSession().getId(), state, Integer.parseInt(env.getProperty("wechat.open.exTime", "1800")));
String url = "https://open.weixin.qq.com/connect/qrconnect?" +
"appid=" +
env.getProperty("wechat.open.pc.appid").trim() +
"&redirect_uri=" +
env.getProperty("application.url") +
env.getProperty("wechat.open.pc.redirect_uri").trim() +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=" +
state + // 由背景自動生成
"#wechat_redirect";
return "redirect:" + url;
}
2)使用者同意授權與否
使用者允許授權後,将會重定向到redirect_uri的網址上,并且帶上code和state參數
redirect_uri?code=CODE&state=STATE
若使用者禁止授權,則不會重定向到我們提供的回調位址中
成功授權後,将獲得Code,通過Code可以擷取access_token
3)擷取access_token
通過上述方法擷取的code擷取access_token.
Http Get請求
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
參數說明:
參數 | 是否必須 | 說明 |
appid | 是 | 應用唯一辨別,在微信開放平台送出應用稽核通過後獲得 |
secret | 是 | 應用密鑰AppSecret,在微信開放平台送出應用稽核通過後獲得 |
code | 是 | 填寫擷取的code參數 |
grant_type | 是 | 填authorization_code |
請求後,
傳回成功的json串為(樣例):
{
"access_token":"ACCESS_TOKEN", // 接口調用憑證
"expires_in":7200, // access_token接口調用憑證逾時時間,機關(秒)
"refresh_token":"REFRESH_TOKEN", //使用者重新整理access_token
"openid":"OPENID", //授權使用者唯一辨別
"scope":"SCOPE", //使用者授權的作用域,使用逗号(,)分隔
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL" //當且僅當該網站應用已獲得該使用者的userinfo授權時,才會出現該字段
}
失敗傳回的樣例:
{"errcode":40029,"errmsg":"invalid code"}
失敗可能原因: 暫時不明
@RequestMapping("/callback/pc")
public String openWeChatCallback(HttpServletRequest httpServletRequest, Model model) {
String code = httpServletRequest.getParameter("code");
String state = httpServletRequest.getParameter("state");
String url = null;
// 判斷state是否合法
String stateStr = RedisPoolUtil.get("wechat-open-state-" + httpServletRequest.getSession().getId());
if (StringUtils.isEmpty(code) || StringUtils.isEmpty(stateStr) || !state.equals(stateStr)) {
throw new WechatParamException("非法參數,請重新登陸", "/");
}
url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" +
env.getProperty("wechat.open.pc.appid").trim() +
"&secret=" +
env.getProperty("wechat.open.pc.appsecret").trim() +
"&code=" +
code +
"&grant_type=authorization_code";
JSONObject wechatAccessToken = HttpClientUtils.httpGet(url);
if (wechatAccessToken.get("errcode") != null) {
throw new WechatParamException("擷取accessToken失敗", "/wechat/open/login");
}
String accessToken = (String) wechatAccessToken.get("access_token");
String openid = (String) wechatAccessToken.get("openid");
String unionid = (String) wechatAccessToken.get("unionid");
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openid) || StringUtils.isEmpty(unionid)) {
throw new WechatParamException("擷取accessToken失敗", "/wechat/open/login");
}
// TODO:根據Openid或Unionid對資料庫進行查詢,如果查詢到對應的使用者資料,則不需要再向微信伺服器發送請求去傳回資料。
// TODO: 建議使用Unionid作為查詢條件。
WeChatUserInfo weChatUserInfo = null;
wechatAccessToken = null; // FIXME: 這裡應該是從資料庫中查詢擷取使用者資訊邏輯。
if (wechatAccessToken == null) {
// 新使用者
weChatUserInfo = getUserInfoByAccessToken(accessToken);
// 資料庫插入的操作
}
if (weChatUserInfo != null) {
model.addAttribute(weChatUserInfo);
return "wechatUser";
}
throw new WechatParamException("擷取使用者資訊失敗", "/wechat/open/login");
}
4)通過access_token調用接口擷取使用者個人資訊(UnionID機制)
前提:
1. access_token有效且未逾時;
2. 微信使用者已授權給第三方應用帳号相應接口作用域(scope)。
Http Get請求:
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
參數 | 是否必須 | 說明 |
access_token | 是 | 調用憑證 |
openid | 是 | 普通使用者的辨別,對目前開發者帳号唯一 |
lang | 否 | 國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語,預設為zh-CN |
傳回成功的json結果(樣例):
{
"openid":"OPENID", //普通使用者的辨別,對目前開發者帳号唯一
"nickname":"NICKNAME", //普通使用者昵稱
"sex":1, //普通使用者性别,1為男性,2為女性
"province":"PROVINCE", //普通使用者個人資料填寫的省份
"city":"CITY", //普通使用者個人資料填寫的城市
"country":"COUNTRY", //國家,如中國為CN
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0", //使用者頭像,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),使用者沒有頭像時該項為空
"privilege":[ //使用者特權資訊,json數組,如微信沃卡使用者為(chinaunicom)
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL" //使用者統一辨別。針對一個微信開放平台帳号下的應用,同一使用者的unionid是唯一的
}
失敗JSON樣例:
{"errcode":40003,"errmsg":"invalid openid"}
注意:在使用者修改微信頭像後,舊的微信頭像URL将會失效,是以開發者應該自己在擷取使用者資訊後,将頭像圖檔儲存下來,避免微信頭像URL失效後的異常情況
最好儲存使用者unionID資訊,以便以後在不同應用中進行使用者資訊互通。
private WeChatUserInfo getUserInfoByAccessToken(String accessToken) {
if (StringUtils.isEmpty(accessToken)) {
return null; //"accessToken為空";
}
String get_userInfo_url = null;
get_userInfo_url = "https://api.weixin.qq.com/sns/userinfo?" +
"access_token=" +
accessToken +
"&openid=" +
env.getProperty("wechat.open.pc.appid").trim();
String userInfo_result = HttpClientUtils.httpGet(get_userInfo_url, "utf-8");
if (!userInfo_result.equals("errcode")) {
WeChatUserInfo weChatUserInfo = JSON.parseObject(userInfo_result, new TypeReference<WeChatUserInfo>() {
});
// TODO: 需要把頭像資訊下載下傳到檔案伺服器,然後替換掉頭像URL。微信的或許不可靠,假設微信使用者更換了頭像,舊頭像URL是否會儲存?而這個URL資訊卻存放在我們的資料庫中,不可靠
return weChatUserInfo;
}
return null; //"擷取使用者資訊失敗"
}
5)重新整理access_token
由于access_token有效期(目前為2個小時)較短,當access_token逾時後,可以使用refresh_token進行重新整理,access_token重新整理結果有兩種:
1. 若access_token已逾時,那麼進行refresh_token會擷取一個新的access_token,新的逾時時間;
2. 若access_token未逾時,那麼進行refresh_token不會改變access_token,但逾時時間會重新整理,相當于續期access_token。
refresh_token擁有較長的有效期(30天),當refresh_token失效的後,需要使用者重新授權。
請求方法:
擷取第一步的code後,請求以下連結進行refresh_token:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
參數說明:
參數 | 是否必須 | 說明 |
appid | 是 | 應用唯一辨別 |
grant_type | 是 | 填refresh_token |
refresh_token | 是 | 填寫通過access_token擷取到的refresh_token參數 |
成功傳回的結果:
{
"access_token":"ACCESS_TOKEN", //接口調用憑證
"expires_in":7200, // access_token接口調用憑證逾時時間,機關(秒)
"refresh_token":"REFRESH_TOKEN", //使用者重新整理access_token
"openid":"OPENID", //授權使用者唯一辨別
"scope":"SCOPE" //使用者授權的作用域,使用逗号(,)分隔
}
失敗樣例:
{"errcode":40030,"errmsg":"invalid refresh_token"}
注意:
1、Appsecret 是應用接口使用密鑰,洩漏後将可能導緻應用資料洩漏、應用的使用者資料洩漏等高風險後果;存儲在用戶端,極有可能被惡意竊取(如反編譯擷取Appsecret);
2、access_token 為使用者授權第三方應用發起接口調用的憑證(相當于使用者登入态),存儲在用戶端,可能出現惡意擷取access_token 後導緻的使用者資料洩漏、使用者微信相關接口功能被惡意發起等行為;
3、refresh_token 為使用者授權第三方應用的長效憑證,僅用于重新整理access_token,但洩漏後相當于access_token 洩漏,風險同上。
建議将secret、使用者資料(如access_token)放在App雲端伺服器,由雲端中轉接口調用請求。
五、測試結果
1、網站應用
首先開啟redis。然後運作代碼。由于SpringBoot會使用内置的Tomcat容器進行管理,直接運作就可以了,然後點選微信登入。彈出掃碼登入視窗,手機掃碼同意登入後就可以擷取到使用者資訊了.
六、應用關鍵參數位置
APPID和APPSecret以及回調位址位置,注:需要修改授權回調域,即回調位址