Java微信公众号开发 - 获取用户信息

本文旨在提供一个Java开发微信公众号服务器的一个入门级别教程,由于本人水平有限,文中若出现错误欢迎同学们指正!作者邮箱luo.xuelin@nclantuo.com。本文欢迎转载,但请注明出处来自SteinsGate博客!
俗话说的好,什么教程都比不上官方开发者文档。所以,微信开发者开发文档在此:开发者文档

目录

<a name="part1">一、网页授权获取用户信息</a>

1、网页授权的说明

网页授权 即用户在微信客户端访问第三方网页时,可以通过网页授权机制,来获取用户的基本信息,从而实现业务逻辑。例如登陆、注册等。
openid 即用户相对于一个公众号来说的唯一标识,可以理解成在此公众号内的用户ID

配置回调域名
在微信公众号使用网页授权之前,开发者需要到公众平台的 开发 > 接口权限 菜单中的网页授权获取用户基本信息栏配置授权回调域名,且只需要填写域名,不需要加http://等协议头。
回调域名必须为全域名。比如配置的域名为www.baidu.com,配置以后此域名下的页面http://www.baidu.com/code.htmlhttp://www.baidu.com/test/login.html都可以进行网页授权。但http://tieba.baidu.com之类的无法进行网页授权。

网页授权两种scope的区别
snsapi_base 为scope发起的网页授权,是用来获取进入页面用户的openid的,并且是静默授权并自动跳转到回调页面。用户并不会感知到授权过程直接进入了第三方的页面。
snsapi_userinfo 为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动确认,并且由于用户确认过,所以开发者也可以获取到未关注用户的基本信息。

特殊场景下的静默授权
上面已提到,对于已snsapi_base为scope的网页授权,就静默授权,用户不会感知到。
对于已关注公众号的用户,如果是从公众号的会话或者是菜单进入的网页授权页面,即使scope是snsapi_userinfo,也是静默授权。

网页授权access_token与普通access_token的区别
网页授权是通过Oauth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭据(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息。
其他的微信接口,需要通过 获取access_token 接口来获取普通的access_token

2、网页授权的流程

网页授权的流程
网页授权的流程大致可以分为以下五步:

  • 生成网页授权URL
  • 引导用户进入授权页面并同意授权,获取code
  • 通过code换去网页授权access_token
  • 如果需要,开发者可以刷新网页授权access_token,避免过期
  • 通过网页授权access_token和openid获取用户基本信息

2.1、生成网页授权URL
接口地址

https://open.weixin.qq.com/connect/oauth2/authorize?
参数 是否必须 说明
appid 公众号的唯一标识
redirect_uri 授权后重定向的回调链接地址,请使用urlencode对链接进行处理
response_type 返回类型,填code
scope snsapi_userinfo、snsapi_base
state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
wechat_redirect 无论直接打开还是做页面302重定向时候,必须带此参数

一个完整的网页授权URL如下:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd91aa2e2fab6&redirect_uri=http%3A%2F%2Fwww.baidu.com&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

当用户点进入URL时就会进入授权界面,如下:

2.2、用户同意授权后
如果用户同意授权,页面就会跳转到redirect_uri

code:code作为换取access_token的票据,每次用户授权带上的code是不一样的,code只能用一次,且5分钟未使用自动过期。

跳转到重定向地址后,开发者可以直接在请求参数中获取到code。

2.3、code换取access_token

此处获取到的access_token与其他接口需要使用到的access_token是不同的。如果网页授权使用的scope是snsapi_base,则在此处获取到access_token的同时,也获取到了openid,snsapi_base的流程也到此结束。

请求地址

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",    
 "expires_in":7200,    
 "refresh_token":"REFRESH_TOKEN",    
 "openid":"OPENID",    
 "scope":"SCOPE" 
}
参数 说明
access_token 网页授权接口调用凭证
expires_in access_token接口调用凭证超时时间,单位(秒)
refresh_token 用户刷新access_token需要用到的token
openid 用户唯一标识
scope 用户授权的作用域

示例代码

String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
Map<String, String> params = new HashMap<String, String>();
params.put("APPID", appid);
params.put("SECRET", appsecret);
params.put("CODE", code);

JSONObject json = HttpUtil.get(StringFormat.format(url, params));
if(json != null){
    boolean isErr = json.containsKey("errcode");
    if(isErr){//请求失败
        String errCode = json.getString("errcode");
        String errMsg = json.getString("errmsg");
    }else{
        String accessToken = json.getString("access_token");
        int expires = json.getInt("expires_in");
        String refreshToken = json.getString("refresh_token");
        String openid = json.getString("openid");
        String scope = json.getString("scope");
    }
}

2.4、验证access_token是否有效
请求地址

https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID 

参数说明

参数 是否必须 说明
access_token 网页授权接口调用凭证
openid 用户的唯一标识

返回说明
请求正确时的返回数据

{ "errcode":0,"errmsg":"ok"}

示例代码

String url = "https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID";
Map<String, String> params = new HashMap<String, String>();
params.put("ACCESS_TOKEN", access_token);
params.put("OPENID", openid);

JSONObject json = HttpUtil.get(StringFormat.format(url, params));
if(json != null){
    String errCode = json.getString("errcode");
    String errMsg = json.getString("errmsg");
}

2.5、刷新access_token(若有必要)

由于access_token有效期比较短,当access_token失效后,可以使用refresh_token进行刷新,refresh_token的有效期为30天。refresh_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 填写获取到的refresh_token参数

返回说明
请求正确时的返回数据

{ 
 "access_token":"ACCESS_TOKEN",  
 "expires_in":7200,   
 "refresh_token":"REFRESH_TOKEN",   
 "openid":"OPENID",   
 "scope":"SCOPE" 
}
参数 说明
access_token 网页授权接口调用凭证
expires_in access_token接口调用凭证超时时间,单位(秒)
refresh_token 用户刷新access_token需要用到的token
openid 用户唯一标识
scope 用户授权的作用域

示例代码

String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
Map<String, String> params = new HashMap<String, String>();
params.put("APPID", appid);
params.put("REFRESH_TOKEN", refresh_token);

JSONObject json = HttpUtil.get(StringFormat.format(url, params));
if(json != null){
    boolean isErr = json.containsKey("errcode");
    if(isErr){//请求失败
        String errCode = json.getString("errcode");
        String errMsg = json.getString("errmsg");
    }else{
        String accessToken = json.getString("access_token");
        int expires = json.getInt("expires_in");
        String refreshToken = json.getString("refresh_token");
        String openid = json.getString("openid");
        String scope = json.getString("scope");
    }
}

2.6、获取用户信息(当scope为snsapi_userinfo)
请求地址

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN 

参数说明

参数 是否必须 说明
access_token 网页授权接口调用凭证
openid 用户的唯一标识
lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

返回说明
请求正确时的返回数据

{
 "openid":"OPENID",  
 "nickname":"NICKNAME",   
 "sex":"1",   
 "province":"PROVINCE"   
 "city":"CITY",   
 "country":"COUNTRY",    
 "headimgurl":"http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ
4eMsv84eavHiaiceqxibJxCfHe/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 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。

示例代码

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("https://api.weixin.qq.com/sns/userinfo?access_token="+ access_token +"&openid="+ openid +"&lang="+ lang +"");
CloseableHttpResponse response = httpclient.execute(httpget);
JSONObject json = null;
try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == HttpStatus.SC_OK) {
        HttpEntity entity = response.getEntity();
        InputStream inStream = entity.getContent(); // 获取请求参数数据
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        
        outStream.close();
        inStream.close();
        String j = new String(outStream.toByteArray(), "utf-8");
        json = JSONObject.fromObject(j);
        System.out.println(result);
    }
} finally {
    response.close();
}
if(json != null){        
    
    boolean isErr = json.containsKey("errcode");
    if(isErr){//请求失败
        System.out.println(json);
    }else{
        System.out.println(json);
    }
}

<a name="part2">二、根据openid获取用户信息</a>

使用此接口的前提是已有用户的openid作为参数。
需要注意的是,此处用到的access_token与网页授权中用到的access_token是完全不同的。此处access_token的获取方式参见Java微信公众号开发 - 开始接入

接口地址

https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN 

参数说明

参数 是否必须 说明
access_token 微信接口全局调用凭证
openid 用户的唯一标识
lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

返回说明

请求正确时的返回数据

{
   "subscribe": 1, 
   "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", 
   "nickname": "Band", 
   "sex": 1, 
   "language": "zh_CN", 
   "city": "广州", 
   "province": "广东", 
   "country": "中国", 
   "headimgurl":  "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4
eMsv84eavHiaiceqxibJxCfHe/0",
  "subscribe_time": 1382694957,
  "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
  "remark": "",
  "groupid": 0,
  "tagid_list":[128,2]
}
参数 说明
subscribe 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
openid 用户的标识,对当前公众号唯一
nickname 用户的昵称
sex 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
city 用户所在城市
country 用户所在国家
province 用户所在省份
language 用户的语言,简体中文为zh_CN
headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
subscribe_time 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
unionid 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
remark 公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注
groupid 用户所在的分组ID(兼容旧的用户分组接口)
tagid_list 用户被打上的标签ID列表

示例代码

String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=";
url = url + access + "&openid=" + openId + "&lang=" + lang;
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet(url);
CloseableHttpResponse response = httpclient.execute(httpget);
JSONObject json = null;
try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == HttpStatus.SC_OK) {
        HttpEntity entity = response.getEntity();
        InputStream inStream = entity.getContent(); // 获取请求参数数据
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        
        outStream.close();
        inStream.close();
        String j = new String(outStream.toByteArray(), "utf-8");
        json = JSONObject.fromObject(j);
    }
} finally {
    response.close();
}
if(json != null){
    boolean isErr = json.containsKey("errcode");
    if(isErr){//请求失败
        System.out.println(json);
    }else{
        System.out.println(json);
    }
}

推荐阅读更多精彩内容