微信开发自动登陆的流程

2016-12-20 11:31:47 sk719887916 阅读数 99294

文/YXJ
地址:http://blog.csdn.net/sk719887916/article/details/53761107

最近在研究微信小程序开发,非常有意思的一个东西。花了一点时间写了一个微信的登录流程,包括后端接口和小程序代码。

做过微信登录的都知道,我们需要一个标识来记录用户的身份的唯一性,在微信中unionId就是我们所需要的记录唯一ID,那么如何拿到unionId就成了关键,我将项目分为小程序和 后台PHP代码两部分来讲。

微信小程序开放平台

#先从我们的小程序代码开始

这是我们小程序的代码结构,登录的主要功能在login.js中

##简单的说一下我们小程序的js代码登录流程
login ->获取code ->getUserInfo获取iv和encryptedData ->传给自己的服务器处理 ->返回给小程序结果

var API_URL = "自己的服务器地址";
Page({
  onLoad: function () {
  console.log("iv");
  wx.login({//login流程
  success: function (res) {//登录成功
    if (res.code) {
      var code = res.code;
      wx.getUserInfo({//getUserInfo流程
        success: function (res2) {//获取userinfo成功
        console.log(res2);
	    var encryptedData = encodeURIComponent(res2.encryptedData);//一定要把加密串转成URI编码
          var iv = res2.iv;
          //请求自己的服务器
         Login(code,encryptedData,iv);
        }
      })

    } else {
      console.log('获取用户登录态失败!' + res.errMsg)
    }
  }
});

}
})

  • code:服务器用来获取sessionKey的必要参数。
  • IV:加密算法的初始向量,encryptedData:加密过的字符串。

##把code iv encryptedData 传递给我们的服务器

function  Login(code,encryptedData,iv){	 console.log('code='+code+'&encryptedData='+encryptedData+'&iv='+iv);
 //创建一个dialog
          wx.showToast({
            title: '正在登录...',
            icon: 'loading',
            duration: 10000
          });
          //请求服务器
          wx.request({
            url: API_URL,
            data: {
              code:code,
              encryptedData:encryptedData,
              iv:iv
            },
            method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
            header: {
              'content-type': 'application/json'
            }, // 设置请求的 header
            success: function (res) {
              // success
              wx.hideToast();
              console.log('服务器返回'+res.data);
            
            },
            fail: function () {
              // fail
              // wx.hideToast();
            },
            complete: function () {
              // complete
            }
          })
  }

看文档的话,应该知道,我们所需要的unionId就在encryptedData中,所以服务器需要这些信息来把unionId解析出来。

#服务器处理逻辑

我php用的是laravel框架

##先下载微信的解密demo
下载地址
这里写图片描述
这里我选择的是PHP代码,把除了demo外的三个class文件,放入我们自己的项目,以后后面调用。

这里讲解一下服务器的处理流程:
通过微信的https://api.weixin.qq.com/sns/jscode2session接口获取seesionKey,然后在通过sessionKey和iv来解密encryptedData数据获取UnionID。
具体文档

/**
     *    登录
     * 
     * @return Response
     */
    public function weixinlogin( $user_id=null )
    {
            global $App_Error_Conf,$Gift_Ids,$Server_Http_Path,$Is_Local,$Test_User,$Good_Vcode,$WeiXin_Xd_Conf;
            $validator_result = input_validator(array('code','iv','encryptedData'));
            if(!empty($validator_result)){
                    return response($validator_result);
            }
            $js_code = $_REQUEST['code'];
            $encryptedData = $_REQUEST['encryptedData'];
            $iv = $_REQUEST['iv'];
            $appid = $WeiXin_Xd_Conf['appid'];
            $secret =  $WeiXin_Xd_Conf['secret'];
            $grant_type =  $WeiXin_Xd_Conf['grant_type'];
            //从微信获取session_key
            $user_info_url = $WeiXin_Xd_Conf['code2session_url'];
            $user_info_url = sprintf("%s?appid=%s&secret=%s&js_code=%s&grant_type=%",$user_info_url,$appid,$secret,$js_code,$grant_type);
            $weixin_user_data = json_decode(get_url($user_info_url));
            $session_key = $weixin_user_data->session_key;
//解密数据
$data = '';
$wxBizDataCrypt = new WXBizDataCrypt($appid, $session_key);
$errCode=$wxBizDataCrypt>decryptData($appid,$session_key,$encryptedData, $iv, $data );

最后拿到的这个 data就是我们解密后的encryptedData里面会包含unionId。(补充一点,如果你的小程序没有绑定微信公众号,是不会反悔uId的)

这样简单登录就实现了!更多微信程序相关文章请关注*游戏机* 文章。
更多阅读下篇:
微信小程序开发(二)图片上传+服务端接收
微信小程序开发(三) 微信小程序授权获取用户信息openid
https://tamic.blog.csdn.net/article/details/89284104

文/YXJ
地址:http://blog.csdn.net/sk719887916/article/details/53761107

作者:Tamic 更多原创关注开发者技术前线

开发者技术前线

2019-04-07 16:46:18 Abysscarry 阅读数 51155

背景:
过年前后做了个微信公众号项目,已经过去一段时间了,抽空回忆总结下基本流程吧,不然很快估计自己就忘了。。

微信公众平台官网:https://mp.weixin.qq.com



一、注册公众号

在这里插入图片描述
首先注册时可以看到公众号有三种类型,个人用户大多数选择订阅号,而企业用户一般选择服务号和企业号

我们平常大多数关注的都是订阅号,他们统一都放置在微信应用的订阅号消息列表中,没有微信支付等高级功能,只是用于发布文章等基础功能。
在这里插入图片描述

服务号企业号都在会话列表,和我们的微信好友是同级别的位置,具备微信支付等高级功能,一般是某个企业品牌的对外操作窗口,如海底捞火锅、顺丰速运等。
在这里插入图片描述

我们前期开发测试只需要注册个人订阅号即可,真正开发使用的是开发者工具里的测试号,具体下面会说。

真正生产的话,使用的都是经过微信认证的订阅号、服务号、企业号。


二、了解公众号管理页面

我们在微信公众平台扫码登录后可以发现管理页面左侧菜单栏有丰富的功能:
在这里插入图片描述
大概可以分为这几大模块:
首页功能小程序管理推广统计设置开发

作为开发人员,首先应该关注的是设置、开发模块;而作为产品运营人员,关注的是功能、管理、推广模块;作为数据分析人员,关注的是统计模块。

首先我们不妨各个功能模块都点击看一看,大概了解下我们能做些什么。可以确认的是,这个微信公众平台当然不只是给开发人员使用的,它提供了很多非技术人员可在UI界面上交互操作的功能模块。

如配置消息回复、自定义菜单、发布文章等:
在这里插入图片描述
这个时候我们可能会想:这些功能好像非技术人员都能随意操作,那么还需要我们技术人员去开发吗?

答案是: 如果只是日常简单的推送文章,就像我们关注的大多数公众号一样,那确实不需要技术人员去开发;但是,如果你想将你们的网站嵌入进去公众号菜单里(这里指的是把前端项目的首页链接配置在自定义菜单),并且实现微信端的独立登录认证、获取微信用户信息、微信支付等高级功能,或者觉得UI交互的配置方式无法满足你的需求,你需要更加自由、随心所欲的操作,那么我们就必须启用开发者模式了,通过技术人员的手段去灵活控制公众号。

这里有一点需要注意,如果我们决定技术人员开发公众号,必须启用服务器配置,而这将导致UI界面设置的自动回复和自定义菜单失效!

我们在 开发 - 基本配置 - 服务器配置 中点击启用
在这里插入图片描述
在这里插入图片描述
我们团队就遇到过这种情况:两个项目组共用一个公众号,结果一个启用了服务器配置,使另一个项目组手动配置的菜单失效了。所以要注意这点!

至于服务器配置中的选项代表什么意思、如何填写,我们下面再讲。


三、必备开发者工具的使用

在这里插入图片描述
我们进入 开发 - 开发者工具, 可以发现微信提供了六种开发者工具,其中前四种属于开发必备:开发者文档在线接口调试工具web开发者工具公众平台测试账号

1.开发者文档

在这里插入图片描述
这个不用说!在我们开发中属于最最最基础和重要的东西了,我们要想熟练开发公众号,首先必须熟读开发者文档!有些功能的开发甚至非要反复研读、咬文嚼字一番不可。PS:该文档吐槽的地方也不少,有些地方的确讲的不够明确!

2.在线接口调试工具

在这里插入图片描述
这个工具也算比较实用,包含大多数接口的在线调试,我们可以直接在上面输入参数,获取微信服务端的返回结果。

3.web开发者工具

在这里插入图片描述
这个工具是一款桌面应用,需要下载,它通过模拟微信客户端的UI使得开发者可以使用这个工具方便地在PC或者Mac上进行开发和调试工作,一般是前端使用该工具进行页面、接口调试。

4.公众平台测试账号

在这里插入图片描述
这个测试号工具对我们的重要性可以说是仅次于开发者文档。我们可以创建测试号无需申请、认证真实的公众帐号、可在测试帐号中体验并测试微信公众平台所有高级接口。并且所有的配置都可在一个页面上编辑,使开发测试变得极其便利。


四、细读开发者文档

文档地址:https://mp.weixin.qq.com/wiki

需要注意的是,细读开发者文档不是让你所有模块都去阅读,而是重点的重复细读,非重点的选择性阅读。
在这里插入图片描述
其中前两个模块:开始前必读开始开发,属于重点关注对象,也是整个微信开发的基石所在,需要多读几遍。其次是微信网页开发模块微信网页授权,比较难理解,需要特别注意。其他的模块则根据你们的项目功能需求,有选择性的阅读即可。

这里我就不多罗嗦了,大家看文档去吧!下面我会描述一些重点内容的实际操作情况以及代码,请确保你已经浏览过文档


五、开发流程重点解析


1.开发环境准备

这里所谓的开发环境准备主要指的是我们项目服务端和微信服务端的网络通讯环境准备。

我们平常开发可能只需要IP端口就能通讯,顶多配置下白名单放行,但微信公众号开发我们需要通过域名通讯(微信会访问我们配置的域名地址:服务器基本配置中的URL,下面会介绍),也就是我们各自开发环境需要拥有独立的域名,微信就能通过这个域名请求到我们的本地开发服务,各自进行开发测试。

而我们一般都是内网开发,整个内网只有一个对外域名,所以这时就需要 内网穿透 ,为我们每个开发人员配置各自开发机器的域名。

那如何进行内网穿透呢?你首先可以找下你们的网管,看他能不能帮你解决,如果不能,那就安装内网穿透工具,我们自己动手!

我选择的内网穿透工具是natapp,这个有免费版、收费版,免费版的域名会随机变化,而收费版可以拥有固定域名,建议选择收费版,9元每月并不贵;大家可以对照natapp的文档安装使用,并不难。
在这里插入图片描述
这样我们本地开发环境就拥有自己的域名啦!然后就可以在测试号管理页面配置本地访问地址URL了。

2.服务器基本配置

无论是在真实公众号开发 - 基本配置 - 服务器配置,还是在 测试号管理 中,我们都可以看到这几个基本参数:
开发者ID(AppID)、开发者密码(AppSecret)、服务器地址(URL)、令牌(Token)

AppID 是公众号唯一开发识别码,配合开发者密码可调用公众号的接口能力,大多数微信接口都需要附带该参数。

AppSecret 是校验公众号开发者身份的密码,具有极高的安全性。切记勿把密码直接交给第三方开发者或直接存储在代码中。如需第三方代开发公众号,请使用授权方式接入。其中获取accessToken就需要同时传入AppID和AppSecret获取。

URL 是开发者用来接收微信消息和事件的接口URL,也就是我们服务后端的入口地址,需要注意的是该地址必须以域名形式填写,且必须以http 或 https 开头,分别支持80端口和443端口。如:http://yuanj.natapp1.cc/wechat。

Token 可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性),也就是我们项目和微信服务端进行通信时,必须保证公众平台配置的Token和我们后台代码配置的Token保持一致,这样微信就能验证我们身份。

注:EncodingAESKey 参数由开发者手动填写或随机生成,将用作消息体加解密密钥,我们前期可以采用明文模式进行开发测试,暂时先不用关注。
在这里插入图片描述
我们点击提交时,微信会以GET请求的方式访问我们配置的URL地址,并附加几个参数进行验证,所以你需要在该地址对应的项目后端接口里对这几个参数进行加工处理返回微信需要的结果,这样就可以验证成功,使微信服务端认可你配置的URL和Token参数,后续就能互相通信了!
在这里插入图片描述
具体情况可以阅读微信文档 - 开始前必读 - 接入指南

这里附上该接口的Java代码:

/**
 * 微信对接验证接口
 * */
@RestController
@RequestMapping(value = "/wechat")
public class ValidateController {
    @Autowired
    WechatConfig wechatConfig;

   @RequestMapping(value = "", method = RequestMethod.GET)
   public void validate(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("-----开始校验签名-----");

        // 接收微信服务器发送请求时传递过来的参数
        String signature = req.getParameter("signature");
        String timestamp = req.getParameter("timestamp");
        String nonce = req.getParameter("nonce"); //随机数
        String echostr = req.getParameter("echostr");//随机字符串

        // 将token、timestamp、nonce三个参数进行字典序排序并拼接为一个字符串
        String TOKEN = wechatConfig.getToken();
        String sortStr = sort(TOKEN,timestamp,nonce);
        
        // 字符串进行shal加密
        String mySignature = WechatUtils.shal(sortStr);
        
        // 校验微信服务器传递过来的签名 和  加密后的字符串是否一致, 若一致则签名通过
        if(!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)){
            System.out.println("-----签名校验通过-----");
            try {
                resp.getWriter().write(echostr);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println("-----校验签名失败-----");
        }
    }
    
   /**
     * 参数排序
     * @param token
     * @param timestamp
     * @param nonce
     * @return
     */
    public static String sort(String token, String timestamp, String nonce) {
        String[] strArray = {token, timestamp, nonce};
        Arrays.sort(strArray);
        StringBuilder sb = new StringBuilder();
        for (String str : strArray) {
            sb.append(str);
        }
        return sb.toString();
    }
}


3.存取access_token参数

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时(7200秒),需定时刷新重复获取将导致上次获取的access_token失效

access_token这个参数非常重要,几乎贯穿整个微信公关号项目开发,我们如何在有效期内定时刷新获取呢?
如果我们的微信公众号项目是单服务架构,可以直接作为静态变量存储在内存里;如果是多服务,可以用中间件存储Redis、数据库都可以。SpringBoot项目内部可以通过@Scheduled注解,执行定时任务,既然access_token有效期是2小时,那我们可以一小时刷新获取一次,将其存入Redis,覆盖之前的access_token。


4.公众号消息管理

在这里插入图片描述
很多公众号都可以通过消息发送来与其进行交互,那这样的功能如何代码实现呢?

具体我们可以在微信文档 - 消息管理 模块查阅:
在这里插入图片描述
在此我要提到的一点就 微信公众号的消息交互都是通过XML格式进行的!这点就很坑了。。现在我们前后端、服务端的消息传输基本都是Json格式了,也习惯了Json格式的解析处理,所以遇到XMl格式的处理又要多费些事了。

为什么微信采用XML格式呢?我个人猜测是几年前还是XML格式的天下,当时Json还没有这么流行,腾讯毕竟是产品业务驱动的,当然选择当时开发人员最熟悉的XML格式了开发,后面随着微信平台的普及,用户越来越多,想重构改成Json格式估计也十分困难,所以历史就遗留下来了呗。。

我在此推荐一个GitHub上一个微信开发 Java SDK,里面有整个微信开发平台很多功能模块造好的轮子,我们可以参考下直接使用:
https://github.com/Wechat-Group/WxJava
在这里插入图片描述
比如现在对于XMl消息解析这个需求,上面就提供了完整详尽的代码。


5.获取openid以及网页授权(重难点)

注意,这是公众号开发的重难点之一,请把技术文档中的微信网页授权模块多读两遍,然后带着疑问来看我的解析。

(1)先明确为什么需要网页授权?我们的目的是什么?

答:用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。也就是通过这种授权机制,我们能获取微信用户信息,比如:头像、昵称、地区、个性签名等。

(2)既然目的是获取用户基本信息,微信不是提供了专门的接口吗?非要网页授权?

答:在文档的 用户管理 - 获取用户基本信息(UnionID机制) 模块可以看到的确有获取用户基本信息接口:
在这里插入图片描述
可以看到,这个接口只需要提供openid或者unionid,即可直接获取用户基本信息。那么问题来了,openid(unionid)又是如何获取呢?

微信平台提供了两种方式获取用户的openid

第一种方式:

用户与公众号产生消息交互时,会以POST请求的方式向我们配置的服务器URL地址发送XML格式的消息,并附带该用户对应公众号的openid!关于什么是消息交互我们可以查看文档中的消息管理模块,比如我们在公众号输入栏中发送文字图片语音等属于普通消息交互,我们关注、取关、点击自定义菜单等属于事件消息交互,每当前端用户进行这个操作时,微信服务端都会向我们项目后台发送POST请求给我们传达信息:
在这里插入图片描述
可以看到,这个推送数据包中就包含了用户的消息交互类型、时间以及我们需要的openid!也就是说,无论用户在公众号里干了啥操作,我们都能知道他这个操作干了啥,以及他是谁(openid),这时就能调用 用户管理 - 获取用户基本信息(UnionID机制) 接口获取用户基本信息了。

别高兴太早,这种通过消息交互获取用户信息的方式,用户占主动地位,我们项目后端服务被动接受,那么如果我有个基本需求:我想在自定义菜单 - 对应我们网站的前端页面上展示微信用户基本信息,能做到吗?你如何把后台接收到的消息和前端用户关联绑定?
可见,这种被动的方式并不能实现该功能,我们需要主动出击,在前端就能获取到当前操作用户的openid!

第二种方式:

这种方式就是通过网页授权机制主动出击!详情见下文。

(3)网页授权有哪几种机制?分别是怎样实现?应用于什么场景?

答:主要有两种机制,对应两种scope:

snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)。

snsapi_userinfo为scope发起的网页授权,是用来获取用户基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

光看这两句解释你可能有一堆疑问,我们逐一分析:

两种机制的前面授权步骤相同,大概如下:

我们先要按照文档要求构造一个链接https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
其中重点参数是redirect_uri,这个参数填的既可以是前端项目url,也可以是后端接口url,然后点击这个链接后,微信服务端经过重定向到我们填写的redirect_uri,会在此redirect_uri后拼接上一个code参数!然后前端或者后端通过code参数就可以调微信接口https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code获取openid等信息了:
在这里插入图片描述
这里讲下 snsapi_basesnsapi_userinfo不同点

首先snsapi_base静默授权,什么意思呢?就是用户没有感知;与之对应的就是非静默授权snsapi_userinfo了,这个scope公众号会弹出一个小窗口需要用户手动点击授权,类似这种:
在这里插入图片描述
那么这两种scope授权的优劣势在哪呢?

snsapi_base 的优势在于用户无感知,体验好,方便快捷;劣势在于获取openid后只能通过用户管理 - 获取用户基本信息(UnionID机制) 接口获取用户基本信息,而这种方式需要确保用户已经关注,不然是没有相关信息的!
snsapi_userinfo 的优势在于无需用户关注公众号,只要用户点击了授权确认,即可通过access_token和openid调用专门的拉去用户信息接口获取信息,比较暴力。。;劣势在于需要用户手动授权,可能影响用户体验
在这里插入图片描述

在此说下,我们项目是通过snsapi_base静默授权的,其中redirect_uri配置的是前端项目首页地址(前后端分离),并将构造的这个链接封装起来,直接配置在自定义菜单里,那么用户点击菜单,就直接重定向到前端项目,然后前端获取code参数调用后端获取openid接口,将获取的openid缓存到客户端,以便后面使用。

(4)想要进行网页授权,我们需要在公众平台配置什么吗?

答:需要!
如果是测试号,需要在 测试号管理 - 体验接口权限表 - 网页服务 - 网页帐号 点击 修改
在这里插入图片描述
在这里插入图片描述
在这里配置的是回调页面redirect_uri的域名

如果是正式号(需要微信认证),需要在 开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息 的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;

而且正式号其他配置的地方也和测试号不一样,比如多了IP白名单、域名根路径下的txt验证文件,这个稍微摸索下应该没啥问题的。


over 暂时就回忆这么多了。。。可能有遗漏大家可以提出哈 ~ 下一篇博客写几个开发时的小问题补充下吧

2018-11-04 18:20:36 zjl_wldjx 阅读数 2047

我们在写小程序微信授权的时候,微信有自己的内置浏览器,我们要先判断用户是否在微信客服端登录

String ua = request.getHeader("User-Agent");判断ua里是否有MicroMessenger字符串,ua.contains("MicroMessenger")

微信的授权有两种方式,一种是静默授权snsapi_base为scope,用户为感知,只能获取用户的openid。

另一种是scope为snsapi_userinfo需要用户同意才行,因为这里介绍的是开发微信的公众号授权,用户已经关注,所以也是为感知的。

第一步,获取用户的code

向网址https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=#wechat_redirect发送一个get请求,然后会返回code.

第二步。通过code换取acesstoken

向网址https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code发送get请求,把获取到的code带上。

第三步,刷新acesstoken

向网址https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN发送一个get请求,把过期的acesstoken带上。

      如何判断acesstoken过期,

java是面向对象的,所以我们创建一个实体类为accesstoken,然后在第二步获取到acesstoken的时候转换为实体类对象,然后在它的构造方法中来计算时间。

第四步,拉取用户的信息、

向网址https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN发送一个get请求,返回的是一个JSON数据。

{    "openid":" OPENID",
" nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl":    "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

 

以上就是微信端授权过程,下面附上微信开发文档的地址

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432

2015-05-18 20:31:51 u011919407 阅读数 4635

登录步骤[1]

1. 申请APP KEY

在开放平台上注册应用即可获取。

2. 导入jar包

导入libammsdk.jar即可。

3. 添加权限

<uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

4. 创建WXEntryActivity类

- 在包名相应目录下新建一个wxapi目录,并在该wxapi目录下新增一个WXEntryActivity类,该类继承自Activity

这里写图片描述

- 在manifest文件里面加上exported属性,设置为true

这里写图片描述

- 实现IWXAPIEventHandler接口

@Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        setIntent(intent);
        api.handleIntent(intent, this);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        System.out.println("onActivityResult");
    }

    @Override
    public void onReq(BaseReq arg0) {
        // TODO Auto-generated method stub
        Toast.makeText(this, "onReq", Toast.LENGTH_SHORT).show();
        System.out.println("onReq");
    }

    @Override
    public void onResp(BaseResp arg0) {
        // TODO Auto-generated method stub
        Toast.makeText(this, "onResp", Toast.LENGTH_SHORT).show();
        SendAuth.Resp authResp = (SendAuth.Resp) arg0;
        System.out.println("onResp code:" + authResp.code);
        StringBuilder sb = new StringBuilder();
        sb.append("https://api.weixin.qq.com/sns/oauth2/access_token");
        sb.append("?appid=");
        sb.append(APP_ID);
        sb.append("&secret=");
        sb.append(SECRET);
        sb.append("&code=");
        sb.append(authResp.code);
        sb.append("&grant_type=authorization_code");
        String url = sb.toString();
        Bundle bundle = new Bundle();
        bundle.putString("url", url);
        //新开一个activity,用Volley发送请求,用code换回access_token、refresh_token、expires_in和openid
        Intent intent = new Intent(WXEntryActivity.this, WeixinActivity2.class);
        intent.putExtras(bundle);
        System.out.println("url:"+url);
        startActivity(intent);
        this.finish();
    }

注意:由于某种未知的原因,WXEntryActivity在执行onresp后会自行销毁并重新生成,然后重新执行onCreate方法。我的方法是在onCreate中加判断条件,并在一定条件下将activity自动销毁。
什么是refresh_token?
但是OAuth2.0为了增强安全性,access token的有效期被大大缩短,通常只有几个小时,也可以申请增加到几十天,但是总是会有过期的时候。为此,OAuth2.0增加了一个refresh token的概念,这个token并不能用于请求api.它是用来在access token过期后刷新access token的一个标记。[2]
什么是openid?
OpenID (OID) is an open standard and decentralized protocol by the non-profit OpenID Foundation that allows users to be authenticated by certain co-operating sites (known as Relying Parties or RP) using a third party service. This eliminates the need for webmasters to provide their own ad hoc systems and allowing users to consolidate their digital identities[3].
新的activity中的相关代码:

@Override
private void sendRequest(String url) {
        // TODO Auto-generated method stub
        System.out.println("Sending request");
        JsonObjectRequest req = new JsonObjectRequest(url, null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        System.out.println("response:" + response.toString());
                        String artiwaresUrl = "http://www.artiwares.com:8080/account/social_account/";
                        HashMap<String, String> map = new HashMap<String, String>();
                        map.put("platform", "wechat");
                        try {
                            map.put("access_token",
                                    response.getString("access_token"));
                            map.put("expires_in",
                                    response.getString("expires_in"));
                            map.put("refresh_token",
                                    response.getString("refresh_token"));
                            map.put("openid", response.getString("openid"));
                            System.out.println("JSONException:"
                                    + response.getString("access_token"));
                            System.out.println("expires_in:"
                                    + response.getString("expires_in"));
                            System.out.println("refresh_token:"
                                    + response.getString("refresh_token"));
                            System.out.println("openid:"
                                    + response.getString("openid"));
                        } catch (JSONException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                            System.out
                                    .println("JSONException:" + e1.toString());
                        }

                        JSONObject params = new JSONObject(map);
                        CookieRequest result = new CookieRequest(
                                Request.Method.POST, artiwaresUrl, params,
                                new Response.Listener<JSONObject>() {
                                    @Override
                                    public void onResponse(JSONObject arg0) {
                                        System.out.println("Login success!");
                                        System.out.println("arg0:"
                                                + arg0.toString());
                                    }
                                }, new Response.ErrorListener() {
                                    @Override
                                    public void onErrorResponse(
                                            VolleyError error) {
                                        // error
                                        System.out.println("VolleyError:"
                                                + error.toString());

                                    }
                                });
                        mQueue.add(result);
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        System.out.println("Error: " + error.getMessage());
                    }
                });
        System.out.println("url2:" + url);
        mQueue.add(req);
    }

实现 WeiboAuthListener 接口

class AuthListener implements WeiboAuthListener {

        @Override
        public void onComplete(Bundle values) {
            mAccessToken = Oauth2AccessToken.parseAccessToken(values);
            System.out.println("token:" + mAccessToken.getToken());
            System.out.println("values:" + values.toString());
            if (mAccessToken.isSessionValid()) {

                Toast.makeText(WeiboActivity.this,
                        "weibosdk_demo_toast_auth_success", Toast.LENGTH_SHORT)
                        .show();
            } else {
                String code = values.getString("code");
                String message = "weibosdk_demo_toast_auth_failed";
                if (!TextUtils.isEmpty(code)) {
                    message = message + "\nObtained the code: " + code;
                }
                Toast.makeText(WeiboActivity.this, message, Toast.LENGTH_LONG)
                        .show();
            }
        }

        @Override
        public void onCancel() {
            Toast.makeText(WeiboActivity.this,
                    "weibosdk_demo_toast_auth_canceled", Toast.LENGTH_LONG)
                    .show();
        }

        @Override
        public void onWeiboException(WeiboException e) {
            Toast.makeText(WeiboActivity.this,
                    "Auth exception : " + e.getMessage(), Toast.LENGTH_LONG)
                    .show();
        }
    }

创建WXAPI:

IWXAPI api = WXAPIFactory.createWXAPI(this, APP_ID, false);
api.handleIntent(getIntent(), this);

以上过程可在WXEntryActivity的onCreate方法中执行。注意:在发出请求之前需要先生成WXEntryActivity的实例,否则onResp方法不会被调用。

发送请求:

api = WXAPIFactory.createWXAPI(this, APP_ID, true);
        api.registerApp(APP_ID);
        final com.tencent.mm.sdk.modelmsg.SendAuth.Req req = new com.tencent.mm.sdk.modelmsg.SendAuth.Req();
        req.scope = "snsapi_userinfo,snsapi_base";
        req.state = "none";
        Toast.makeText(this, "getToken()", Toast.LENGTH_SHORT).show();
        api.sendReq(req);

注意:req.scope = “snsapi_userinfo,snsapi_base”;只填任何一个都不行。