1.准备工作
移动应用微信登录是基于OAuth2.0协议标准 构建的微信OAuth2.0授权登录系统。
在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的移动应用,并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程。
2.授权流程说明
- 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
- 通过code参数加上AppID和AppSecret等,通过API换取access_token;
- 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。
3. 获取access_token时序图:
4. maven依赖
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
5.在application.yml文件中配置你的
#第三方微信登录(用你自己的)
#appID App的ID
#appSecret
weixinconfig:
weixinappID: wxf7865421a3c4d5f
weixinappSecret: 6cdbe6d4ce6sbcf0593c913d8a0ce12
创建配置类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix="weixinconfig")
public class WeixinLoginProperties {
private String weixinappID; // 商户appid
private String weixinappSecret; // 私钥 pkcs8格式的
public String getWeixinappID() {
return weixinappID;
}
public void setWeixinappID(String weixinappID) {
this.weixinappID = weixinappID;
}
public String getWeixinappSecret() {
return weixinappSecret;
}
public void setWeixinappSecret(String weixinappSecret) {
this.weixinappSecret = weixinappSecret;
}
}
6.第一步:请求CODE
这一步客户端会把code传过来 ,不用你操心
7.第二步:通过code获取access_token
package io.renren.api.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.configurationprocessor.json.JSONException;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import io.renren.api.dao.TpAccesstokenMapper;
import io.renren.api.dao.TpUsersMapper;
import io.renren.api.entity.TpAccesstoken;
import io.renren.api.entity.TpAccesstokenExample;
import io.renren.api.entity.TpAccumulativeAward;
import io.renren.api.entity.TpUsers;
import io.renren.api.entity.TpUsersExample;
import io.renren.api.properties.WeixinLoginProperties;
import io.renren.api.service.TpAccesstokenService;
import io.renren.api.service.TpAccumulativeAwardService;
import io.renren.api.service.TpUsersService;
import io.renren.common.utils.R;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
import javax.annotation.Resource;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
/**
* 第三方微信登录
* @author Administrator
*
*/
@SuppressWarnings("deprecation")
@Controller
@RequestMapping("/api")
public class WeXinController {
//微信公众平台申请
//应用唯一标识,在微信开放平台提交应用审核通过后获得 appID
//应用密钥AppSecret,在微信开放平台提交应用审核通过后获得 appSecret
//TpAccesstoken 用来保存微信返回的用户信息oppid等
@Resource
private WeixinLoginProperties weixinLoginProperties;
@Autowired
TpUsersService tpUsersService;
@Autowired
TpUsersMapper tpUsersMapper;
@Autowired
TpAccesstokenService tpAccesstokenService;
@Autowired
TpAccesstokenMapper tpAccesstokenMapper;
@Autowired
TpAccumulativeAwardService tpAccumulativeAwardService;
/**
* 获取accessToken,该步骤返回的accessToken期限为一个月
*
* @param code
* @return
* @throws Exception
*/
@SuppressWarnings("all")
@RequestMapping("weixincallback")
@ResponseBody
public R getAccessToken(String code) throws Exception {
String appID = weixinLoginProperties.getWeixinappID();
String appSecret = weixinLoginProperties.getWeixinappSecret();
String accesstoken;
String openid = null;
String refreshtoken;
int expiresIn;
String unionid;//可通过获取用户基本信息中的unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,
//用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。
if (code != null) {
System.out.println(code);
}
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appID+"&secret="+appSecret+"&code="+code+"&grant_type=authorization_code";
URI uri = URI.create(url);
org.apache.http.client.HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(uri);
HttpResponse response;
try {
response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
StringBuilder sb = new StringBuilder();
for (String temp = reader.readLine(); temp != null; temp = reader.readLine()) {
sb.append(temp);
}
JSONObject object = new JSONObject(sb.toString().trim());
System.out.println("object:"+object);
accesstoken = object.getString("access_token");
System.out.println("accesstoken:"+accesstoken);
openid = object.getString("openid");
System.out.println("openid:"+openid);
refreshtoken = object.getString("refresh_token");
System.out.println("refreshtoken:"+refreshtoken);
expiresIn = (int) object.getLong("expires_in");
unionid = object.getString("unionid");
// 将用户信息保存到数据库
//1.先查询用户是否是第一次第三方登录如果是第一次那么是将用户信息添加到数据库 如果不是那么是更新到数据库
TpUsers userInfo = getUserInfo(accesstoken,openid);
Integer userId = userInfo.getUserId();
TpAccesstokenExample example = new TpAccesstokenExample();
example.createCriteria().andOpenidEqualTo(openid);
List<TpAccesstoken> list = tpAccesstokenMapper.selectByExample(example);
if(list!=null&&list.size()>0) {
//那么该用户不是第一次 执行更新操作
TpAccesstoken tpAccesstoken = list.get(0);
tpAccesstoken.setAccesstoken(accesstoken);
tpAccesstoken.setUserId(userId);
tpAccesstoken.setExpiresIn(expiresIn);
tpAccesstoken.setOpenid(openid);
tpAccesstoken.setRefreshtoken(refreshtoken);
tpAccesstokenService.save(tpAccesstoken);
}else {
TpAccesstoken tpAccesstoken=new TpAccesstoken();
tpAccesstoken.setUserId(userId);
tpAccesstoken.setAccesstoken(accesstoken);
tpAccesstoken.setExpiresIn(expiresIn);
tpAccesstoken.setOpenid(openid);
tpAccesstoken.setRefreshtoken(refreshtoken);
tpAccesstokenService.save(tpAccesstoken);
//tpAccesstokenService.insertAccesstoken(userId,openid, accesstoken, expiresIn, refreshtoken);
}
//refreshAccessToken(openid);
System.out.println("Openid"+userInfo.getOpenid());
return R.ok().put("userInfo", userInfo).put("openid", openid);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return R.ok().put("openid", openid);
}
/*
*
* 1 { 2 "access_token":"ACCESS_TOKEN", 3 "expires_in":7200, 4
* "refresh_token":"REFRESH_TOKEN", 5 "openid":"OPENID", 6 "scope":"SCOPE", 7
* "unionid":"o6_bmasdasdsad6_2sgVt7hMZOPfL" 8 } 复制代码 复制代码 参数 说明 access_token
* 接口调用凭证 expires_in access_token 接口调用凭证超时时间,单位(秒) refresh_token
* 用户刷新access_token openid 授权用户唯一标识 scope 用户授权的作用域,使用逗号(,)分隔 unionid
* 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
*
*/
/**
* 刷新token
*
* @param openID
* @return
*/
@SuppressWarnings({ "unused", "resource" })
private void refreshAccessToken(String openid) {
String refreshtoken=null;
TpAccesstoken tpAccesstoken=new TpAccesstoken();
String appID = weixinLoginProperties.getWeixinappID();
String appSecret = weixinLoginProperties.getWeixinappSecret();
TpAccesstokenExample example = new TpAccesstokenExample();
example.createCriteria().andOpenidEqualTo(openid);
List<TpAccesstoken> list = tpAccesstokenMapper.selectByExample(example);
if(list!=null&&list.size()>0) {
tpAccesstoken = list.get(0);
refreshtoken = tpAccesstoken.getRefreshtoken();
}
String uri = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+appID+"&grant_type=refresh_token&refresh_token="+refreshtoken;
org.apache.http.client.HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(URI.create(uri));
try {
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
StringBuilder builder = new StringBuilder();
for (String temp = reader.readLine(); temp != null; temp = reader.readLine()) {
builder.append(temp);
}
JSONObject object = new JSONObject(builder.toString().trim());
String accessToken = object.getString("access_token");
String refreshToken = object.getString("refresh_token");
openid = object.getString("openid");
int expires_in = (int) object.getLong("expires_in");
tpAccesstoken.setAccesstoken(accessToken);
tpAccesstoken.setExpiresIn(expires_in);
tpAccesstoken.setOpenid(openid);
tpAccesstoken.setRefreshtoken(refreshToken);
tpAccesstokenService.save(tpAccesstoken);
}
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 根据accessToken获取用户信息
*
* @param accessToken
* @param openID
* @return
* @throws Exception
*/
@SuppressWarnings({ "unused", "resource" })
public TpUsers getUserInfo(String accessToken, String openID) throws Exception {
String appID = weixinLoginProperties.getWeixinappID();
String appSecret = weixinLoginProperties.getWeixinappSecret();
String uri = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openID;
org.apache.http.client.HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(URI.create(uri));
try {
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
StringBuilder builder = new StringBuilder();
for (String temp = reader.readLine(); temp != null; temp = reader.readLine()) {
builder.append(temp);
}
JSONObject object = new JSONObject(builder.toString().trim());
String country = object.getString("country");
String nikeName = object.getString("nickname");
String unionid = object.getString("unionid");
String province = object.getString("province");
String city = object.getString("city");
String openid = object.getString("openid");
String sex = object.getString("sex");
String headimgurl = object.getString("headimgurl");
String language = object.getString("language");
BigDecimal bigDecimal=new BigDecimal(0.0);
TpUsersExample example=new TpUsersExample();
example.createCriteria().andOpenidEqualTo(openid);
List<TpUsers> list = tpUsersMapper.selectByExample(example);
if(list!=null&&list.size()>0) {
TpUsers tpUsers = list.get(0);
System.out.println("---------");
return tpUsers;
}else {
TpUsers tpUsers=new TpUsers();
tpUsers.setOauth("wx");
tpUsers.setOpenid(openid);
tpUsers.setUnionid(unionid);
tpUsers.setUserName(nikeName);
tpUsers.setUserMoney(bigDecimal);
tpUsersService.save(tpUsers);
System.out.println("+++++++");
return tpUsers;
}
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
@RequestMapping("/isaccesstoken")
@SuppressWarnings({ "resource" })
private boolean isAccessTokenIsInvalid(String accessToken,String openID) {
String url = "https://api.weixin.qq.com/sns/auth?access_token=" + accessToken + "&openid=" + openID;
URI uri = URI.create(url);
org.apache.http.client.HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(uri);
HttpResponse response;
try {
response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
StringBuilder sb = new StringBuilder();
for (String temp = reader.readLine(); temp != null; temp = reader.readLine()) {
sb.append(temp);
}
JSONObject object = new JSONObject(sb.toString().trim());
/* {
"errcode":0,"errmsg":"ok"
}
错误的Json返回示例:
{
"errcode":40003,"errmsg":"invalid openid"
}*/
int errorCode = object.getInt("errcode");
if (errorCode == 0) {
return true;
}
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return false;
}
}
获取第一步的code后,请求以下链接进行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 是 填写通过access_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
openid 授权用户唯一标识
scope 用户授权的作用域,使用逗号(,)分隔
错误返回样例:
{"errcode":40030,"errmsg":"invalid refresh_token"}
刷新或续期access_token使用
接口说明
access_token是调用授权关系接口的调用凭证,由于access_token有效期(目前为2个小时)较短,当access_token超时后,可以使用refresh_token进行刷新,access_token刷新结果有两种:
1.若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
2.若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。
refresh_token拥有较长的有效期(30天)且无法续期,当refresh_token失效的后,需要用户重新授权后才可以继续获取用户头像昵称。
请求方法
使用/sns/oauth2/access_token接口获取到的refresh_token进行以下接口调用:
http请求方式: GET
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 是 填写通过access_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
openid 授权用户唯一标识
scope 用户授权的作用域,使用逗号(,)分隔
错误返回样例:
{
"errcode":40030,"errmsg":"invalid refresh_token"
}
获取用户个人信息(UnionID机制)
接口说明
此接口用于获取用户个人信息。开发者可通过OpenID来获取用户基本信息。特别需要注意的是,如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。请注意,在用户修改微信头像后,旧的微信头像URL将会失效,因此开发者应该自己在获取用户信息后,将头像图片保存下来,避免微信头像URL失效后的异常情况。
请求说明
http请求方式: GET
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
参数说明
参数 是否必须 说明
access_token 是 调用凭证
openid 是 普通用户的标识,对当前开发者帐号唯一
lang 否 国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语,默认为zh-CN
返回说明
正确的Json返回结果:
{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
参数 说明
openid 普通用户的标识,对当前开发者帐号唯一
nickname 普通用户昵称
sex 普通用户性别,1为男性,2为女性
province 普通用户个人资料填写的省份
city 普通用户个人资料填写的城市
country 国家,如中国为CN
headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
privilege 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
unionid 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
建议:
开发者最好保存unionID信息,以便以后在不同应用之间进行用户信息互通。
错误的Json返回示例:
{
"errcode":40003,"errmsg":"invalid openid"
}
工具类
package io.renren.common.utils;
import java.util.HashMap;
import java.util.Map;
/**
* 返回数据
*
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2016年10月27日 下午9:59:27
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(500, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(500, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}