微信开放平台开发第三方授权登陆(三):第三方登陆授权开发(Android客户端)

版权声明:本文为博主原创文章,转载添加原文链接 https://blog.csdn.net/qq_34190023/article/details/82017427

微信开放平台第三方授权登陆开发文档(Android端)

  当前期准备完成后,已经获取到应用的AppID和AppSecret、且已经成功申请到微信登陆功能。可以进行第三方登陆授权开发。

注意:

目前移动应用上微信登录只提供原生的登录方式,需要用户安装微信客户端才能配合使用

对于Android应用,建议总是显示微信登录按钮,当用户手机没有安装微信客户端时,请引导用户下载安装微信客户端

 

开放平台中创建移动应用时,需要添加包名(一定要和开发的包名完全一致,不能是填写的包名的子包,否则微信无法回调成功)

安装验签工具:Gen_Signature_Android2.apk

填写包名,然后会生成应用签名,填写应用签名就可以了。

 

一、需求

拥有第三方微信登录功能,并获取到用户信息。

二、开发流程

Android移动应用:(App唤醒微信客户端授权登陆)

1. 应用发起微信授权登录请求,用户允许授权应用后,微信会拉起应用或重定向到第三方网站(服务端),并且带上授权临时票据code参数;

2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;

3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

https://res.wx.qq.com/op_res/ZLIc-BdWcu_ixroOT0sBEtk0UwpTewqS6ujxbC2QOpbKIVp_DzleM_C9I-9GPDDh

获取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目录下)。

https://res.wx.qq.com/op_res/yXeM-qkPNMo3NZ6AOSZ0x8MqkBf9ATOfaw-2Ic93vUG8xFid8niGKr3W_RfCmMxe

右键单击工程,选择Build Path中的Configure Build Path...,选中Libraries这个tab,并通过Add Jars...导入工程libs目录下的libammsdk.jar文件。(如下图所示)。

https://res.wx.qq.com/op_res/AcZSyJLkQzLTjKChk8gLiJZok4cRD8b2OnpCH--OcGDexwEgscrauV7ZkLqsHNKs

在需要使用微信终端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以上版本,发送网络请求时,必须是以线程异步的方式发送请求,否则发送请求会失败。

 

猜你喜欢

转载自blog.csdn.net/qq_34190023/article/details/82017427