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 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
发授权请求时带有code 参数,这时可以根据不同的值进行不同的操作