微信公众号快速开发(四)微信网页授权

4,674 阅读6分钟

引入授权

OAuth2概念引入

OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 2.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。

以上来自百度百科,即OAuth由Resourse Owner(资源所有者),Client(客户端),以及Provider(服务提供商)组成,Provider包括Authorization Server(认证服务器)和Resource Server(资源服务器)两部分。

微信的网页授权是基于OAuth2.0协议。比如当我们的公众号中要修改用户的基本信息时,公众号就要先获取微信中的用户数据;又或者用户修改图片时需要访问手机的相册。如果按传统的方式,用户登录公众号之后,第三方的公众号就可以随意从微信端获取用户信息,或是手机的全部数据,这就造成了密码泄露的隐患,当你不想使用该公众号又担心数据外泄,只能修改密码。

微信网页授权流程

OAuth协议的出现正是为了解决上述这些问题。他将用户名密码授权方式改为通过令牌授权,第三方应用请求访问资源,资源所有者(用户)同意授权后,再次访问服务提供商(微信官方)去向认证服务器申请令牌(Token),认证服务器同意后发放令牌(Token),然后第三方应用再拿这个令牌(Token)去向资源服务器(微信官方)申请想要的资源,资源服务器验证通过后开放这个应用所需的资源。

具体而言,网页授权流程分为四步:

  1. 引导用户进入授权页面同意授权,获取code
  2. 通过code换取网页授权access_token(与基础支持中的access_token不同)
  3. 如果需要,开发者可以刷新网页授权access_token,避免过期
  4. 通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

具体介绍参见公众号开发文档->微信网页授权

开发准备

一、微信web开发者工具

便于在客户端调式,下载地址:微信web开发者工具

二、配置网页账号

微信测试公众平台->体验接口权限->网页服务->网页账号->网页授权获取用户基本信息->修改

这里暂时还是使用内网穿透的域名,若需要开发微信支付功能,需使用真实域名。

三、配置静态页面

根据开发文档的返回json,拟做一个页面,用于显示用户的信息

静态资源位置:templates/person.html,该页面用于显示用户的详细信息

开发步骤

用户同意授权,获取code

一、URL

根据文档,引导网页授权的URL的格式为:open.weixin.qq.com/connect/oau…

注意:

  1. 微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问;
  2. 回调redirect_uri,应当使用https链接来确保授权code的安全;
  3. 网页授权作用域为snsapi_userinfo,后续便可以通过access_tokenopenid拉取用户信息;
  4. 如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE;
  5. 参数说明,如下图:

二、添加转发页面的控制器

因为本项目暂时使用的时thymeleaf模板,无法直接访问项目中的静态资源,因此需要配置重定向的Controller。(为了跳转方便,后面页面与URL保持不重名)

  • IndexController:
@Controller
@RequestMapping("/api/v1/wechat1")
public class IndexController {

    // 用于thymeleaf环境下,跳转到字符串相应的html页面
    @RequestMapping("/{path}")
    public String webPath(@PathVariable String path) {
        return path;
    }

    @RequestMapping("/index")
    public void index(String code, Model model, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 显式授权,获得code
        if (code != null) {
            JSONObject accessTokenJson = WeChatUtil.getWebAccessToken(code);
            WXPayUtil.getLogger().info("获取网页授权的AccessToken凭据: "+accessTokenJson.toJSONString());
            String openid = accessTokenJson.getString(("openid"));
            request.getSession().setAttribute("openid", openid);
            WXPayUtil.getLogger().info("index openid={}"+openid);
            // 重定向到预下单页面
            response.sendRedirect("user"); // 回调的访问地址
        } else {
            StringBuffer url = RequestUtil.getRequestURL(request);
            WXPayUtil.getLogger().info("index 请求路径:{}"+url);
            String path = WeChatUtil.WEB_REDIRECT_URL.replace("APPID", WeChatConstants.APP_ID).replace("REDIRECT_URI", url).replace("SCOPE", "snsapi_userinfo");
            WXPayUtil.getLogger().info("index 重定向:{}"+path);
            // 重定向到授权获取code的页面
            response.sendRedirect(path);
        }
    }
}    

三、启动项目,访问:chety.mynatapp.cc/api/v1/wech…

四、根据URL的格式,替换相关参数。访问: 微信授权页面

open.weixin.qq.com/connect/oau…

五、点击【同意】后,页面将自动跳转到【/person.html】中

在重定向的URL中,可以看到code和state(这里没有设置,所以为空)

code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期

通过code换取网页授权access_token

一、URL

获取code后,请求以下链接获取access_token: api.weixin.qq.com/sns/oauth2/…

由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。

二、添加获取access_token的方法

  • WeChatUtil
//获取网页授权accessToken的接口
public static final String GET_WEB_ACCESSTOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";


/**
 * 通过code换取网页授权access_token
 * @param code
 * @return
 */
public static JSONObject getWebAccessToken(String code){
    String result = HttpUtil.get(GET_WEB_ACCESSTOKEN_URL.replace("APPID", WeChatConstants.APP_ID).replace("SECRET", WeChatConstants.APPSECRET).replace("CODE", code));
    return JSONObject.parseObject(result);
}

返回说明

刷新access_token(如果需要)

由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权。

一、URL

获取第二步的refresh_token后,请求以下链接获取access_token: api.weixin.qq.com/sns/oauth2/…

grant_type填写为refresh_token

二、添加工具类方法

// 获取网页授权accessToken的接口
public static final String GET_WEB_ACCESSTOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";

/**
 * 刷新access_token
 * @param refresh_token
 * @return
 */
public static JSONObject refreshWebAccessToken(String refresh_token){
    String result = HttpUtil.get(GET_WEB_ACCESSTOKEN_URL.replace("APPID", WeChatConstants.APP_ID).replace("REFRESH_TOKEN", refresh_token));
    return JSONObject.parseObject(result);
}

拉取用户信息(需scope为 snsapi_userinfo)

一、URL

http:GET(请使用https协议) api.weixin.qq.com/sns/userinf…

二、添加工具类方法

/**
 * 获取用户信息
 * @param accessToken
 * @param openId
 * @return
 */
public static JSONObject getUserInfo(String accessToken,String openId){
    String result = HttpUtil.get(GET_USERINFO_URL.replace("ACCESS_TOKEN", accessToken).replace("OPENID",openId));
    return JSONObject.parseObject(result);
}

三、添加跳转用户信息的接口

/**
 * 网页授权获取用户信息
 * @param code
 * @return
 */
@RequestMapping("/user")
public String person(String code,ModelMap map){
    if(code!=null) {
        //通过code来换取access_token
        JSONObject result = WeChatUtil.getWebAccessToken(code);
        //通过access_token和openid拉取用户信息
        JSONObject userInfo = WeChatUtil.getUserInfo(result.getString("access_token"), result.getString("openid"));
        WXPayUtil.getLogger().info("用户信息为:{}"+ JSON.toJSONString(userInfo));
        //获取json对象中的键值对集合
        Set<Map.Entry<String, Object>> entries = userInfo.entrySet();
        for (Map.Entry<String, Object> entry : entries) {          
            map.addAttribute(entry.getKey(),entry.getValue());
        }
    }
    return "person";
}

这里包含用户信息的键名直接用了微信返回的默认值,person页面也使用该默认名。

四、再次访问:

open.weixin.qq.com/connect/oau…

可以看到,已经获取了微信中返回的信息

附:检验授权凭证(access_token)是否有效

http:GET(请使用https协议) api.weixin.qq.com/sns/auth?ac…