APP调用微信授权登录-JAVA后台实现

  • Post author:
  • Post category:java


由于最近项目需求,需要在app中实现微信授权快速登录,参考学习各位大佬的帖子后终于完成了。现在回头来总结记录一下,如有总结的不到位的,请谅解。

编码前准备工作:开发之前我们需要准备两个东西AppID和AppSecret,需要到微信开放平台(

https://open.weixin.qq.com

)注册开发者账号,并在移动应用中将我们的APP创建进去,填写对应资料后提交审核。

审核通过之后微信开放平台会为我们生成对应的AppID,以及设置后面需要用到的AppSecret。

审核通过后微信也为我们提供了许多接口,例如微信支付(微信支付需要到微信商户平台

https://pay.weixin.qq.com

,开通商户账号,具体实现下一篇微信支付再细说)、微信登录;微信登录就是我们需要用到的授权登录。

准备工作做好后我们开始实现,我们项目用的是spring+springBoot+mybatis框架进行的接口开发,嵌入了swagger进行接口管理。

1.第一步是由app端调起微信授权登录页面让用户进行授权操作,用户确认授权后会返回一个code,这个code就是我们后面获取授权用户信息的关键(具体实现见末尾附上的微信开发文档)

2.拿着第一步获得的code去获取用户信息并进行登录操作,下面附上鄙人的代码

controller:

/**
 * @throws
 * @title weChatLogin
 * @description 微信授权登录
 * @author Kuangzc
 * @updateTime 2019-9-12 16:00:51
 */
@ApiOperation(value = "微信授权登录")
@ApiImplicitParams({
        @ApiImplicitParam(paramType = "query", name = "code", value = "用户换取access_token的code,仅在ErrCode为0时有效", required = true, dataType = "String")
})
@GetMapping("/weChatLogin")
protected ApiResult weChatLogin(@RequestParam String code) {
    return appLoginRegisterService.weChatLogin(code);
}

interface:

/**
 * @throws
 * @title weChatLogin
 * @description 微信授权登录
 * @author Kuangzc
 * @updateTime 2019-9-12 16:00:51
 */
ApiResult weChatLogin(String code);

serviceImpl:

/**
 * @throws
 * @title weChatLogin
 * @description 微信授权登录
 * @author Kuangzc
 * @updateTime 2019-9-12 16:00:51
 */
public ApiResult weChatLogin(String code) {
    try {
        //通过第一步获得的code获取微信授权信息
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + AuthUtil.APPID + "&secret="
                + AuthUtil.APPSECRET + "&code=" + code + "&grant_type=authorization_code";
        JSONObject jsonObject = AuthUtil.doGetJson(url);
        String openid = jsonObject.getString("openid");
        String token = jsonObject.getString("access_token");
        String infoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=" + token + "&openid=" + openid
                + "&lang=zh_CN";
        JSONObject userInfo = AuthUtil.doGetJson(infoUrl);

        //成功获取授权,以下部分为业务逻辑处理了,根据个人业务需求写就可以了
        if (userInfo != null && openid != null) {
            String nickname = userInfo.getString("nickname");
            String headimgurl = userInfo.getString("headimgurl");
            headimgurl = headimgurl.replace("\\", "");
            //根据openid查询时候有用户信息
            ResponseMemberDTO memberDTO = appCoreMemberMapper.getMemberInfoByOpenid(openid);
            if (Objects.isNull(memberDTO)) {

                //没有绑定用户请前往绑定
                HashMap map = new HashMap();
                map.put("state", "1");
                map.put("openid", openid);
                map.put("token", "");
                map.put("nickname", nickname);
                map.put("photo", headimgurl);
                return ApiUtil.success("登录成功", map);
            } else {

                //已经绑定用户信息直接登录
                UserInfo userInfos = UserInfo.build(memberDTO.getId().longValue(), memberDTO.getMobile(), new Date(), 1);
                String Token = tokenManager.setToken(userInfos);
                // 缓存权限
                Map<String, String> httpUrl = new HashMap<>();
                httpUrl.put("kylin", "shuoye.com:8484");
                authHandler.setRoleUrl(memberDTO.getId().toString(), httpUrl);
                HashMap map = new HashMap();
                map.put("state", "2");
                map.put("openid", openid);
                map.put("token", Token);
                return ApiUtil.success("登录成功", map);
            }
        } else {
            return ApiUtil.fail("登录失败");
        }
    } catch (Exception e) {
        return ApiUtil.fail("登录失败");
    }
}
AuthUtil:
package com.shuoyeah.kylin.common.util;

import com.alibaba.fastjson.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * 微信授权登录调用Util
 */
public class AuthUtil {
    public static final String APPID = "***************";//自己的微信APPID
    public static final String APPSECRET = "***************";//自己的微信APPSECRET

    public static JSONObject doGetJson(String URL) throws IOException {
        JSONObject jsonObject = null;
        HttpURLConnection conn = null;
        InputStream is = null;
        BufferedReader br = null;
        StringBuilder result = new StringBuilder();
        try {
            //创建远程url连接对象
            URL url = new URL(URL);
            //通过远程url连接对象打开一个连接,强转成HTTPURLConnection类
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            //设置连接超时时间和读取超时时间
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);
            conn.setRequestProperty("Accept", "application/json");
            //发送请求
            conn.connect();
            //通过conn取得输入流,并使用Reader读取
            if (200 == conn.getResponseCode()) {
                is = conn.getInputStream();
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                String line;
                while ((line = br.readLine()) != null) {
                    result.append(line);
                    System.out.println(line);
                }
            } else {
                System.out.println("ResponseCode is an error code:" + conn.getResponseCode());
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
                if (is != null) {
                    is.close();
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            conn.disconnect();
        }
        jsonObject = JSONObject.parseObject(result.toString());
        return jsonObject;
    }
}

返回的userinfo:

其中包含地址、用户性别、昵称、头像等信息以及唯一的openid,需要注意的是没有手机号码。我们就可以拿着这些信息去进行业务逻辑的实现了。

以上就是我的微信授权登录的实现,如有不到位的地方,敬请谅解,谢谢。

下面附上微信开发文档


准备工作

移动应用微信登录是基于

OAuth2.0 协议标准

构建的微信 OAuth2.0 授权登录系统。

在进行微信 OAuth2.0 授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的移动应用,并获得相应的 AppID 和 AppSecret,申请微信登录且通过审核后,可开始接入流程。

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

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

3、对于iOS应用,考虑到iOS应用商店审核指南中的相关规定,建议开发者接入微信登录时,先检测用户手机是否已安装微信客户端(使用sdk中isWXAppInstalled函数 ),对未安装的用户隐藏微信登录按钮,只提供其他登录方式(比如手机号注册登录、游客登录等)。


授权流程说明

微信 OAuth2.0 授权登录让微信用户使用微信身份安全登录第三方应用或网站,在微信用户授权登录已接入微信 OAuth2.0 的第三方应用后,第三方可以获取到用户的接口调用凭证(access_token),通过 access_token 可以进行微信开放平台授权关系接口调用,从而可实现获取微信用户基本开放信息和帮助用户实现基础开放功能等。

微信 OAuth2.0 授权登录目前支持 authorization_code 模式,适用于拥有 server 端的应用授权。该模式整体流程为:

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

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

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

获取 access_token 时序图:


第一步:请求 CODE


移动应用微信授权登录

开发者需要配合使用微信开放平台提供的 SDK 进行授权登录请求接入。正确接入 SDK 后并拥有相关授权域(scope)权限后,开发者移动应用会在终端本地拉起微信应用进行授权登录,微信用户确认后微信将拉起开发者移动应用,并带上授权临时票据(code)。

iOS 平台应用授权登录接入代码示例(请参考 iOS 接入指南):


-(void)sendAuthRequest
{
	//构造SendAuthReq结构体
	SendAuthReq* req =[[[SendAuthReq alloc]init]autorelease];
	req.scope = @"snsapi_userinfo";
	req.state = @"123";
	//第三方向微信终端发送一个SendAuthReq消息结构
	[WXApi sendReq:req];
}

Android 平台应用授权登录接入代码示例(请参考 Android 接入指南):

{
	// send oauth request
	Final SendAuth.Req req = new SendAuth.Req();
	req.scope = "snsapi_userinfo";
	req.state = "wechat_sdk_demo_test";
	api.sendReq(req);
}


参数说明

参数 是否必须 说明
appid 应用唯一标识,在微信开放平台提交应用审核通过后获得
scope 应用授权作用域,如获取用户个人信息则填写 snsapi_userinfo
state 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止 csrf 攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加 session 进行校验


返回示例:

appid: wxd477edab60670232
scope: snsapi_userinfo
state: wechat_sdk_demo


可拉起微信打开授权登录页:


返回说明

用户点击授权后,微信客户端会被拉起,跳转至授权界面,用户在该界面点击允许或取消,SDK 通过 SendAuth 的 Resp 返回数据给调用方。

返回值 说明
ErrCode ERR_OK = 0(用户同意) ERR_AUTH_DENIED = -4(用户拒绝授权) ERR_USER_CANCEL = -2(用户取消)
code 用户换取 access_token 的 code,仅在 ErrCode 为 0 时有效
state 第三方程序发送时用来标识其请求的唯一性的标志,由第三方程序调用 sendReq 时传入,由微信终端回传,state 字符串长度不能超过 1K
lang 微信客户端当前语言
country 微信用户当前国家信息


第二步:通过 code 获取 access_token

获取第一步的 code 后,请求以下链接获取 access_token:

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",
  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
参数 说明
access_token 接口调用凭证
expires_in access_token 接口调用凭证超时时间,单位(秒)
refresh_token 用户刷新 access_token
openid 授权用户唯一标识
scope 用户授权的作用域,使用逗号(,)分隔
unionid 当且仅当该移动应用已获得该用户的 userinfo 授权时,才会出现该字段


错误返回样例:

{"errcode":40029,"errmsg":"invalid code"}


刷新 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 失效的后,需要用户重新授权。


请求方法

获取第一步的 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" }


注意:


1、Appsecret 是应用接口使用密钥,泄漏后将可能导致应用数据泄漏、应用的用户数据泄漏等高风险后果;存储在客户端,极有可能被恶意窃取(如反编译获取Appsecret);
2、access_token 为用户授权第三方应用发起接口调用的凭证(相当于用户登录态),存储在客户端,可能出现恶意获取access_token 后导致的用户数据泄漏、用户微信相关接口功能被恶意发起等行为;
3、refresh_token 为用户授权第三方应用的长效凭证,仅用于刷新access_token,但泄漏后相当于access_token 泄漏,风险同上。

```





**建议将Appsecret、用户数据(如access_token)放在App云端服务器,由云端中转接口调用请求。**





**第三步:通过access_token调用接口**


获取access_token后,进行接口调用,有以下前提:


1. access_token有效且未超时;
2. 微信用户已授权给第三方应用帐号相应接口作用域(scope)。


对于接口作用域(scope),能调用的接口有以下:



| 授权作用域(scope) | 接口 | 接口说明 |
| --- | --- | --- |
| snsapi_base | /sns/oauth2/access_token | 通过code换取access_token、refresh_token和已授权scope |
| /sns/oauth2/refresh_token | 刷新或续期access_token使用 |
| /sns/auth | 检查access_token有效性 |
| snsapi_userinfo | /sns/userinfo | 获取用户个人信息 |



其中snsapi_base属于基础接口,若应用已拥有其它scope权限,则默认拥有snsapi_base的权限。使用snsapi_base可以让移动端网页授权绕过跳转授权登录页请求用户授权的动作,直接跳转第三方网页带上授权临时票据(code),但会使得用户已授权作用域(scope)仅为snsapi_base,从而导致无法获取到需要用户授权才允许获得的数据和基础功能。



接口调用方法可查阅[微信授权关系接口调用指南](/doc/oplatform/Mobile_App/WeChat_Login/Authorized_API_call_UnionID.html)


**F.A.Q**

**1. 什么是授权临时票据(code)?**


答:第三方通过code进行获取access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。code的临时性和一次保障了微信授权登录的安全性。第三方可通过使用https和state参数,进一步加强自身授权登录的安全性。





**2. 什么是授权作用域(scope)?**


答:授权作用域(scope)代表用户授权给第三方的接口权限,第三方应用需要向微信开放平台申请使用相应scope的权限后,使用文档所述方式让用户进行授权,经过用户授权,获取到相应access_token后方可对接口进行调用。


**3.开放平台移动应用微信登录目前是否收费?**


答:“微信登录”和第三方网站共享微信庞大的用户价值,同时为微信用户提供更便捷服务和更优质内容,实现双向共赢,目前不收取任何费用。



版权声明:本文为a1101282836原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。