如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。我们在进行公众号网页开发的时候,想要获取用户的基本信息,首先得获取到access_token,从access_token里我们要拿出用户的openid来作为用户在我们系统中的唯一标识,以及通过openid可以保证该用户的只能访问到与其openid相对应的数据,防止越权漏洞。因此,我们需要对网页进行授权,否则是无法在获取到用户的openid的。

先上官方的文档,微信官方文档地址如下:

为了能够与微信进行联调,所以我们需要使用到内网穿透工具,让外网能够访问到我们内网的接口地址。我之前写了一篇关于如何使用natapp进行内网穿透的文章,这里就不再过多赘述这些基本的工具使用了:

在本文中会介绍两种获取openid的方式:自己根据文档接口手写代码获取、使用第三方已经封装好的SDK来获取。第三方SDK的GitHub地址:

说明与注意:

(1)网页授权分为两种:

  • 一种为只获取openid (基本授权 snsapi_base)
  • 一种为获取用户全部信息,仅限账户、昵称以及头像等信息 (高级授权 snsapi_userinfo)

(2)你的公众号必须为认证的订阅号或者认证的服务号或申请官方提供的测试号,否则没有此接口权限。

(3)你要配置好回调域名:即用户点击网址获取用户信息后打开哪个域名。


测试号申请及设置外网域名

我们先来申请一个官方的测试号,微信测试账号申请地址如下:

使用微信测试账号对网页进行授权

登录成功后,即可获取测试号信息:
使用微信测试账号对网页进行授权

下拉页面找到测试号二维码那一栏,然后使用微信扫描二维码关这个注测试公众号。关注成功后,稍等一会就会显示出用户列表。如下:
使用微信测试账号对网页进行授权

配置完测试号后,继续下拉页面找到网页帐号的设置,点击修改:
使用微信测试账号对网页进行授权

然后将我们的在natapp里注册外网域名配置进去:
使用微信测试账号对网页进行授权

注:由于是测试号,这一块不会严格去检测这个域名,如果是使用真实的公众账号进行配置时,会对配置的域名进行检测。但是即便是测试,也要保证这个域名是可用的,不然就无法进行联调了。


手动获取openid

(1)第一步,用户同意授权,获取code参数:

完成以上测试账号的配置及微信网页授权后,创建一个SpringBoot的Web工程,并打开natapp的客户端。关于获取code这一步要仔细查看这部分文档,我就不过多解释了:
使用微信测试账号对网页进行授权
使用微信测试账号对网页进行授权

从文档中可以看到,需要让用户在访问页面的过程中打开一个特定的链接,然后用户授权成功后会跳转回调redirect_uri参数里指定的链接,这样我们就能获取code参数了。so,在工程中创建controller包,在该包里新建一个 WeixinController 控制器,我们来写一个这样的跳转回调接口给微信进行回调,看看能否获取到回调时传递的code参数:

package org.sell.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: sell
 * @description: 微信网页授权,获取用户openid
 * @author: 01
 * @create: 2018-05-15 13:35
 **/
@RestController
@RequestMapping("/weixin")
@Slf4j
public class WeixinController {

    @GetMapping("/auth")
    public void auth(@RequestParam("code") String code) {
        log.info("进入auth方法...");
        log.info("code = {}", code);
    }
}

编写完接口代码之后,我们来基于文档中给出的链接进行修改,我这里填写了appid、redirect_uri以及scope三个参数,其余的参数我保持了默认。所以填写完成后的连接如下:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx37617xxxxxxc6c76&redirect_uri=https://zero.mynatapp.cc/sell/weixin/auth&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect

如果需要获取用户信息的话,则需要把scope参数改为snsapi_userinfo,用户访问这个连接时就会提示用户登录。但是如果是使用的测试账号的appid,则不会弹出登录界面。使用真实的公众号账号的appid才会弹出登录界面,我们使用的是测试账户所以是不会弹出登录界面的:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx376172f171ac6c76&redirect_uri=https://zero.mynatapp.cc/sell/weixin/auth&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

然后复制该链接到微信中打开,一定要在微信中打开,使用pc端的浏览器或手机端的浏览器是无法打开该连接的,打开后也会提示你在微信中打开。在微信访问该地址成功后,控制台会输出如下内容,其中code参数获取成功就代表这一步测试是成功的:

2018-05-15 13:47:02,365 - 进入auth方法...
2018-05-15 13:47:02,365 - code = 0219vS7617imVS1dqe861eXP7619vS7j

但是code是一次性的,不能直接使用,所以接下来的第二步我们需要拿这个code去换取access_token。


使用code来换取access_token

关于使用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以及code。由于code参数我们需要在auth接口被微信调用时获取,所以修改auth接口的代码如下:

@GetMapping("/auth")
public void auth(@RequestParam("code") String code) {
    log.info("进入auth方法...");
    log.info("code = {}", code);

    String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx3761xxxxx1ac6c76&secret=f12caef4d31axxxxx819c4c405541f4&code=" + code + "&grant_type=authorization_code";
    RestTemplate restTemplate = new RestTemplate();
    String response = restTemplate.getForObject(url, String.class);
    log.info("response = {}", response);
}

重新启动SpringBoot,然后再次到微信里访问之前那个获取code参数的链接,访问成功后,微信会进行会回调,我们的接口就会接收到一个json格式的返回信息,其中就有我们需要的openid。我这里是访问成功的,所以获取到了这个返回信息,将其格式化后如下:

{
  "access_token": "9_lZjiw19HhAga4cZ9C4rmtN50AkLzCwLVnlp8msI9xvCCWMIR88CmZAtB-IN1SGOwR_nh76V100vaTF8qihmBnfDx7XYynqHurb4UiqSZTDo",
  "expires_in": 7200,
  "refresh_token": "9_oMBSt0Bu-UUbVajpfo4OdyPwIfYbN1lut8KHjT4JEMTTbITtPnBFcQopP-SIzYvWHQuEdeQRcVHt1pRJHwvDhecDrd5pGz5HPckOCeHvmxg",
  "openid": "ok_HP0txxYBxxxxxxd1kZGz6A",
  "scope": "snsapi_base"
}

从回调的数据中可以看到,openid包含在了这个数据里,这样我们就获取到了用户的openid,到此为止,我们手工获取OpenID的方式就演示完成了。接下来我们当然就是上工具了,有一个很优秀的第三方SDK,在文章的开头也给出了GitHub的链接,秉承一贯不重复造轮子能用现成的就用现成的良(hou)好(yan)习(wu)惯(chi),下一小节中我们将介绍如何使用该SDK来对接微信网页授权。


使用第三方SDK获取openid

首先需要将SDK集成到我们的项目中,在pom.xml文件中加入如下依赖配置:

<dependency>
  <groupId>com.github.binarywang</groupId>
  <artifactId>weixin-java-mp</artifactId>
  <version>3.0.0</version>
</dependency>

注:工程中还使用了lombok,如果没有了解过lombok的话,可能需要了解一下才能看懂一些注解的作用,关于lombok的使用及配置可以参考我之前写的一篇文章:Lombok快速入门

关于该SDK的一些文档地址:

由于可能会出现异常,在我们的工程里新建一个enums包,用于存放枚举类,在该包中新建一个 ResultEnum 枚举类,代码如下:

package org.sell.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum ResultEnum {
    WECHAT_MP_ERROR(20, "微信公众账号出现异常"),
   ;

    private Integer code;
    private String msg;
}

新建一个exception包,自定义一个异常类。代码如下:

package org.sell.exception;

import org.sell.enums.ResultEnum;

/**
 * @program: sell
 * @description: 自定义异常
 * @author: 01
 * @create: 2018-05-09 15:45
 **/
public class SellException extends RuntimeException {

    private Integer code;

    public SellException(Integer code, String msg) {
        super(msg);
        this.code = code;
    }

    public SellException(ResultEnum resultEnum) {
        super(resultEnum.getMsg());
        this.code = resultEnum.getCode();
    }
}

将默认的applicationContext.properties文件改为applicationContext.yml文件,然后在该文件中配置我们的AppId以及AppSecret。如下:

wechat:
  mpAppId: wx37617xxxxxac6c76
  mpAppSecret: f12caef4d31adxxxxxxc4c405541f4

在工程中新建config包,用于存放一些配置类,在该包下新建 WechatAccountConfig 类,用于加载我们的配置在.yml文件里的AppId以及AppSecret属性。代码如下:

package org.sell.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @program: sell
 * @description: 测试号相关信息的配置类
 * @author: 01
 * @create: 2018-05-15 15:50
 **/
@Data
@Component
@ConfigurationProperties(prefix = "wechat")  // 载入配置文件里前缀为wechat的配置信息
public class WechatAccountConfig {
    private String mpAppId;
    private String mpAppSecret;
}

接着再创建一个 WechatMpConfig 类,配置微信网页授权时所需的信息。代码如下:

package org.sell.config;

import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @program: sell
 * @description: 微信网页授权信息配置类
 * @author: 01
 * @create: 2018-05-15 15:40
 **/
@Component
public class WechatMpConfig {

    @Autowired
    private WechatAccountConfig wechatAccountConfig;

    /**
     * 配置WxMpService所需信息
     * @return
     */
    @Bean  // 此注解指定在Spring容器启动时,就执行该方法并将该方法返回的对象交由Spring容器管理
    public WxMpService wxMpService(){
        WxMpService wxMpService = new WxMpServiceImpl();
        // 设置配置信息的存储位置
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());

        return wxMpService;
    }

    /**
     * 配置appID和appsecret
     * @return
     */
    @Bean
    public WxMpConfigStorage wxMpConfigStorage(){
        // 使用这个实现类则表示将配置信息存储在内存中
        WxMpInMemoryConfigStorage wxMpInMemoryConfigStorage = new WxMpInMemoryConfigStorage();
        wxMpInMemoryConfigStorage.setAppId(wechatAccountConfig.getMpAppId());
        wxMpInMemoryConfigStorage.setSecret(wechatAccountConfig.getMpAppSecret());

        return wxMpInMemoryConfigStorage;
    }
}

在controller包下,新建一个 WechatController 控制器类,该类的接口用于对接微信网页授权,授权完成后,需跳转到我们指定的网页中。代码如下:

package org.sell.controller;

import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.sell.enums.ResultEnum;
import org.sell.exception.SellException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.net.URLEncoder;

/**
 * @program: sell
 * @description: 对接微信网页授权
 * @author: 01
 * @create: 2018-05-15 15:35
 **/
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WechatController {

    @Autowired
    private WxMpService wxMpService;

    /**
     * 获取code参数
     *
     * @param returnUrl 需要跳转的url
     * @return
     */
    @GetMapping("/authorize")
    public String authorize(@RequestParam("returnUrl") String returnUrl) {
        // 暂时将我们的回调地址硬编码在这里,方便一会调试
        String url = "https://zero.mynatapp.cc/sell/wechat/userInfo";
        // 获取微信返回的重定向url
        String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_BASE, URLEncoder.encode(returnUrl));
        log.info("【微信网页授权】获取code,redirectUrl = {}", redirectUrl);

        return "redirect:" + redirectUrl;
    }

    /**
     * 使用code参数换取access_token信息,并获取到openid
     *
     * @param code
     * @param returnUrl
     * @return
     */
    @GetMapping("/userInfo")
    public String userInfo(@RequestParam("code") String code, @RequestParam("state") String returnUrl) {
        WxMpOAuth2AccessToken wxMpOAuth2AccessToken;
        try {
            // 使用code换取access_token信息
            wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
        } catch (WxErrorException e) {
            log.error("【微信网页授权】异常,{}", e);
            throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
        }
        // 从access_token信息中获取到用户的openid
        String openId = wxMpOAuth2AccessToken.getOpenId();
        log.info("【微信网页授权】获取openId,openId = {}", openId);

        // 重定向到我们要跳转的页面
        return "redirect:" + returnUrl + "?openid=" + openId;
    }
}

完成以上代码的编写后,重新启动SpringBoot,启动完成后,在微信里访问如下链接,我这里随便指定了一个需要跳转的url,是我的51cto博客地址:

https://zero.mynatapp.cc/sell/wechat/authorize?returnUrl=http://blog.51cto.com/zero01

我这里在微信访问该连接后是跳转成功的,跳转到了我们在链接里指定的51cto博客地址:
使用微信测试账号对网页进行授权

控制台输出的日志内容如下:

2018-05-15 17:09:03,543 - Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-05-15 17:09:03,544 - FrameworkServlet 'dispatcherServlet': initialization started
2018-05-15 17:09:03,557 - FrameworkServlet 'dispatcherServlet': initialization completed in 13 ms
2018-05-15 17:09:03,580 - 【微信网页授权】获取code,redirectUrl = https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx376172xxxxc76&redirect_uri=https%3A%2F%2Fzero.mynatapp.cc%2Fsell%2Fwechat%2FuserInfo&response_type=code&scope=snsapi_base&state=http%3A%2F%2Fblog.51cto.com%2Fzero01#wechat_redirect
2018-05-15 17:09:05,453 - 【微信网页授权】获取openId,openId = ok_HP0txxYBgxxxxxx9d1kZGz6A

可以看到,openid成功获取,并且页面也成功跳转了,我们这一步的测试也就通过了,以上就是如何使用这个第三方的SDK获取openid。到此为止,两种获取用户openid的方式都介绍了,至于在实际项目里使用哪一种方式就看自己的实际情况了,我这里是使用第三方的SDK,毕竟真实的项目模块比较多,涉及的微信特性也比较多,如果没有特殊要求的话也没必要重复去造轮子。