转自:http://www.cuiyongzhi.com/post/78.html
好长时间没有写文章了,主要是最近的工作和生活上的事情比较多而且繁琐,其实到现在我依然还是感觉有些迷茫,最后还是决定静下心来坚持一开始的选择,继续我们的微信系列文章的后续更新,也希望在自己有时间的时候能把更多的内容呈现给大家,前面一系列的文章讲述了很多微信开发相关的基础知识点 【微信系列文章】,那么从这一篇开始将讲述微信较深一层次或者说在产品应用中时刻会用到的一些技术点,那么下面就让我们进入正题吧,这一篇我要讲述的是在微信服务号开发中常用到的微信网页授权(OAuth2.0授权)获取用户基本信息!
我们来说下OAuth2.0授权的一些应用场景,例如:示例①希望用户在点击打开一个网页的时候可以获取该用户的微信昵称,微信头像等;示例②:当某个用户在我们会员系统中,希望用户在直接打开网页的时候展示用户在我们的会员系统中的积分情况和消费信息等...,那么这一系列的应用场景中我们是如何在一个网页中获取微信用户的相关信息的呢?这里我们就要用到微信网页授权的功能来解决我们遇到的难题了!
(一)首先在我们开始应用OAuth2.0授权之前我们先来了解一下它,看他本身的实现流程和机制!
①关于网页授权回调域名的说明
在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的开发者中心页配置授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
授权回调域名配置规范为全域名,比如需要网页授权的域名为: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鉴权
如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可
②关于网页授权的两种scope的区别说明
以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
③关于网页授权access_token和普通access_token的区别(参见:微信开发中的token获取)
微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息;
其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。
④关于UnionID机制(参见:开发中微信公众平台/开放平台/商户平台的关联)
请注意,网页授权获取用户基本信息也遵循UnionID机制。即如果开发者有在多个公众号,或在公众号、移动应用之间统一用户帐号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定公众号后,才可利用UnionID机制来满足上述需求。
UnionID机制的作用说明:如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为同一用户,对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号),unionid是相同的。
⑤关于特殊场景下的静默授权
上面已经提到,对于以snsapi_base为scope的网页授权,就静默授权的,用户无感知;
对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。
(二)OAuth2.0授权获取用户微信信息的具体实现
在整个的授权获取用户信息的流程中可以分为以下几步:
开发前的授权域名配置
引导用户进入授权页面同意授权,获取code
通过code换取网页授权access_token(与基础支持中的access_token不同)并通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
①开发前的授权域名配置
在进行代码实现之前我们需要配置安全授权域名,具体安全域名的配置目录:【微信公众平台】——>【接口权限】——>【网页授权获取用户基本信息】,简单如下图:
②引导用户进入授权页面同意授权,获取code
这一步在整个的网页授权过程中是非常重要的一步,因为只有引导用户授权获取到code才能开始后面信息的获取,在这里需要注意的是我们在配置授权链接中的redirect_uri必须是我们在第一步中配置的安全域名,参考连接如下:
1 2 3 4 | Scope为snsapi_base Scope为snsapi_userinfo https: //open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=redirect_uri&response_type=code&scope=snsapi_userinfo&state=2#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重定向时候,必须带此参数 |
注:code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期!
因为oauth2.0授权机制是在腾讯机器会将最终的url带上code中转到我们所配置的redirect_uri上,所以在我们的服务端我们需要在Controller加入一个weiXinOauth方法用于接收腾讯服务器中转过来的参数code和state,简单实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * @Description: 微信授权登录 * @param @param request * @param @param response * @param @param code * @param @param state * @author dapengniao * @date 2016年4月26日 上午9:40:18 */ @RequestMapping ( "weixinOauth" ) public void weiXinOauth(HttpServletRequest request, HttpServletResponse response, @RequestParam (value = "code" , required = true ) String code, @RequestParam (value = "state" , required = true ) String state) { System.out.println( "Code=============" +code+ "==========state=======" +state); } |
此时到这里我们对code的获取就完成了,下面继续下一步操作;
③通过code换取网页授权access_token(与基础支持中的access_token不同)并通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
在前面我们通过方法获取到了在授权中需要用到的code,那么我们接下来要做的就是通过code获取token+openid,这里如果我们采用的是snsapi_userinfo的方式授权的话,那么后面我们可以通过token+openid获取用户信息了,在这里我写了一个实用的方法OauthCode_GetUseInfo来实现这些步骤,简单代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | package com.cuiyongzhi.wechat.common; import java.util.HashMap; import com.cuiyongzhi.web.util.GlobalConstants; import com.cuiyongzhi.wechat.util.HttpUtils; import net.sf.json.JSONObject; /** * ClassName: OauthCode_GetUseInfo * @Description: 用户oauth2.0授权登录 通过code获取用户真实信息 * @author dapengniao * @date 2016年4月26日 上午9:54:55 */ public class OauthCode_GetUseInfo { private String openid; private String access_token; private String code; private String unionid; private HashMap<String, String> params = new HashMap<String, String>(); public OauthCode_GetUseInfo(String code) { this .code = code; params.put( "appid" , GlobalConstants.getInterfaceUrl( "appid" )); params.put( "secret" , GlobalConstants.getInterfaceUrl( "AppSecret" )); } /** * * @param @return hashmap {subscribe是否关注 0没有关注,1关注 unionid openid nickname昵称 * sex性别 province省份 city城市 headimgurl图像地址} * @param @throws Exception * @author dapengniao * @date 2016年4月26日 上午9:54:55 */ public HashMap<String, String> getUserInfo() throws Exception { // 将用户信息获取拼装成map // 通过code获取access_token,openid,unionid params.put( "code" , code); params.put( "grant_type" , "authorization_code" ); String tokenrs = HttpUtils.sendGet( GlobalConstants.getInterfaceUrl( "OauthCodeUrl" ), params); System.out.println( "tokenrs======================" +tokenrs); access_token = JSONObject.fromObject(tokenrs).getString( "access_token" ); openid = JSONObject.fromObject(tokenrs).getString( "openid" ); unionid = JSONObject.fromObject(tokenrs).getString( "unionid" ); // 通过用户openid信息获取用户详细信息 params.clear(); params.put( "access_token" , access_token); params.put( "openid" , openid); params.put( "lang" , "zh_CN" ); String useinfors = HttpUtils.sendGet( GlobalConstants.getInterfaceUrl( "OauthInfoUrl" ), params); // 通过用户的openid判断用户是否关注公众账号 params.clear(); params.put( "access_token" , GlobalConstants.getInterfaceUrl( "access_token" ) ); params.put( "openid" , openid); params.put( "lang" , "zh_CN" ); String subscribers = "" ; subscribers = HttpUtils.sendGet( GlobalConstants.getInterfaceUrl( "SubscribeUrl" ), params); // 将用户信息获取拼装成map System.out.println(subscribers); params.clear(); params.put( "subscribe" , JSONObject.fromObject(subscribers).getString( "subscribe" )); params.put( "unionid" , unionid); params.put( "openid" , openid); params.put( "nickname" , JSONObject.fromObject(useinfors).getString( "nickname" )); params.put( "sex" , JSONObject.fromObject(useinfors).getString( "sex" )); params.put( "province" , JSONObject.fromObject(useinfors).getString( "province" )); params.put( "city" , JSONObject.fromObject(useinfors).getString( "city" )); params.put( "headimgurl" , JSONObject.fromObject(useinfors).getString( "headimgurl" )); return params; } /** * @Description: 通过openid获取用户信息 * @param @param openid * @param @return * @param @throws Exception * @author dapengniao * @date 2016年4月26日 上午9:53:40 */ public static HashMap<String, String> Openid_userinfo(String openid) throws Exception { HashMap<String, String> params = new HashMap<String, String>(); params.put( "access_token" , GlobalConstants.getInterfaceUrl( "access_token" )); params.put( "openid" , openid); params.put( "lang" , "zh_CN" ); String subscribers = HttpUtils.sendGet( GlobalConstants.getInterfaceUrl( "SubscribeUrl" ), params); params.clear(); params.put( "nickname" , JSONObject.fromObject(subscribers).getString( "nickname" )); params.put( "headimgurl" , JSONObject.fromObject(subscribers).getString( "headimgurl" )); params.put( "sex" , JSONObject.fromObject(subscribers).getString( "sex" )); return params; } @SuppressWarnings ( "unused" ) private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000 ); } } |
通过方法我们只需要传入code,就可以将个人的用户信息以Map的形式返回,用于我们在前端的使用,在这里我们将Controller的代码做简单的修改,用于对OauthCode_GetUseInfo中方法对用户信息的获取,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | /** * @Description: 微信授权登录 * @param @param request * @param @param response * @param @param code * @param @param state * @author dapengniao * @date 2016年4月26日 上午9:40:18 */ @RequestMapping ( "weixinOauth" ) public void weiXinOauth(HttpServletRequest request, HttpServletResponse response, @RequestParam (value = "code" , required = true ) String code, @RequestParam (value = "state" , required = true ) String state) { System.out.println( "Code=============" +code+ "==========state=======" + state); try { // 用code取得微信用户的基本信息 OauthCode_GetUseInfo weixin = new OauthCode_GetUseInfo(code); Map<String, String> wmap = weixin.getUserInfo(); System.out.println( "用户昵称=================================" + wmap.get( "nickname" )); } catch (Exception e) { logger.error(e.toString(), e); } } |