微信開放平臺開發系列文章:
目錄
一、需求
根據需求,需要擁有第三方微信登錄功能,並獲取到用戶信息。
二、開發流程
微信公衆平臺第三方授權登錄的應用場景在於 : 在微信客戶端(PC或APP)訪問第三方網頁,公衆號可以通過網頁授權機制,獲取用戶基本信息,實現業務邏輯。
1用戶同意授權,獲取code
2通過code換取網頁授權access_token
3通過access_token拉取用戶信息(需scope爲 snsapi_userinfo)
公衆號第三方授權獲取用戶信息基本流程
三、具體實現步驟
1.引導用戶跳轉到微信授權網頁
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
參數 |
說明 |
appid |
公衆號的唯一標識 |
redirect_uri |
授權後重定向的回調鏈接地址, 請使用 urlEncode 對鏈接進行處理 |
response_type |
返回類型,請填寫code |
scope |
應用授權作用域,snsapi_base (不彈出授權頁面,直接跳轉,只能獲取用戶openid),snsapi_userinfo (彈出授權頁面,可通過openid拿到暱稱、性別、所在地。並且, 即使在未關注的情況下,只要用戶授權,也能獲取其信息 ) |
state |
重定向後會帶上state參數,開發者可以填寫a-zA-Z0-9的參數值,最多128字節 |
#wechat_redirect |
無論直接打開還是做頁面302重定向時候,必須帶此參數 |
如果用戶同意授權,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE。
注意:code作爲換取access_token的票據,每次用戶授權帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動過期。
@RequestMapping("/login")
public String wechatLogin(HttpServletRequest httpServletRequest) {
// 應用授權作用域:
// 當爲snsapi_base時,不彈出授權頁面,直接跳轉,只能獲取用戶openid。
// 當爲snsapi_userinfo時,彈出授權頁面,可通過openid拿到暱稱、性別、所在地。並且, 即使在未關注的情況下,只要用戶授權,也能獲取其信息
String scope = "snsapi_userinfo";
String state = UUID.randomUUID().toString().replaceAll("-","");
// 採用redis等進行緩存state 使用sessionId爲key 30分鐘後過期,可配置
RedisPoolUtil.setEx("wechat-mp-state-"+httpServletRequest.getSession().getId(), state, Integer.parseInt(env.getProperty("wechat.mp.exTime", "1800")));
log.info("state= "+state);
// 微信公衆平臺
String get_auth_url = "https://open.weixin.qq.com/connect/oauth2/authorize?"
+ "appid="
+ env.getProperty("wechat.mp.appid")
+ "&redirect_uri="
+ env.getProperty("application.url")
+ env.getProperty("wechat.mp.redirect_uri")
+ "&response_type=code"
+ "&scope="
+ scope
+ "&state="
+ state
+ "#wechat_redirect";
log.info("URL:"+get_auth_url);
return "redirect:" + get_auth_url;
}
2. 通過code換取網頁授權access_token
用戶同意授權後,通過code換取網頁授權access_token
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 |
請求正確返回的結果樣例:
{ "access_token":"ACCESS_TOKEN", //網頁授權接口調用憑證,注意:此access_token與基礎支持的access_token不同
"expires_in":7200, // access_token接口調用憑證超時時間,單位(秒)
"refresh_token":"REFRESH_TOKEN", //用戶刷新access_token
"openid":"OPENID", //用戶唯一標識
"scope":"SCOPE" } //用戶授權的作用域,使用逗號(,)分隔
錯誤返回樣例:
{"errcode":40029,"errmsg":"invalid code"}
@RequestMapping("/getInfo")
public String getInfo(HttpServletRequest httpServletRequest, Model model) {
String code = httpServletRequest.getParameter("code");
String state = httpServletRequest.getParameter("state");
String value = RedisPoolUtil.get("wechat-mp-state-"+httpServletRequest.getSession().getId());
log.info("Code = " + code+" . State="+state+" value="+value);
/* 如果state值不匹配,則爲非法請求,拋出異常 */
if (StringUtils.isEmpty(code)||StringUtils.isEmpty(state)||value==null || !state.equals(value)){
throw new RuntimeException("非法請求");
}
// 通過code換取網頁授權access_token
String get_access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" +
env.getProperty("wechat.mp.appid") +
"&secret=" +
env.getProperty("wechat.mp.secret") +
"&code=" +
code +
"&grant_type=authorization_code";
log.info(get_access_token_url);
JSONObject accessTokenJsonObject = HttpClientUtils.httpGet(get_access_token_url);
checkResult(accessTokenJsonObject);
// 成功獲取
String access_token = (String) accessTokenJsonObject.get("access_token");
Integer expires_in = (Integer) accessTokenJsonObject.get("expires_in");//access_token接口調用憑證超時時間,單位(秒)
String userOpenid = (String) accessTokenJsonObject.get("openid");//用戶唯一標識
String scope = (String) accessTokenJsonObject.get("scope");//用戶授權的作用域,使用逗號(,)分隔
// TODO:文檔沒有寫返回unionid,需要在實際公衆號中進行測試(綁定開放平臺的會有),如果沒有unionid,則用openid存儲用戶,雖然這並不是合理的做法。因爲openid僅僅該公衆號下的用戶唯一標識。。。
// access_token擁有較短的有效期,當access_token超時後,可以使用refresh_token進行刷新,
// refresh_token有效期爲30天,當refresh_token失效之後,需要用戶重新授權
WeChatUserInfo weChatUserInfo = null;
weChatUserInfo = null; //FIXME: 根據unionid從數據庫中查詢
if (weChatUserInfo == null){
weChatUserInfo = getMPWeChatUserInfo(access_token,userOpenid);
// insert into database
}
if (weChatUserInfo.getUnionid()!=null){
// 當前是綁定開放平臺的公衆號.Union將成爲唯一標識
}
model.addAttribute(weChatUserInfo);
return "wechatUser";
}
3.根據token獲取用戶信息
http:GET(請使用https協議) https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
參數 |
描述 |
access_token |
網頁授權接口調用憑證,注意:此access_token與基礎支持的access_token不同 |
openid |
用戶的唯一標識 |
lang |
返回國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語 |
正確時返回的JSON數據包如下:
{
"openid":" OPENID",
" nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2" ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
private WeChatUserInfo getMPWeChatUserInfo(String accessToken, String openId){
String get_userInfo_url = "https://api.weixin.qq.com/sns/userinfo?" +
"access_token=" +
accessToken +
"&openid=" +
openId +
"&lang=zh_CN";
JSONObject userInfoJsonObject = HttpClientUtils.httpGet(get_userInfo_url);
checkResult(userInfoJsonObject);
String user_openid = (String) userInfoJsonObject.get("openid");
String user_nickname = (String) userInfoJsonObject.get("nickname");
Integer user_sex = (Integer) userInfoJsonObject.get("sex");//用戶的性別,值爲1時是男性,值爲2時是女性,值爲0時是未知
String user_province = (String) userInfoJsonObject.get("province");
String user_city = (String) userInfoJsonObject.get("city");
String user_country = (String) userInfoJsonObject.get("country");//國家,如中國爲CN,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),用戶沒有頭像時該項爲空。若用戶更換頭像,原有頭像URL將失效。
String user_headimgurl = (String) userInfoJsonObject.get("headimgurl");//頭像
List user_privilege = (List) userInfoJsonObject.get("privilege");//用戶特權信息,json 數組,如微信沃卡用戶爲(chinaunicom)
String user_unionid = (String) userInfoJsonObject.get("unionid");
WeChatUserInfo weChatUserInfo = new WeChatUserInfo();
weChatUserInfo.setOpenid(user_openid);
weChatUserInfo.setNickname(user_nickname);
weChatUserInfo.setSex(user_sex);
weChatUserInfo.setProvince(user_province);
weChatUserInfo.setCity(user_city);
weChatUserInfo.setCountry(user_country);
weChatUserInfo.setHeadimgurl(user_headimgurl);
weChatUserInfo.setPrivilege(StringTools.listToString(user_privilege));
weChatUserInfo.setUnionid(user_unionid);
log.info("用戶信息如下:" +
" opeinId:" + user_openid +
" 用戶暱稱:" + user_nickname +
" 用戶的性別:" + user_sex +
" 省份:" + user_province +
" 城市:" + user_city +
" 國家:" + user_country +
" 頭像:" + user_headimgurl +
" 用戶特權信息:" + user_privilege +
" UnionId:" + user_unionid
);
return weChatUserInfo;
}
四、參數位置:
開放平臺綁定公衆號: