本文旨在提供一个Java开发微信公众号服务器的一个入门级别教程,由于本人水平有限,文中若出现错误欢迎同学们指正!作者邮箱[email protected]。本文欢迎转载,但请注明出处来自SteinsGate博客!
俗话说的好,什么教程都比不上官方开发者文档。所以,微信开发者开发文档在此:开发者文档
目录
- 网页授权获取用户信息
- 根据openid获取用户信息
一、网页授权获取用户信息
1、网页授权的说明
网页授权 即用户在微信客户端访问第三方网页时,可以通过网页授权机制,来获取用户的基本信息,从而实现业务逻辑。例如登陆、注册等。
openid 即用户相对于一个公众号来说的唯一标识,可以理解成在此公众号内的用户ID
配置回调域名
在微信公众号使用网页授权之前,开发者需要到公众平台的 开发 > 接口权限 菜单中的网页授权获取用户基本信息栏配置授权回调域名,且只需要填写域名,不需要加http://等协议头。
回调域名必须为全域名。比如配置的域名为www.baidu.com,配置以后此域名下的页面http://www.baidu.com/code.html、http://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 params = new HashMap();
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 params = new HashMap();
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 params = new HashMap();
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);
}
}
二、根据openid获取用户信息
使用此接口的前提是已有用户的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);
}
}