微信公眾平臺—使用者管理(獲取使用者基本資訊)
play 框架中的使用
網頁授權獲取使用者基本資訊
如果使用者在微信客戶端中訪問第三方網頁,公眾號可以通過微信網頁授權機制,來獲取使用者基本資訊,進而實現業務邏輯
- 關於網頁授權回撥域名的說明
1、在微信公眾號請求使用者網頁授權之前,開發者需要先到公眾平臺官網中的開發者中心頁配置授權回撥域名。請注意,這裡填寫的是域名(是一個字串),而不是URL,因此請勿加http://等協議頭;
2、授權回撥域名配置規範為全域名,比如需要網頁授權的域名為:www.qq.com,配置以後此域名下面的頁面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以進行OAuth2.0鑑權。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com無法進行OAuth2.0鑑權
3、如果公眾號登入授權給了第三方開發者來進行管理,則不必做任何設定,由第三方代替公眾號實現網頁授權即可
2 . 關於網頁授權的兩種scope的區別說明
1、以snsapi_base為scope發起的網頁授權,是用來獲取進入頁面的使用者的openid的,並且是靜默授權並自動跳轉到回撥頁的。使用者感知的就是直接進入了回撥頁(往往是業務頁面)
2、以snsapi_userinfo為scope發起的網頁授權,是用來獲取使用者的基本資訊的。但這種授權需要使用者手動同意,並且由於使用者同意過,所以無須關注,就可在授權後獲取該使用者的基本資訊。
3 、使用者管理類介面中的“獲取使用者基本資訊介面”,是在使用者和公眾號產生訊息互動或關注後事件推送後,才能根據使用者OpenID來獲取使用者基本資訊。這個介面,包括其他微信介面,都是需要該使用者(即openid)關注了公眾號後,才能呼叫成功的。
3 . 關於特殊場景下的靜默授權
1、上面已經提到,對於以snsapi_base為scope的網頁授權,就靜默授權的,使用者無感知;
2、對於已關注公眾號的使用者,如果使用者從公眾號的會話或者自定義選單進入本公眾號的網頁授權頁,即使是scope為snsapi_userinfo,也是靜默授權,使用者無感知。
4 . 具體而言,網頁授權流程分為四步:
1、引導使用者進入授權頁面同意授權,獲取code
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 的格式在傳遞過程中可能會編譯成其他亂碼格式具體的變化可以參照log 和 localhost_access_log(訪問日誌)
跳轉回調redirect_uri,應當使用https連結來確保授權code的安全性。
跳轉回調redirect_uri,應當使用https連結來確保授權code的安全性。
code說明 :
code作為換取access_token的票據,每次使用者授權帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動過期。
2、通過code換取網頁授權access_token(與基礎支援中的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
3、如果需要,開發者可以重新整理網頁授權access_token,避免過期
由於access_token擁有較短的有效期,當access_token超時後,可以使用refresh_token進行重新整理,refresh_token擁有較長的有效期(7天、30天、60天、90天),當refresh_token失效的後,需要使用者重新授權 具體參考文件
4、通過網頁授權access_token和openid獲取使用者基本資訊(支援UnionID機制)
如果網頁授權作用域為snsapi_userinfo,則此時開發者可以通過access_token和openid拉取使用者資訊了。
請求方法
http:GET(請使用https協議)
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
參考程式碼:
/**
* 授權回撥,得到微信使用者相關資訊類
*/
public class WeChatWebOAuthManageService extends GongZhongObject {
public static String getBaseOauth2Url(String redirectUri, String state) {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WeChatGongZhongService.appId
+ "&redirect_uri=" + redirectUri + "&response_type=code&scope=snsapi_base&state=" + state
+ "#wechat_redirect";
return url;
}
public static String getUserinfoOauth2Url(String redirectUri, String state) {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WeChatGongZhongService.appId
+ "&redirect_uri=" + redirectUri + "&response_type=code&scope=snsapi_userinfo&state=" + state
+ "#wechat_redirect";
return url;
}
public static OauthAccessToken getAccessToken(String code) {
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + WeChatGongZhongService.appId
+ "&secret=" + WeChatGongZhongService.appSecret + "&code=" + code + "&grant_type=authorization_code";
JSONObject result = WeChatUtil.httpRequest(url, "GET", null);
return (OauthAccessToken) JSONUtils.toBean(result, OauthAccessToken.class);
}
public static OauthAccessToken refreshAccessToken(String refreshToken) {
String result = GongZhongUtils.sendPost("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="
+ WeChatGongZhongService.appId + "&grant_type=refresh_token&refresh_token=" + refreshToken, "");
return (OauthAccessToken) JSONUtils.toBean(JSONObject.fromObject(result), OauthAccessToken.class);
}
public static UserInfo getUserInfo(String accessToken, String openId) {
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId;
JSONObject result = WeChatUtil.httpRequest(url, "GET", null);
return (UserInfo) JSONUtils.toBean(result, UserInfo.class);
}
}
在某個操作之前要進行授權認證時可以直接按以下方式跳轉
回撥URL 在配置檔案中有配置
wechat.redirect_uri=http://123.56.68.163**/wechat/others/userOAuth**
業務中呼叫
String url = WeChatWebOAuthManageService.getUserinfoOauth2Url(Constants.REDIRECT_URI+encode,"50");
redirect(url);
注意:可以請求轉發,但是不可以在程式碼裡直接請求(微信本身特性不允許)
String url = WeChatWebOAuthManageService.getUserinfoOauth2Url(Constants.REDIRECT_URI+encode,"50");
WeChatUtil.httpRequest(url, "GET", "");
回撥 URL /wechat/others/userOAuth
package controllers.wechat.others;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.shove.gateway.weixin.gongzhong.vo.menu.Menu;
import com.shove.gateway.weixin.gongzhong.vo.weboauth.OauthAccessToken;
import com.shove.gateway.weixin.gongzhong.vo.weboauth.UserInfo;
import business.BackstageSet;
import business.User;
import business.WeChatMenu;
import constants.Constants;
import controllers.WeiXinController;
import controllers.mobile.Mobile;
import controllers.wechat.account.WechatAccountHome;
import controllers.wechat.activitycenter.ActivityCenter;
import controllers.wechat.service.InvestAction;
import controllers.wechat.service.PacketAction;
import controllers.wechat.service.RegistAndLogin;
import models.SubUserInfo;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import play.Logger;
import services.WeChatGongZhongService;
import services.WeChatMenuManageService;
import services.WeChatReceiveMessageService;
import services.WeChatWebOAuthManageService;
import utils.ErrorInfo;
import utils.WeChatUtil;
public class WeChatFrontGateway extends WeiXinController {
/**
* 微信介面和使用者進行溝通入口,包括使用者傳送訊息,點選click事件
*
* @throws Exception
*/
public static void microMessageEntrance() throws Exception {
WeChatReceiveMessageService weChatReceiveMessageService = new WeChatReceiveMessageService();
// 執行方法
WeChatGongZhongService.execute(weChatReceiveMessageService);
}
/**
* 授權回撥地址。 微信端使用者點選view選單事件,回撥到這個地址。使用者必須先授權(得到使用者的openId),才能判定使用者是否繫結
*/
public static void userOAuth() {
String code = params.get("code");
String state = params.get("state");
if (null != state && state.contains("#")) {
Logger.info("轉換state,state包含#:%s", state);
state = state.split("#")[0];
Logger.info("轉換state之後:%s", state);
} else {
Logger.info("state不包含#:%s", state);
}
Logger.info("code:%s", code);
// 同意授權
if (!"authdeny".equals(code)) {
try {
Logger.info("%s", "獲取oauthAccessToken之前");
OauthAccessToken oauthAccessToken = WeChatWebOAuthManageService
.getAccessToken(code);
// 獲取網頁訪問憑證
String accessToken = oauthAccessToken.getAccess_token();
// 獲取網頁使用者標識
String openId = oauthAccessToken.getOpenid();
Logger.info("openId:%s", openId);
// 獲取使用者資訊
UserInfo userInfo = WeChatWebOAuthManageService.getUserInfo(accessToken, openId);
SubUserInfo subUserInfo = new SubUserInfo().setUserInfo(userInfo);
// 查詢使用者是否繫結
boolean flag = User.isBind(openId, new ErrorInfo());
int opt = Integer.parseInt(state);
if (flag) {
/*
* 防止使用者綁定了,再繼續點選繫結,那麼就讓使用者跳到解除繫結頁面
*/
if (41 == opt) {
// 如果綁定了,還點選繫結按鈕,那麼跳到解除繫結頁面
// flash.error("您的微訊號已經繫結過賬號,不能再次繫結");
// WechatAccountHome.errorShow();
// 解綁方法
Logger.info("%s", "使用者正在解綁");
// 解綁過程中對OpenID進行Encrypt.encrypt3DES(arg0, arg1)加密
openId = WeChatUtil.encryptOpenId(openId);
RegistAndLogin.unBoundUser(openId);
}
/*
* 解綁
*/
if (42 == opt) {
// 解綁方法
Logger.info("%s", "使用者正在解綁");
// 解綁過程中對OpenID進行Encrypt.encrypt3DES(arg0, arg1)加密
openId = WeChatUtil.encryptOpenId(openId);
RegistAndLogin.unBoundUser(openId);
}
ErrorInfo error = new ErrorInfo();
Map<String, String> map = User
.findAccountAndPasswordByOpenId(openId, error);
String account = map.get("account");
String password = map.get("password");
// 如果繫結,幫助當前使用者直接登入(這裡已經放在快取中去了)
User user = new User();
Logger.debug("account=%s", account);
user.name = account;
user.setName(account);
// 以加密形式登入
user.login(password, true, Constants.CLIENT_WECHAT, error);
BackstageSet.getCurrentBackstageSet();
// 其它按鈕,都是跳轉到指定的頁面
switch (opt) {
case 11:
RegistAndLogin.register("","");
break;
.....
case 50:
String mobile = params.get("mobile");
String orderNum = params.get("orderNum");
//將userInfo 快取起來
WeChatUtil.cacheUserInfo(subUserInfo);
//將user 快取起來
WeChatUtil.cacheUser(user);
WeChatUtil.cacheOpenId(openId);
PacketAction.sendPacket(mobile, Long.valueOf(orderNum), openId, subUserInfo, user);
break;
}
} else {
/*
* 如果使用者沒有繫結微信賬號,將使用者的openId快取起來,快取時間2小時
* @Add by fchunbo
*/
if(openId != null){
WeChatUtil.cacheOpenId(openId);
Logger.info("使用者的OpenId已快取:%s",openId);
}
if (42 == opt) {
/*
* 如果使用者已經解綁,再點選解綁,那麼跳轉到繫結介面
*/
Logger.info("%s", "使用者正在繫結");
// 繫結過程中對OpenID進行Encrypt.encrypt3DES(arg0, arg1)加密
openId = WeChatUtil.encryptOpenId(openId);
RegistAndLogin.bindUser(openId);
}
if (41 == opt) {
// 如果是繫結按鈕,則繫結
Logger.info("%s", "使用者正在繫結");
// 繫結過程中對OpenID進行Encrypt.encrypt3DES(arg0, arg1)加密
openId = WeChatUtil.encryptOpenId(openId);
RegistAndLogin.bindUser(openId);
} else {
// 使用者沒有繫結,如果是必須要登入的操作,那麼必須先登入
switch (opt) {
case 50:
//直接跳轉到輸手機號領取紅包頁面
String mobile = params.get("mobile");
String orderNum = params.get("orderNum");
Logger.debug("flag=%s,opt=50,mobile=%s,orderNum=%s,userinfo=%s", flag, mobile, orderNum, userInfo);
//將userInfo 快取起來
WeChatUtil.cacheUserInfo(subUserInfo);
WeChatUtil.cacheOpenId(openId);
PacketAction.grabPacketPageAfter(mobile, orderNum,userInfo);
}
// 其餘的必須先進行登入操作,這時看使用者有沒有登入,如果快取中有值,說明使用者已經登入過了
User user = User.currUser();
if (null != user) {
switch (opt) {
// 這些操作必須要進行登入操作
case 21:
WechatAccountHome.accountInfo();
break;
case 22:
WechatAccountHome.myLoanBids(0, null,
Constants.WECHAT_CURRPAGE, 0);
break;
case 23:
WechatAccountHome.myInvestBids(0,
Constants.WECHAT_CURRPAGE, null, 0);
break;
case 24:
WechatAccountHome.transferDebts(
Constants.WECHAT_CURRPAGE,
Constants.WECHAT_PAGESIZE, null, null,
null, 0);
break;
}
}
// 如果快取中沒有值,則必須先登入
RegistAndLogin.login();
}
}
} catch (Exception e) {
Logger.error("獲取訪問憑證時%s", e.getMessage());
WechatAccountHome.errorShow(e.getMessage());
}
} else {
// 使用者不同意授權,那麼跳轉到錯誤提示頁面
WechatAccountHome.errorShow("您不同意授權");
}
}
/**
* 微信多客服端外掛
*/
public static void plugin() {
render("wechat/plugin.html");
}
/**
* 客服端查詢使用者資訊
*
* @param openId
*/
public static void userInformation(String openId) {
ErrorInfo error = new ErrorInfo();
JSONObject json = new JSONObject();
User user = User.getUserInformation(openId, error);
if (error.code < 0 || null == user) {
json.put("code", error.code);
json.put("msg", error.msg);
renderJSON(json);
}
json.put("user", user);
renderJSON(json);
}
/**
* 初始化選單
*/
public static void createMenu() {
ErrorInfo error = new ErrorInfo();
WeChatMenu.createMenu(error);
renderText("建立成功");
}
/**
* 查詢選單
*/
public static void queryMenu() {
List<Menu> list = WeChatMenuManageService.getMenu();
renderJSON(JSONArray.fromObject(list));
}
/**
* JS-SDK,獲取ticket等
*/
public static void sign(){
String url = params.get("url");
Map<String,String> tickets = WeChatGongZhongService.sign(url);
renderJSON(JSONObject.fromObject(tickets));
}
/**
* 測試二維碼入口,暫時沒用
*/
public static void qrcode() {
String key = params.get("key");
if (null == key || key.equals("")) {
renderText("不合法的請求");
} else {
if (key.equals("1")) {
// 得到永久二維碼ticket
String ticket = WeChatGongZhongService.createLimitQrcode(
"sp2p2weixin", 928);
// 建立二維碼並返回路徑。
try {
String direction = WeChatGongZhongService
.getQrcodeByTicket("public/", ticket);
renderText(direction);
} catch (IOException e) {
e.printStackTrace();
}
} else if (key.equals("2")) {
// 得到有時間限制的二維碼ticket
String ticket = WeChatGongZhongService.createTempQrcode(
"sp2pweixin", 999);
// 建立二維碼並返回路徑。
try {
String direction = WeChatGongZhongService
.getQrcodeByTicket("public/", ticket);
renderText(direction);
} catch (IOException e) {
e.printStackTrace();
}
} else {
renderText("不合法的請求");
}
}
}
}
發授權請求時帶有code 引數,這時可以根據不同的值進行不同的操作