微信开放平台第三方授权登陆开发文档(Android端)
当前期准备完成后,已经获取到应用的AppID和AppSecret、且已经成功申请到微信登陆功能。可以进行第三方登陆授权开发。
注意:
目前移动应用上微信登录只提供原生的登录方式,需要用户安装微信客户端才能配合使用。
对于Android应用,建议总是显示微信登录按钮,当用户手机没有安装微信客户端时,请引导用户下载安装微信客户端
开放平台中创建移动应用时,需要添加包名(一定要和开发的包名完全一致,不能是填写的包名的子包,否则微信无法回调成功)
安装验签工具:Gen_Signature_Android2.apk。
填写包名,然后会生成应用签名,填写应用签名就可以了。
一、需求
拥有第三方微信登录功能,并获取到用户信息。
二、开发流程
Android移动应用:(App唤醒微信客户端授权登陆)
1. 应用发起微信授权登录请求,用户允许授权应用后,微信会拉起应用或重定向到第三方网站(服务端),并且带上授权临时票据code参数;
2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;
3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。
获取Token的流程
三、开发使用的技术及工具
1、.后端采用IDEA2017 进行开发
2、使用Android Studio 3.1.3 进行开发
3、后端必须基于JDK7以上版本,采用JDK8开发,前端基于Android SDK4.4
4、使用fastJson对json数据进行处理
1.前端(Android)
目录结构如下:
1)Android微信授权登录开发环境配置
I.添加微信依赖
Android Studio环境
在build.gradle文件中,添加依赖
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'
}
或
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
Eclipse环境下:
在工程中新建一个libs目录,将开发工具包中libs目录下的libammsdk.jar复制到该目录中(如下图所示,建立了一个名为SDK_Sample 的工程,并把jar包复制到libs目录下)。
右键单击工程,选择Build Path中的Configure Build Path...,选中Libraries这个tab,并通过Add Jars...导入工程libs目录下的libammsdk.jar文件。(如下图所示)。
在需要使用微信终端API的文件中导入相应的类。
import com.tencent.mm.opensdk.openapi.WXTextObject;
II. AndroidManifest.xml 设置
添加如下权限支持:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
III.若要混淆代码,为保证sdk正常使用,需在配置proguard.cfg(proguard-rule.pro):
# wechar
-keep class com.tencent.mm.opensdk.** {*;}
-keep class com.tencent.wxop.** {*;}
-keep class com.tencent.mm.sdk.** {*;}
2)引导用户点击登录并授权
I.layout.xml
添加button:
<Button
android:id="@+id/wechar_login_btn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@string/wechar_login"/>
II.监听button点击事件,拉起微信授权页
findViewById(R.id.wechar_login_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isWXAppInstalledAndSupported()) { // 用户是否安装微信客户端
// send oauth request
final SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
req.state = "none";
api.sendReq(req);
finish();
} else {
// TODO: 这里需要引导用户去下载微信客户端
Toast.makeText(WXEntryActivity.this, "用户没有安装微信", Toast.LENGTH_SHORT).show();
}
}
});
III.用户手机是否安装微信客户端检查
private boolean isWXAppInstalledAndSupported() {
IWXAPI msgApi = WXAPIFactory.createWXAPI(this, null);
msgApi.registerApp(Constants.APP_ID);
boolean sIsWXAppInstalledAndSupported = msgApi.isWXAppInstalled()
&& msgApi.isWXAppSupportAPI();
return sIsWXAppInstalledAndSupported;
}
3)接收微信服务端返回的数据并向服务端发送请求
用户统一授权后,微信会返回数据,需要在.wxapi.WXEntryActivity下对数据进行处理。
I.新建wxapi包(包名固定,且必须是在微信开放平台注册的包名下)
II.新建Activity类,命名为WXEntryActivity
WXEntryActivity,并继承Activity类,实现IWXAPIEventHandler接口的两个方法
public interface IWXAPIEventHandler {
void onReq(BaseReq var1);
void onResp(BaseResp var1);
}
WXEntryActivity实现
public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
private IWXAPI api; // 在onCreate中进行了初始化
onReq方法
// 微信发送请求到第三方应用时,会回调到该方法
@Override
public void onReq(BaseReq req) {
Toast.makeText(this, "Test ", Toast.LENGTH_SHORT).show();
switch (req.getType()) {
case ConstantsAPI.COMMAND_GETMESSAGE_FROM_WX:
break;
case ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX:
break;
default:
break;
}
}
onResp方法:
在onResp中需要实现逻辑,微信返回的数据在这里会被接收。
微信返回的数据包含code。在onResp需要实现向服务端发送请求,带上code等参数,后端再通过相应的参数去请求微信服务端,最终将获取到的用户信息返回给前端Android。
// 第三方应用发送到微信的请求处理后的响应结果,会回调到该方法
@Override
public void onResp(final BaseResp resp) {
int result = 0;
Toast.makeText(this, "baseresp.getType = " + resp.getType(), Toast.LENGTH_SHORT).show();
//成功后发送请求
switch (resp.errCode) {
case BaseResp.ErrCode.ERR_OK:
result = R.string.errcode_success;
final String code = ((SendAuth.Resp) resp).code;//需要转换一下才可以
new Thread(new Runnable() {
@Override
public void run() {
//向服务端发送请求,预计返回用户信息数据,返回给前端进行显示。
String url = "http://p2a3b8.natappfree.cc" +
"/wechar/open/callback/android" + "?" +
"state=" + "android" +//这里的state需与后端进行探讨
"&code=" + code;
String str = ApacheHttpUtil.get(url);
JSONObject jsonObject = (JSONObject) JSONObject.parse(str);
weCharUserInfo = (WeCharUserInfo) JSON.parseObject(jsonObject.get("data").toString(), new TypeReference<WeCharUserInfo>() {
});
}
}).start();
while (true) {
// TODO: 这里处理方案不合理,死循环或将造成界面卡死(需要前端优化)
if (weCharUserInfo != null) {
Intent intent = new Intent(WXEntryActivity.this, WecharUserInfoViewItem.class);
/* 通过Bundle对象存储需要传递的数据 */
Bundle bundle = new Bundle();
bundle.putString("wecharopenid", weCharUserInfo.getOpenid());
bundle.putString("wecharnickname", weCharUserInfo.getNickname());
bundle.putString("wecharsex", weCharUserInfo.getSex().toString());
bundle.putString("wecharprovince", weCharUserInfo.getProvince());
bundle.putString("wecharcity", weCharUserInfo.getCity());
bundle.putString("wecharcountry", weCharUserInfo.getCountry());
bundle.putString("wecharheadimgurl", weCharUserInfo.getHeadimgurl());
bundle.putString("wecharprivilege", weCharUserInfo.getPrivilege());
bundle.putString("wecharunionid", weCharUserInfo.getOpenid());
/*把bundle对象assign给Intent*/
intent.putExtras(bundle);
startActivity(intent);
break;
}
}
break;
case BaseResp.ErrCode.ERR_USER_CANCEL:
result = R.string.errcode_cancel; // 发送取消
break;
case BaseResp.ErrCode.ERR_AUTH_DENIED:
result = R.string.errcode_deny; // 发送被拒绝
break;
case BaseResp.ErrCode.ERR_UNSUPPORT:
result = R.string.errcode_unsupported; // 不支持错误
break;
default:
result = R.string.errcode_unknown; // 发送返回
break;
}
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
}
重写onNewIntent方法
在WXEntryActivity中将接收到的intent及实现了IWXAPIEventHandler接口的对象传递给IWXAPI接口的handleIntent方法,
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
api.handleIntent(intent, this);
}
III.在manifest文件添加WXEntryActivity,并加上exported属性,设置为true,:
<activity
android:name=".wxapi.WXEntryActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask">
<!--android:launchMode="singleTop">-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sdksample" />
</intent-filter>
</activity>
4)根据服务端返回数据进行解析并显示给前端Android页面
获取数据已经跳转代码如上(onResp方法中)。需要前端优化处理
public class WecharUserInfoViewItem extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wechar_user_info);
Bundle bundle = this.getIntent().getExtras();
TextView wecharopenid = (TextView) findViewById(R.id.wecharopenid);
wecharopenid.setText(bundle.getString("wecharopenid"));
TextView wecharnickname = (TextView) findViewById(R.id.wecharnickname);
wecharnickname.setText(bundle.getString("wecharnickname"));
TextView wecharsex = (TextView) findViewById(R.id.wecharsex);
wecharsex.setText(bundle.getString("wecharsex"));
TextView wecharprovince = (TextView) findViewById(R.id.wecharprovince);
wecharprovince.setText(bundle.getString("wecharprovince"));
TextView wecharcity = (TextView) findViewById(R.id.wecharcity);
wecharcity.setText(bundle.getString("wecharcity"));
TextView wecharcountry = (TextView) findViewById(R.id.wecharcountry);
wecharcountry.setText(bundle.getString("wecharcountry"));
TextView wecharunionid = (TextView) findViewById(R.id.wecharunionid);
wecharunionid.setText(bundle.getString("wecharunionid"));
}
}
布局xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/wecharheadimgurl"
android:layout_width="200dp"
android:layout_height="100dp"
android:scaleType="center"/>
<!--用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)[这里是一个list]-->
<!--String privilege;-->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="openid:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wecharopenid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="unionid:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wecharunionid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户昵称:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wecharnickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="85dp"
tools:layout_editor_absoluteY="212dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户城市:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wecharcity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户性别:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wecharsex"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="省份:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wecharprovince"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="国家:"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="216dp" />
<TextView
android:id="@+id/wecharcountry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="118dp"
tools:layout_editor_absoluteY="302dp" />
</LinearLayout>
</LinearLayout>
2.服务端(Java)
服务端需要做的是:接受Android前端发送的请求,获取code,根据AppId和APPSecret等向微信服务端发送请求,然后获取到Token,再根据Token获取到用户基本信息。最终通过JSON的方式返回给前端。
1).统一返回JSON
@Getter @Setter
public class Result<T> {
private int code;
private String msg;
private T data;
/*** 成功时候的调用* */
public static <T> Result<T> success(T data){
return new Result<T>(data);
}
/*** 失败时候的调用* */
public static <T> Result<T> error(CodeMsg cm){
return new Result<T>(cm);
}
private Result(T data) {
this.code = 0;
this.msg = "success";
this.data = data;
}
private Result(CodeMsg cm) {
if(cm == null) {
return;
}
this.code = cm.getCode();
this.msg = cm.getMsg();
}
}
2).相关参数配置:
# 微信开放平台Android
wechar.open.android.appid =
wechar.open.android.appsecret =
3).请求响应逻辑
@ResponseBody
@RequestMapping("/callback/{type}")
public Result openWeCharCallback(HttpServletRequest httpServletRequest, @PathVariable("type") String type) {
String code = httpServletRequest.getParameter("code");
String state = httpServletRequest.getParameter("state");
String url = null;
if (type.equals("pc")) { // PC端逻辑
// 判断state是否合法
String stateStr = RedisPoolUtil.get("wechar-open-state-" + httpServletRequest.getSession().getId());
if (StringUtils.isEmpty(code) || StringUtils.isEmpty(stateStr) || !state.equals(stateStr + "STATE")) {
return Result.error(CodeMsg.ILLEGAL_PARAM);
// throw new WecharParamException("非法参数,请重新登陆", "/");
}
url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" +
env.getProperty("wechar.open.pc.appid").trim() +
"&secret=" +
env.getProperty("wechar.open.pc.appsecret").trim() +
"&code=" +
code +
"&grant_type=authorization_code";
} else if (type.equals("android")) {
url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" +
env.getProperty("wechar.open.android.appid").trim() +
"&secret=" +
env.getProperty("wechar.open.android.appsecret").trim() +
"&code=" +
code +
"&grant_type=authorization_code";
}else {
return Result.error(CodeMsg.NONSUPPORT);
}
JSONObject wecharAccessToken = HttpClientUtils.httpGet(url);
String accessToken = (String) wecharAccessToken.get("access_token");
if (StringUtils.isEmpty(accessToken) || wecharAccessToken.get("errcode") != null) {
return Result.error(CodeMsg.FAIL_GETTOKEN);
}
WeCharUserInfo weCharUserInfo = getUserInfoByAccessToken(accessToken, type);
if (weCharUserInfo != null) {
return Result.success(weCharUserInfo);
}
return Result.error(CodeMsg. FAIL_GETUSERINFO);
}
4).根据Token获取用户信息:
/**
* 根据accessToken获取用户个人公开信息
*
* @param accessToken
* @return
*/
private WeCharUserInfo getUserInfoByAccessToken(String accessToken, String type) {
if (StringUtils.isEmpty(accessToken)) {
return null;
}
String get_userInfo_url = null;
if (type.equals("pc")) {
get_userInfo_url = "https://api.weixin.qq.com/sns/userinfo?" +
"access_token=" +
accessToken +
"&openid=" +
env.getProperty("wechar.open.pc.appid").trim();
} else if (type.endsWith("android")) {
get_userInfo_url = "https://api.weixin.qq.com/sns/userinfo?" +
"access_token=" +
accessToken +
"&openid=" +
env.getProperty("wechar.open.android.appid").trim();
}
String userInfo_result = HttpClientUtils.httpGet(get_userInfo_url, "utf-8");
if (!userInfo_result.equals("errcode")) {
WeCharUserInfo weCharUserInfo = JSON.parseObject(userInfo_result, new TypeReference<WeCharUserInfo>() {
});
return weCharUserInfo;
}
return null;
}
五、注意事项:
1.Android4.0以上版本,发送网络请求时,必须是以线程异步的方式发送请求,否则发送请求会失败。