微信公共平臺接入之:網頁授權(微信授權,微信access_token獲取,獲取微信使用者資訊),微信開發者工具使用,微信公眾平臺測試號申請接入
1、微信公眾平臺文件入口
2、微信網頁授權
關於網頁授權回撥域名的說明
1、在微信公眾號請求使用者網頁授權之前,開發者需要先到公眾平臺官網中的“開發 - 介面許可權 - 網頁服務 - 網頁帳號 - 網頁授權獲取使用者基本資訊”的配置選項中,修改授權回撥域名。請注意,這裡填寫的是域名(是一個字串),而不是URL,因此請勿加 http:// 等協議頭;
即:公眾號設定裡面–>公眾號設定 —>功能設定 裡面的 網頁授權域名 不用帶http或https字樣
類似
3、如果公眾號登入授權給了第三方開發者來進行管理,則不必做任何設定,由第三方代替公眾號實現網頁授權即可
關於網頁授權的兩種scope的區別說明
1、以snsapi_base為scope發起的網頁授權,是用來獲取進入頁面的使用者的openid的,並且是靜默授權並自動跳轉到回撥頁的。使用者感知的就是直接進入了回撥頁(往往是業務頁面)
2、以snsapi_userinfo為scope發起的網頁授權,是用來獲取使用者的基本資訊的。但這種授權需要使用者手動同意,並且由於使用者同意過,所以無須關注,就可在授權後獲取該使用者的基本資訊。
3、使用者管理類介面中的“獲取使用者基本資訊介面”,是在使用者和公眾號產生訊息互動或關注後事件推送後,才能根據使用者OpenID來獲取使用者基本資訊。這個介面,包括其他微信介面,都是需要該使用者(即openid)關注了公眾號後,才能呼叫成功的。
關於網頁授權access_token和普通access_token的區別
1、微信網頁授權是通過OAuth2.0機制實現的,在使用者授權給公眾號後,公眾號可以獲取到一個網頁授權特有的介面呼叫憑證(網頁授權access_token),通過網頁授權access_token可以進行授權後接口呼叫,如獲取使用者基本資訊;
2、其他微信介面,需要通過基礎支援中的“獲取access_token”介面來獲取到的普通access_token呼叫。
關於UnionID機制
1、請注意,網頁授權獲取使用者基本資訊也遵循UnionID機制。即如果開發者有在多個公眾號,或在公眾號、移動應用之間統一使用者帳號的需求,需要前往微信開放平臺(open.weixin.qq.com)繫結公眾號後,才可利用UnionID機制來滿足上述需求。
2、UnionID機制的作用說明:如果開發者擁有多個移動應用、網站應用和公眾帳號,可通過獲取使用者基本資訊中的unionid來區分使用者的唯一性,因為同一使用者,對同一個微信開放平臺下的不同應用(移動應用、網站應用和公眾帳號),unionid是相同的。
關於特殊場景下的靜默授權
1、上面已經提到,對於以snsapi_base為scope的網頁授權,就靜默授權的,使用者無感知;
2、對於已關注公眾號的使用者,如果使用者從公眾號的會話或者自定義選單進入本公眾號的網頁授權頁,即使是scope為snsapi_userinfo,也是靜默授權,使用者無感知。
具體而言,網頁授權流程分為四步:
1、引導使用者進入授權頁面同意授權,獲取code
2、通過code換取網頁授權access_token(與基礎支援中的access_token不同)
3、如果需要,開發者可以重新整理網頁授權access_token,避免過期
4、通過網頁授權access_token和openid獲取使用者基本資訊(支援UnionID機制)
目錄
1 第一步:使用者同意授權,獲取code
2 第二步:通過code換取網頁授權access_token
3 第三步:重新整理access_token(如果需要)
4 第四步:拉取使用者資訊(需scope為 snsapi_userinfo)
5 附:檢驗授權憑證(access_token)是否有效
第一步:使用者同意授權,獲取code
在確保微信公眾賬號擁有授權作用域(scope引數)的許可權的前提下(服務號獲得高階介面後,預設擁有scope引數中的snsapi_base和snsapi_userinfo),引導關注者開啟如下頁面:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“該連結無法訪問”,請檢查引數是否填寫錯誤,是否擁有scope引數對應的授權作用域許可權。
尤其注意:由於授權操作安全等級較高,所以在發起授權請求時,微信會對授權連結做正則強匹配校驗,如果連結的引數順序不對,授權頁面將無法正常訪問
參考連結(請在微信客戶端中開啟此連結體驗):
scope為snsapi_base
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
scope為snsapi_userinfo
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
尤其注意:跳轉回調redirect_uri,應當使用https連結來確保授權code的安全性。
引數說明
引數 是否必須 說明
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要在程式碼層面進行url.encode,示例程式碼:
/**
* 獲取微信code的值
*/
@RequestMapping("/accredit")
public String accredit(
HttpServletResponse response,
HttpServletRequest request,
Model model,
@RequestParam(value = "state",required = false,defaultValue = "-1") Long state) throws Exception {
log.info("【微信-授權】微信授權,獲取微信code");
//thirdPart_weixin_app_id :wxxxxxxxxxx04
//thirdPart_domain :http://網頁授權域名/
//這裡可以最後直接redirect到java後端action地址。也可以直接redirect到前端頁面地址。前提條件是這兩個地址對應的域名是上面配置的"網頁授權域名"
//String redirectUri = URLEncoder.encode(SysConfig.getValue("thirdPart_domain") + "/wap/thirdPart/accreditCallback","utf-8");
String redirectUri = URLEncoder.encode(SysConfig.getValue("thirdPart_redirect_url"));
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + SysConfig.getValue("thirdPart_weixin_app_id")
+ "&redirect_uri=" + redirectUri
+ "&response_type=code"
+ "&scope=snsapi_userinfo"
//這個state最後會在跳轉後在redirect_url後面攜帶過去,同樣微信code也可以攜帶過去,顯示的現象是redirect_url實際的url後面多了?code=xxxxx&state=xxx 這樣的內容。
//如果是java後端的action,可以通過方法定義中的String code,Long state來接收這兩個引數
+ "&state=" + state
+ "#wechat_redirect";
logger.info("url>>>>>>>>>>>>>>>>>>>>>>>>>>" + url);
//要注意的是這裡必須是重定向的,如果是返回json型別的,證明是不可行的,不要再走死衚衕了
return "redirect:" + url;
}
下圖為scope等於snsapi_userinfo時的授權頁面:
使用者同意授權後
如果使用者同意授權,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE。
code說明 : code作為換取access_token的票據,每次使用者授權帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動過期。
錯誤返回碼說明如下:
返回碼 說明
10003 redirect_uri域名與後臺配置不一致
10004 此公眾號被封禁
10005 此公眾號並沒有這些scope的許可權
10006 必須關注此測試號
10009 操作太頻繁了,請稍後重試
10010 scope不能為空
10011 redirect_uri不能為空
10012 appid不能為空
10013 state不能為空
10015 公眾號未授權第三方平臺,請檢查授權狀態
10016 不支援微信開放平臺的Appid,請使用公眾號Appid
第二步:通過code換取網頁授權access_token
首先請注意,這裡通過code換取的是一個特殊的網頁授權access_token,與基礎支援中的access_token(該access_token用於呼叫其他介面)不同。公眾號可通過下述介面來獲取網頁授權access_token。如果網頁授權的作用域為snsapi_base,則本步驟中獲取到網頁授權access_token的同時,也獲取到了openid,snsapi_base式的網頁授權流程即到此為止。
尤其注意:由於公眾號的secret和獲取到的access_token安全級別都非常高,必須只儲存在伺服器,不允許傳給客戶端。後續重新整理access_token、通過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
返回說明
正確時返回的JSON資料包如下:
{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }
引數 描述
access_token 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同
expires_in access_token介面呼叫憑證超時時間,單位(秒)
refresh_token 使用者重新整理access_token
openid 使用者唯一標識,請注意,在未關注公眾號時,使用者訪問公眾號的網頁,也會產生一個使用者和公眾號唯一的OpenID
scope 使用者授權的作用域,使用逗號(,)分隔
錯誤時微信會返回JSON資料包如下(示例為Code無效錯誤):
{"errcode":40029,"errmsg":"invalid code"}
案例:
/**
* 微信回撥,獲取微信access_ticket,獲取使用者資訊介面
* @param response
* @param request
* @param code :微信code
* @return
*/
@ResponseBody
@RequestMapping("/accreditCallback")
public String accreditCallback(
HttpServletResponse response,
HttpServletRequest request,
String code
) {
log.info("【xxxx分享-授權-回撥】微信使用者授權獲取使用者資訊開始");
log.info("【xxxx分享-授權-回撥】獲取到微信code,code = {},引數params = {}" , code);
AjaxResponseObj ajaxObj = new AjaxResponseObj();
try {
if (StringUtils.isNotBlank(code)) {
//thirdPart_weixin_app_id ----> xxxxx_weixin_app_id ----> :wxxxxxxxxxxxxxxxx04
//thirdPart_domain -----> xxxxxx_weixin_app_secret ----> :http://網頁授權域名/
//thirdPart_weixin_app_secret ---> xxxxx_weixin_app_secret ---> :d9xxxxxxxxxxxxcca925c92ef
String authUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" + SysConfig.getValue("thirdPart_weixin_app_id") +
"&secret=" + SysConfig.getValue("thirdPart_weixin_app_secret") +
"&code=" + code +
"&grant_type=authorization_code";
String result = HttpClientUtil.getInstance().sendHttpGet(authUrl);
JSONObject json = JSONObject.fromObject(result);
log.info("【xxxx分享-授權-回撥】返回的使用者資訊是:{}",json.toString());
log.info("【xxxx分享-授權-回撥】微信使用者授權回撥返回的狀態值:" + (!json.containsKey("errcode")));
if(!json.containsKey("errcode")) { //呼叫正確
//獲取openid
String openId = json.getString("openid");
String access_token = json.getString("access_token");
logger.info("access_token=============" + access_token);
XXXXSysUserThreeInfo tb = new XXXXSysUserThreeInfo();
tb.setUid(openId);
tb.setOpenid(openId);
tb.setType(SysUserThreeTypeEnum.REDENVELOPE.getValue());
tb.setAccessToken(access_token);
//通過access_token, openId獲取微信的使用者資訊
WeiXinUserResponse weiXinUserResponse= WeiXinUtil.getScopeUsersDetail(access_token, openId);
logger.info("=================微信使用者資訊 start===================");
logger.info(weiXinUserResponse.toString());
logger.info("=================微信使用者資訊 end===================");
if(null != weiXinUserResponse) {
//tb.setUid(weiXinUserResponse.getUnionid());
//替換暱稱存在特殊字串情況,使用*替換
tb.setName(StringUtil.removeFourChar(filterEmoji(weiXinUserResponse.getNickname())));
log.info("【xxx分享-授權-回撥】使用者詳情獲取成功,暱稱:" + weiXinUserResponse.getNickname()+":轉換後暱稱:"+tb.getName());
tb.setIconurl(weiXinUserResponse.getHeadimgurl());
}
//這裡略去若干敏感程式碼
if (null != xxxSysUser) {
String redirectUrl = SysConfig.getValue("thirdPart_redirect_url");
if (StringUtils.isNotEmpty(redirectUrl)) {
//獲取到系統內的ticket
String loginTicket = createLoginTicket(user,userTypestr,2);
Map<String,Object> callBackData = new HashMap<String,Object>();
callBackData.put("ticket",loginTicket);
ajaxObj.setData(callBackData);
ajaxObj.setStatus(1);
ajaxObj.setShowMessage("成功獲取使用者資訊");
//注意,這裡是使用的ajax介面返回,若專案沒有做前後端分離,可以直接使用redirect:url的方式重定向到相應的頁面,並可以可以把相應的引數攜帶過去。
return ajaxObj.getJsonpData(request,response);
}
}
}else{
//略去若干敏感程式碼
ajaxObj.setStatus(0);
ajaxObj.setData("");
ajaxObj.setShowMessage("獲取使用者資訊失敗");
return ajaxObj.getJsonpData(request,response);
}
}
ajaxObj.setStatus(0);
ajaxObj.setShowMessage("微信code為空");
ajaxObj.setData("");
return ajaxObj.getJsonpData(request,response);
} catch (Exception e) {
//略
return ajaxObj.getJsonpData(request,response);
}
}
/**
* 過濾表情
* @param source
* @return
*/
public static String filterEmoji(String source) {
if(StringUtils.isNotEmpty(source)) {
Pattern emoji = Pattern.compile ("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]",Pattern.UNICODE_CASE | Pattern . CASE_INSENSITIVE ) ;
Matcher emojiMatcher = emoji.matcher(source);
if ( emojiMatcher.find()) {
source = emojiMatcher.replaceAll("*");
return source ;
}
return source;
}
return source;
}
第三步:重新整理access_token(如果需要)
由於access_token擁有較短的有效期,當access_token超時後,可以使用refresh_token進行重新整理,refresh_token有效期為30天,當refresh_token失效之後,需要使用者重新授權。
請求方法
獲取第二步的refresh_token後,請求以下連結獲取access_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引數
返回說明
正確時返回的JSON資料包如下:
{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }
引數 描述
access_token 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同
expires_in access_token介面呼叫憑證超時時間,單位(秒)
refresh_token 使用者重新整理access_token
openid 使用者唯一標識
scope 使用者授權的作用域,使用逗號(,)分隔
錯誤時微信會返回JSON資料包如下(示例為code無效錯誤):
{"errcode":40029,"errmsg":"invalid code"}
第四步:拉取使用者資訊(需scope為 snsapi_userinfo)
如果網頁授權作用域為snsapi_userinfo,則此時開發者可以通過access_token和openid拉取使用者資訊了。
請求方法
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://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2" ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
引數 描述
openid 使用者的唯一標識
nickname 使用者暱稱
sex 使用者的性別,值為1時是男性,值為2時是女性,值為0時是未知
province 使用者個人資料填寫的省份
city 普通使用者個人資料填寫的城市
country 國家,如中國為CN
headimgurl 使用者頭像,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),使用者沒有頭像時該項為空。若使用者更換頭像,原有頭像URL將失效。
privilege 使用者特權資訊,json 陣列,如微信沃卡使用者為(chinaunicom)
unionid 只有在使用者將公眾號繫結到微信開放平臺帳號後,才會出現該欄位。
錯誤時微信會返回JSON資料包如下(示例為openid無效):
{"errcode":40003,"errmsg":" invalid openid "}
附:檢驗授權憑證(access_token)是否有效
請求方法
http:GET(請使用https協議) https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID
引數說明
引數 描述
access_token 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同
openid 使用者的唯一標識
返回說明
正確的JSON返回結果:
{ "errcode":0,"errmsg":"ok"}
錯誤時的JSON返回示例:
{ "errcode":40003,"errmsg":"invalid openid"}
案例:
public class WeiXinUtil {
//網頁授權獲取使用者資訊
public static final String GET_SCOPE_USER = "https://api.weixin.qq.com/sns/userinfo";
/**
* 根據openid獲取使用者資訊
* @param access_token
* @param open_id
* @return
*/
public static WeiXinUserResponse getScopeUsersDetail(String access_token,String open_id){
WeiXinUserResponse tb=null;
try{
String url = GET_SCOPE_USER+"?access_token=" + access_token+"&openid="+open_id+"&lang=zh_CN";
//URLConnectionUtil 可以通過編寫httpclient來替換
String result = URLConnectionUtil.getHttps(url);
if(StringUtils.isNotEmpty(result)){
JSONObject jo=JSONObject.fromObject(result);
if(!jo.containsKey("errcode")){
jo.remove("privilege");
tb=(WeiXinUserResponse)JSONObject.toBean(jo, WeiXinUserResponse.class);
}
}
}catch(Exception e){
e.printStackTrace();
}
return tb;
}
}
import java.io.Serializable;
import org.apache.ibatis.type.Alias;
@Alias("WeiXinUserResponse")
public class WeiXinUserResponse implements Serializable{
private static final long serialVersionUID = 1L;
private String openid;// 使用者的唯一標識
private String nickname;// 使用者暱稱
private String sex;// 使用者的性別,值為1時是男性,值為2時是女性,值為0時是未知
private String province;// 使用者個人資料填寫的省份
private String city;// 普通使用者個人資料填寫的城市
private String country;// 國家,如中國為CN
private String headimgurl;// 使用者頭像,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),使用者沒有頭像時該項為空。若使用者更換頭像,原有頭像URL將失效。
private String unionid;//
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getHeadimgurl() {
return headimgurl;
}
public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}
public String getUnionid() {
return unionid;
}
public void setUnionid(String unionid) {
this.unionid = unionid;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
3、公眾號網頁測試
下載安裝後工具介面如下:
如果除錯的時候,發現提示”未繫結開發者賬號”,需要到微信公眾平臺中“開發者工具”(名稱忘記了,好像是這個)去繫結自己的微信賬號。
在繫結過程中提示 要繫結的微信的 需要關注”公眾平臺安全助手”,然後開啟自己的手機,登入自己的微信,進入”發現”頻道–>在搜尋欄輸入”公眾平臺安全助手”,然後進行關注。
然後在回到公眾平臺管理後臺,進行設定。設定完成後,發現還需要進入自己的手機上的”公眾平臺安全助手”中接受並同意邀請。
介面類似: