【微信开放平台】微信第三方扫码登录(亲测可用)

开放平台需要企业认证才能注册,正好这次公司提供了一个账号,调通以后,就顺便写一篇博客吧。

公众平台与开放平台的区别

微信开放平台

主要面对移动应用/网站应用开发者,为其提供微信登录、分享、支付等相关权限和服务。

微信公众平台

微信公众平台用于管理、开放微信公众号(包括订阅号、服务号、企业号),简单的说就是微信公众号的后台运营、管理系统。

这里想吐槽一下,微信基本注册全都要邮箱,公众号一个、小程序一个、开放平台一个、我哪他妈有这么多邮箱啊,又记不住密码,下面正题。

官方文档链接

准备工作

网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统。
在进行微信OAuth2.在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用,并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程。

image.png

点击创建网站应用
image.png

按照要求填写信息,等待审核通过就可以获取appid和appsecret
image.png

然后找到回调授权域名这一项 修改成要回调的域名,例如:www.baidu.com 不要加http://或https://
image.png

好了需要提前准备的东西就完事了,下面就是代码部分了。


image.png

这是完整的调用图
我这里用的spring boot项目进行测试的,但是代码都大致相同


image.png

第一步:请求CODE

这是自己用到的HttpClientUtils工具类

package com.lj.demo.controller.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.HttpClients;

import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.apache.commons.io.IOUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
/**
 * @author LvJun
 * @create 2018-03-16 17:51
 **/

    public class HttpClientUtils {

        public static final int connTimeout=10000;
        public static final int readTimeout=10000;
        public static final String charset="UTF-8";
        private static HttpClient client = null;

        static {
            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
            cm.setMaxTotal(128);
            cm.setDefaultMaxPerRoute(128);
            client = HttpClients.custom().setConnectionManager(cm).build();
        }

        public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
            return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
        }

        public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
            return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
        }

        public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
                SocketTimeoutException, Exception {
            return postForm(url, params, null, connTimeout, readTimeout);
        }

        public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
                SocketTimeoutException, Exception {
            return postForm(url, params, null, connTimeout, readTimeout);
        }

        public static String get(String url) throws Exception {
            return get(url, charset, null, null);
        }

        public static String get(String url, String charset) throws Exception {
            return get(url, charset, connTimeout, readTimeout);
        }

        /**
         * 发送一个 Post 请求, 使用指定的字符集编码.
         *
         * @param url
         * @param body RequestBody
         * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
         * @param charset 编码
         * @param connTimeout 建立链接超时时间,毫秒.
         * @param readTimeout 响应超时时间,毫秒.
         * @return ResponseBody, 使用指定的字符集编码.
         * @throws ConnectTimeoutException 建立链接超时异常
         * @throws SocketTimeoutException  响应超时
         * @throws Exception
         */
        public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
                throws ConnectTimeoutException, SocketTimeoutException, Exception {
            HttpClient client = null;
            HttpPost post = new HttpPost(url);
            String result = "";
            try {
                if (StringUtils.isNotBlank(body)) {
                    HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
                    post.setEntity(entity);
                }
                // 设置参数
                Builder customReqConf = RequestConfig.custom();
                if (connTimeout != null) {
                    customReqConf.setConnectTimeout(connTimeout);
                }
                if (readTimeout != null) {
                    customReqConf.setSocketTimeout(readTimeout);
                }
                post.setConfig(customReqConf.build());

                HttpResponse res;
                if (url.startsWith("https")) {
                    // 执行 Https 请求.
                    client = createSSLInsecureClient();
                    res = client.execute(post);
                } else {
                    // 执行 Http 请求.
                    client = HttpClientUtils.client;
                    res = client.execute(post);
                }
                result = IOUtils.toString(res.getEntity().getContent(), charset);
            } finally {
                post.releaseConnection();
                if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
                    ((CloseableHttpClient) client).close();
                }
            }
            return result;
        }


        /**
         * 提交form表单
         *
         * @param url
         * @param params
         * @param connTimeout
         * @param readTimeout
         * @return
         * @throws ConnectTimeoutException
         * @throws SocketTimeoutException
         * @throws Exception
         */
        public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
                SocketTimeoutException, Exception {

            HttpClient client = null;
            HttpPost post = new HttpPost(url);
            try {
                if (params != null && !params.isEmpty()) {
                    List<NameValuePair> formParams = new ArrayList<org.apache.http.NameValuePair>();
                    Set<Entry<String, String>> entrySet = params.entrySet();
                    for (Entry<String, String> entry : entrySet) {
                        formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                    }
                    UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
                    post.setEntity(entity);
                }

                if (headers != null && !headers.isEmpty()) {
                    for (Entry<String, String> entry : headers.entrySet()) {
                        post.addHeader(entry.getKey(), entry.getValue());
                    }
                }
                // 设置参数
                Builder customReqConf = RequestConfig.custom();
                if (connTimeout != null) {
                    customReqConf.setConnectTimeout(connTimeout);
                }
                if (readTimeout != null) {
                    customReqConf.setSocketTimeout(readTimeout);
                }
                post.setConfig(customReqConf.build());
                HttpResponse res = null;
                if (url.startsWith("https")) {
                    // 执行 Https 请求.
                    client = createSSLInsecureClient();
                    res = client.execute(post);
                } else {
                    // 执行 Http 请求.
                    client = HttpClientUtils.client;
                    res = client.execute(post);
                }
                return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
            } finally {
                post.releaseConnection();
                if (url.startsWith("https") && client != null
                        && client instanceof CloseableHttpClient) {
                    ((CloseableHttpClient) client).close();
                }
            }
        }




        /**
         * 发送一个 GET 请求
         *
         * @param url
         * @param charset
         * @param connTimeout  建立链接超时时间,毫秒.
         * @param readTimeout  响应超时时间,毫秒.
         * @return
         * @throws ConnectTimeoutException   建立链接超时
         * @throws SocketTimeoutException   响应超时
         * @throws Exception
         */
        public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
                throws ConnectTimeoutException,SocketTimeoutException, Exception {

            HttpClient client = null;
            HttpGet get = new HttpGet(url);
            String result = "";
            try {
                // 设置参数
                Builder customReqConf = RequestConfig.custom();
                if (connTimeout != null) {
                    customReqConf.setConnectTimeout(connTimeout);
                }
                if (readTimeout != null) {
                    customReqConf.setSocketTimeout(readTimeout);
                }
                get.setConfig(customReqConf.build());

                HttpResponse res = null;

                if (url.startsWith("https")) {
                    // 执行 Https 请求.
                    client = createSSLInsecureClient();
                    res = client.execute(get);
                } else {
                    // 执行 Http 请求.
                    client = HttpClientUtils.client;
                    res = client.execute(get);
                }

                result = IOUtils.toString(res.getEntity().getContent(), charset);
            } finally {
                get.releaseConnection();
                if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
                    ((CloseableHttpClient) client).close();
                }
            }
            return result;
        }


        /**
         * 从 response 里获取 charset
         *
         * @param ressponse
         * @return
         */
        @SuppressWarnings("unused")
        private static String getCharsetFromResponse(HttpResponse ressponse) {
            // Content-Type:text/html; charset=GBK
            if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
                String contentType = ressponse.getEntity().getContentType().getValue();
                if (contentType.contains("charset=")) {
                    return contentType.substring(contentType.indexOf("charset=") + 8);
                }
            }
            return null;
        }



        /**
         * 创建 SSL连接
         * @return
         * @throws GeneralSecurityException
         */
        private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
            try {
                SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                    public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
                        return true;
                    }
                }).build();

                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

                    @Override
                    public boolean verify(String arg0, SSLSession arg1) {
                        return true;
                    }

                    @Override
                    public void verify(String host, SSLSocket ssl)
                            throws IOException {
                    }

                    @Override
                    public void verify(String host, X509Certificate cert)
                            throws SSLException {
                    }

                    @Override
                    public void verify(String host, String[] cns,
                                       String[] subjectAlts) throws SSLException {
                    }

                });

                return HttpClients.custom().setSSLSocketFactory(sslsf).build();

            } catch (GeneralSecurityException e) {
                throw e;
            }
        }

}

微信有两种方式认证一种是调转到微信域名下,还一种是直接把二维码嵌套到自己网站中。

这里是跳转到微信的认证方式

首先在页面做一个简单的按钮 触发事件获取认证code

 <button onclick="login()">微信登录</button>

js调用部分

<script>
    function login() {
        $.ajax({
            type: "POST",
            //这个路径根据自己的情况填写
            url: "/demo-wechat/wechat/getCode",
            success: function (data) {
                console.log(data);
                if (data.code == 200) {
                    window.location.href = (data.result);
                } else {
                    alert("认证失败");
                }
            },
            error: function (data) {
                alert("认证失败");
            }
        });
    }
</script>

后台代码url参数都要根据自己的实际情况进行修改

    @RequestMapping("/getCode")
    public void getCode() throws Exception {
        //拼接url
        StringBuilder url = new StringBuilder();
        url.append("https://open.weixin.qq.com/connect/qrconnect?");
        //appid
        url.append("appid=" + "appid");
        //回调地址 ,回调地址要进行Encode转码 
        String redirect_uri = "回调地址"
        //转码
        url.append("&redirect_uri=" + URLEncodeUtil.getURLEncoderString(redirect_uri));
        url.append("&response_type=code");
        url.append("&scope=snsapi_login");
        HttpClientUtils.get(url.toString(), "GBK");
    }
参数 是否必填 说明
appid 应用唯一标识
redirect_uri 请使用urlEncode对链接进行处理
response_type 填code
scope 网页应用目前仅填写snsapi_login即可
state 安全性相关具体可以参考官方文档这里可以不填

参数说明

redirect_uri 这个参数是用户扫码确认以后微信调用自己的路径例如:https://www.baidu.com/wechat/callback
拼完路径可以打印出来看一下 完整的路径应该是这样
https://open.weixin.qq.com/connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https%3A%2F%2Fpassport.yhd.com%2Fwechat%2Fcallback.do&response_type=code&scope=snsapi_login&state=3d6be0a4035d839573b04816624a415e#wechat_redirect
到此获取code就完成了,用户确认以后回调自己的时候会带两个参数,
1.code
2.state
如果调用没有传递state就不会返回state 用户拒绝了登录请求,返回的结果不会有code

第二步:通过code获取access_token

接下来完成回调的方法直接上代码

/**
     * 微信回调
     */
    @RequestMapping("/callback")
    public ModelAndView callback(String code,String state) throws Exception {
        System.out.println("===="+code+"==="+state+"====");
        if(StringUtils.isNotEmpty(code)){
            StringBuilder url = new StringBuilder();
            url.append("https://api.weixin.qq.com/sns/oauth2/access_token?");
            url.append("appid=" + "appid");
            url.append("&secret=" + "secret");
            //这是微信回调给你的code
            url.append("&code=" + code);
            url.append("&grant_type=authorization_code");
            String result = HttpClientUtils.get(url.toString(), "UTF-8");
            System.out.println("result:" + result.toString());
        }
        return new ModelAndView("login");
    }

返回说明

正确的返回:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
错误返回样例:

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

这个时候我们拿到用了用户的一些必要的信息,剩下的就根据自己具体的业务进行后面的操作吧。

这里是第二种 认证方式,把二维码嵌套到自己的网站

这里只需要改前台代码
创建一个放二维码的容器

<div id="login_container" style="width: 500px;height: 500px;"></div>

导入微信的JS

//我这种写法是spring boot对应的 根据自己前端框架导入
<script th:src="@{http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js}"></script>

在JS里面实例这么一段代码

var obj = new WxLogin({
        id:"login_container",
        appid: "appid",
        scope: "snsapi_login",
        redirect_uri: "回调地址",
        state: "",
        style: "",//这个是二维码样式
        href: ""
    });

这样打开这个页面就会自动加载微信二维码。扫码认证后 后面流程不变,返回的也是code,根据code获取token。

这只是一个最简单入门的Demo,代码也有很多需要优化的地方,欢迎大家指正说明,最后感谢大家观看。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容