微信公众号配置 Token 认证以及消息推送功能(一)

本文涉及的产品
.cn 域名,1个 12个月
简介: 微信公众号配置 Token 认证以及消息推送功能

前言

基于后端服务,操作微信公众号,定制化菜单、消息推送、消息回复等功能,都需要通过服务端进行统一的 Token 认证,这样双方才能形成绑定的关系,从而实现相互认知后进行通信.

公众号服务配置 Token认证

接入服务器配置的目的:接入微信第三方开发平台,在微信公众平台提供的基础服务上开发微网站、微商城、活动应用zd、娱乐插件等扩展功能

如何配置

服务器基本配置

URL必须是以 HTTP 或 HTTPS 开头,必须是在线上可访问的域名地址,部署在本地不可以,/wx/auth 是后台的接口地址,Token 是在后端服务配置的一个固定值,必须要一致相互才能 ping 通,其他如上图所示,点击提交后进行启用

Token 认证接口

/wx/auth 接口源码如下:

/**
 * @author vnjohn
 * @since 2023/2/19
 */
@Slf4j
@RestController("/wx")
@Api(value = "微信服务器 token 认证", tags = "微信服务器 token 认证 接口API")
public class WxPortalController {
    @GetMapping("/auth")
    public void authPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (StringUtils.isNotBlank(request.getParameter("signature"))) {
            String signature = request.getParameter("signature");
            String timestamp = request.getParameter("timestamp");
            String nonce = request.getParameter("nonce");
            String echostr = request.getParameter("echostr");
            log.info("signature[{}], timestamp[{}], nonce[{}], echostr[{}]", signature, timestamp, nonce, echostr);
            if (SignUtil.checkSignature(signature, timestamp, nonce)) {
                log.info("数据源为微信后台,将echostr[{}]返回!", echostr);
                response.getOutputStream().println(echostr);
            }
        }
    }
}

Token 认证类源码如下:

/**
 * @author vnjohn
 * @since 2023/2/19
 */
public class SignUtil {
    /**
     * 这里是自定义 Token,需和你在公众号后台提交的 Token 保持一致
     */
    private static final String TOKEN = "familySchool";
    /**
     * 校验签名
     *
     * @param signature 签名
     * @param timestamp 时间戳
     * @param nonce     随机数
     * @return 布尔值
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        String checktext = null;
        if (null != signature) {
            // 对ToKen,timestamp,nonce 按字典排序
            String[] paramArr = new String[]{TOKEN, timestamp, nonce};
            Arrays.sort(paramArr);
            // 将排序后的结果拼成一个字符串
            String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
            try {
                MessageDigest md = MessageDigest.getInstance("SHA-1");
                // 对接后的字符串进行sha1加密
                byte[] digest = md.digest(content.getBytes());
                checktext = byteToStr(digest);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }
        // 将加密后的字符串与signature进行对比
        return checktext != null && checktext.equals(signature.toUpperCase());
    }
    /**
     * 将字节数组转化为16进制字符串
     *
     * @param byteArrays 字符数组
     * @return 字符串
     */
    private static String byteToStr(byte[] byteArrays) {
        String str = "";
        for (int i = 0; i < byteArrays.length; i++) {
            str += byteToHexStr(byteArrays[i]);
        }
        return str;
    }
    /**
     * 将字节转化为十六进制字符串
     *
     * @param myByte 字节
     * @return 字符串
     */
    private static String byteToHexStr(byte myByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tampArr = new char[2];
        tampArr[0] = Digit[(myByte >>> 4) & 0X0F];
        tampArr[1] = Digit[myByte & 0X0F];
        String str = new String(tampArr);
        return str;
    }
}

公众号获取网页授权及用户信息

导向

获取网页授权前提是我们需要在公众号上配置好网页授权的域名,配置好以后,我们需要一个触发点去触发这个网页(H5)以便用户点击我们设置好的触发按钮或事件时能拿到其对应的用户信息,本篇文章我们采用被关注自动回复消息,点击消息触发

此处介绍公众号UNION_ID和OPEN_ID区别:

  • UNION_ID:在同一个主体下,公众号、小程序、APP 涉及到微信应用的时候,所拿到的 UNIONID 都是一样的,打个比方:相当于身份证,在哪个地方都可以用到而且是唯一的
  • OPEN_ID:公众号、小程序都会有一个会话 ID 就是 OPEN_ID,一拿到这个会话 ID 我们就能在这个应用作为唯一标识通行,打个比方:社保卡在我们购买药品时可以用到、驾驶证在我们出行时可以使用

同一个微信开放平台下的相同主体的 App、公众号、小程序,如果用户已经关注公众号,或者曾经登录过 App 或公众号,则用户打开小程序时,开发者可以直接通过 wx.login 获取到该用户 UNION_ID,无须用户再次授权。如果用户未关注公众号,可以通过 wx.getUserInfo 获取到该用户 UNION_ID

网页授权

首先要拿到网页授权的接口权限

修改网页授权接口时,要修改 JS 接口安全域名以及网页授权域名

上图需要下载对应的文件,前提是域名/文件名需要访问到你对应的文件内容才能配好网页授权域名,代码如下:

/**
 * @author vnjohn
 * @since 2023/2/19
 */
@RestController
public class DomainAuthController {
    /**
     * 网页授权域名,需要把用到的文件下载下来,文件名是需要请求的接口
     * 返回的是文件对应的内容
     */
    @GetMapping("MP_verify_zxgx6O368tVx5sDK.txt")
    private String returnConfigFile() {
        //把 MP_verify_xxxxxx.txt 中的内容返回
        return "zxgx6O368tVx5sDK";
    }
}

关注后消息触发授权

在导向中提及到的 被关注后自动回复消息点击触发

URL 地址:<a href="https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx6a14ea8110b086af&redirect_uri=http%3A%2F%2Fxxxx%2Fwx%2FwxLogin&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect">点击绑定你的个人信息</a>

域名是我们配置好网页授权域名,接口地址是我们后台处理的接口

官方介绍:微信网页授权认证

redirect_uri 回调的链接地址是需要通过 utf-8 编码过后的,如下:

java.net.URLEncoder.encode(source, "utf-8");
• 1

WxConstant 类是自定义微信常量类:存放请求 TOKEN Address、APPID、SECRET

/**
 * @author vnjohn
 * @since 2023/2/19
 */
public class WxConstant {
    /**
     * 小程序授权grant_type
     */
    public static final String AUTHORIZATION_CODE = "023VMFlQ0fMEp72wK7kQ0FIqlQ0VMFlu";
    /**
     * 打卡模板id
     */
    public final static String PUNCH_TEMPLATE_ID = "0pMwyHxbobrotPdcbkIUt3fY03pnVm9Ril4KEqLmeNE";
    /**
     * 收款模板id
     */
    public final static String COLLECTION_TEMPLATE_ID = "7vdNCysQL64KBQtHG4xy2wsTQWyvQo4P66HPDvXbIHE";
    /**
     * 学校通知模板id
     */
    public final static String MSG_TEMPLATE_ID = "DVBa7LpPssglj5DYgNtenxDhZ-8DBuxnctVgiwnhGxs";
    /**
     * 公众号APP_ID
     */
    public final static String GZH_APP_ID = "公众号APP_ID";
    /**
     * 公众号SECRET
     */
    public final static String GZH_SECRET = "公众号SECRET";
    /**
     * 公众号获取用户信息的 URL
     */
    public final static String GZH_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
    /**
     * 小程序APP_ID
     */
    public final static String APPLET_APP_ID = "小程序APP_ID";
    /**
     * 小程序APP_SECRET
     */
    public final static String APPLET_SECRET = "小程序APP_SECRET";
    /**
     * 小程序获取token凭证获取(GET)url、公众号推送消息获取token凭证
     */
    public final static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" +
            "APPID&secret=SECRET";
    /**
     * 生成小程序二维码
     */
    public final static String QRCODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESSTOKEN";
    /**
     * 公众号推送模板消息URL
     */
    public final static String MSG_API = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESSTOKEN";
    /**
     * 获取网页授权凭证URL
     */
    public final static String AUTH_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
}
@Data
public class Oauth2Token {
    /**
     * 网页授权接口调用凭证
     */
    private String accessToken;
    /**
     * 凭证有效时长
     */
    private int expiresIn;
    /**
     * 用于刷新凭证
     */
    private String refreshToken;
    /**
     * 用户标识
     */
    private String openId;
    /**
     * 用户授权作用域
     */
    private String scope;
}

/wx/login 接口源码如下:

/**
 * @author vnjohn
 * @since 2023/2/19
 */
@Slf4j
@RestController
@RequestMapping("/wx/wxLogin")
public class WxLoginController {
    @GetMapping
    public void weixinLogin(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 用户同意授权后,能获取到code
        String code = request.getParameter("code");//拿到code的值
        logger.info("****************code:" + code);
        // 用户同意授权
        if (!"authdeny".equals(code)) {
            // 获取网页授权access_token
            Oauth2Token oauth2Token = getOauth2AccessToken(WxConstant.GZH_APP_ID, WxConstant.GZH_SECRET, code);
            logger.info("***********************************oauth2Token信息:" + oauth2Token.toString());
            // 网页授权接口访问凭证
            String accessToken = oauth2Token.getAccessToken();
            // 用户标识
            String openId = oauth2Token.getOpenId();
            // 获取用户信息
            WxUserInfo wxUserInfo = getWxUserInfo(accessToken, openId);
            logger.info("***********************************用户信息unionId:" + wxUserInfo.getUnionid() + "***:" + wxUserInfo.getNickname());
            //到数据库查询 该 用户信息是否存在
            int result = userService.queryIsExists(wxUserInfo.getUnionid());
            if (result < 1) {//添加一条新的记录
                User communityUser = new User();
                communityUser.setNickName(wxUserInfo.getNickname());
                communityUser.setOpenId(wxUserInfo.getOpenId());
                communityUser.setUnionId(wxUserInfo.getUnionid());
                result = userService.addUser(communityUser);
                if (result > 0) {
                    logger.info("绑定用户信息成功");
                } else {
                    logger.info("绑定用户信息失败");
                }
            } else {
                // 这种情况出现:已关注->取关->再次关注(数据库肯定存在记录,此时重新删除,再次添加)
                userService.delInfo(wxUserInfo.getUnionid());
                User communityUser = new User();
                communityUser.setNickName(wxUserInfo.getNickname());
                communityUser.setOpenId(wxUserInfo.getOpenId());
                communityUser.setUnionId(wxUserInfo.getUnionid());
                result = userService.addUser(communityUser);
                if (result > 0) {
                    logger.info("绑定用户信息成功");
                } else {
                    logger.info("绑定用户信息失败");
                }
            }
            // 设置要传递的参数
            //具体业务start
            logger.info("openId:" + openId);
            //具体业务end
            logger.info(wxUserInfo.toString());
            //重定向到我们需要跳转的地址 由前端编写的h5页面 必须是部署到服务器上的
            response.sendRedirect("跳转的网页地址");
            return;
        }
        return;
    }
    /**
     * 获取网页授权凭证
     *
     * @param appId     公众账号的唯一标识
     * @param appSecret 公众账号的密钥
     * @param code
     * @return Oauth2Token
     */
    public static Oauth2Token getOauth2AccessToken(String appId, String appSecret, String code) {
        Oauth2Token wat = null;
        // 拼接请求地址
        String requestUrl = WxConstant.AUTH_URL.replace("APPID", appId).replace("SECRET", appSecret).replace("CODE", code);
        // 获取网页授权凭证
        JSONObject jsonObject = JSON.parseObject(HttpUtil.get(requestUrl));
        if (null != jsonObject) {
            try {
                wat = new Oauth2Token();
                wat.setAccessToken(jsonObject.getString("access_token"));
                wat.setExpiresIn(jsonObject.getInteger("expires_in"));
                wat.setRefreshToken(jsonObject.getString("refresh_token"));
                wat.setOpenId(jsonObject.getString("openid"));
                wat.setScope(jsonObject.getString("scope"));
            } catch (Exception e) {
                wat = null;
                int errorCode = jsonObject.getInteger("errcode");
                String errorMsg = jsonObject.getString("errmsg");
                log.error("获取网页授权凭证失败 errcode:{} errmsg:{}", errorCode, errorMsg);
            }
        }
        return wat;
    }
    /**
     * 通过网页授权获取用户信息
     *
     * @param accessToken 网页授权接口调用凭证
     * @param openId      用户标识
     * @return WxUserInfo
     */
    public static WxUserInfo getWxUserInfo(String accessToken, String openId) {
        WxUserInfo wxUserInfo = null;
        // 拼接请求地址
        String requestUrl = WxConstant.GZH_USER_INFO.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);
        // 通过网页授权获取用户信息
        JSONObject jsonObject = JSON.parseObject(HttpUtil.get(requestUrl));
        if (null != jsonObject) {
            try {
                wxUserInfo = new WxUserInfo();
                // 用户的标识
                wxUserInfo.setOpenId(jsonObject.getString("openid"));
                // 昵称
                wxUserInfo.setNickname(jsonObject.getString("nickname"));
                // 性别(1是男性,2是女性,0是未知)
                wxUserInfo.setSex(jsonObject.getInteger("sex"));
                // 用户所在国家
                wxUserInfo.setCountry(jsonObject.getString("country"));
                // 用户所在省份
                wxUserInfo.setProvince(jsonObject.getString("province"));
                // 用户所在城市
                wxUserInfo.setCity(jsonObject.getString("city"));
                // 用户头像
                wxUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
                // 用户特权信息
                List<String> list = JSON.parseArray(jsonObject.getString("privilege"), String.class);
                wxUserInfo.setPrivilegeList(list);
                // 与开放平台共用的唯一标识,只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
                wxUserInfo.setUnionid(jsonObject.getString("unionid"));
                // 此处需要进入数据库  需要将 UnionId、openId 存入数据库
            } catch (Exception e) {
                wxUserInfo = null;
                int errorCode = jsonObject.getInteger("errcode");
                String errorMsg = jsonObject.getString("errmsg");
                log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, errorMsg);
            }
        }
        return wxUserInfo;
    }
    /**
     * URL编码(utf-8)
     *
     * @param source
     * @return
     */
    public static String urlEncodeUTF8(String source) {
        String result = source;
        try {
            result = java.net.URLEncoder.encode(source, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }
}

微信网页授权成功并拿到用户信息以后跳转的 H5 网页是一个简单的显示跳转成功页面,让公司的前端大佬做一下就 OK

关注后会显示出来,点击绑定你的个人信息

点击后跳转成功,完成测试 ,注意:全部操作需要部署到线上操作,或者通过内网穿透的方式来部署,否则无法进行测试


目录
相关文章
|
7月前
|
小程序
微信小程序如何实现进入小程序自动连WiFi功能
微信小程序如何实现进入小程序自动连WiFi功能
214 0
|
2月前
|
存储 自然语言处理 小程序
微信小程序多语言切换神器:简繁体切换功能完全指南
随着全球化的发展,支持多种语言的应用程序愈发重要。本文介绍了如何在微信小程序中实现简体与繁体字体之间的切换功能,以满足不同地区用户的需求。通过创建utils文件夹并编写相应的转换函数,开发者可以方便地实现语言切换,从而提升用户体验。文章中还附带了示例代码和效果图,帮助读者更好地理解和应用这一功能。
109 0
微信小程序多语言切换神器:简繁体切换功能完全指南
|
3月前
|
算法 JavaScript 前端开发
切西瓜法实现微信抢红包功能
该文章介绍了使用“切西瓜法”和“栅栏法”两种算法来模拟微信抢红包的随机分配机制,并通过具体的JavaScript代码实现了红包金额的公平随机分配过程。
切西瓜法实现微信抢红包功能
|
3月前
|
人工智能 前端开发 JavaScript
MacTalk 测评通义灵码,实现“微信表情”小功能
墨问西东创始人池建强分享了团队使用通义灵码的经验。
|
3月前
|
小程序 API 开发工具
使用python 实现微信签到提醒功能
【9月更文挑战第4天】使用python 实现微信签到提醒功能
82 2
|
4月前
|
Web App开发 缓存 小程序
【Azure API 管理】从微信小程序访问APIM出现200空响应的问题中发现CORS的属性[terminate-unmatched-request]功能
【Azure API 管理】从微信小程序访问APIM出现200空响应的问题中发现CORS的属性[terminate-unmatched-request]功能
|
4月前
|
小程序 前端开发 开发者
|
4月前
|
小程序 前端开发 API
Ant Design Mini 问题之在微信小程序中,由于不支持slot特性,Ant Design Mini的什么组件功能受到了限制,如何解决
Ant Design Mini 问题之在微信小程序中,由于不支持slot特性,Ant Design Mini的什么组件功能受到了限制,如何解决
117 1
|
5月前
|
开发框架 移动开发 前端开发
在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
|
4月前
|
JavaScript 前端开发 网络协议
WebSocket在Java Spring Boot+Vue框架中实现消息推送功能
在现代Web应用中,实时消息提醒是一项非常重要的功能,能够极大地提升用户体验。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实现实时消息提醒提供了高效且低延迟的解决方案。本文将详细介绍如何在Java Spring Boot后端和Vue前端框架中利用WebSocket实现消息提醒功能。
211 0