如何实现微信登录:网页版 & 公众号后端开发指南

957 阅读8分钟

网站登录

一. 开发准备

1. 注册企业账号

微信开放平台 (qq.com)

在微信开放平台注册账号。

2. 企业开发平台认证

完成企业信息认证,获得开发权限。

3. 创建网站应用

详情见:网站应用创建流程 - 腾讯客服 (qq.com)

注意:需要等待审核1-7天,审核成功腾讯会给你提供AppID和AppSecret

填写文件

open.weixin.qq.com/zh_CN/htmle…

4. 配置回调地址

无需带上http或者https

注意:有个测试账号是公众号使用的不是应用网站。

mp.weixin.qq.com/debug/cgi-b…

这个是微信公众平台,不是开放平台。

微信公众平台开发是指为微信公众号进行业务开发,为移动应用、PC端网站、公众号第三方平台(为各行各业公众号运营者提供服务)的开发,请前往微信开放平台接入。

5. 注意事项:

企业账号与开发者资质认证主体保持一致。

developers.weixin.qq.com/doc/oplatfo…

所提供的官网不得是无法访问应用详细信息的登录界面,如网站需登录后才可访问,建议提供相关账号密码以供审核人员登录并查看相关信息。

提交的官网主体信息或者应用市场下载链接开发者信息,需与微信开放平台认证主体信息一致,如不一致,可在提审基本信息页面的流程图处附上授权书,授权书可自行拟定,需说明清楚授权关系、授权内容,并加盖双方公司公章,个人主体则签名即可。

你所提交的官网开发者信息,需要与微信开放平台开发者资质认证主体信息一致,如不一致,提交时需提供相关授权书(加盖双方公章) 以供审核。

二. 实现逻辑

具体逻辑:微信登录功能 / 网站应用微信登录开发指南 (qq.com)

用户登录原理图:

微信调用逻辑图:

① 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;

② 通过code参数加上AppID和AppSecret等,通过API换取access_token;

code的超时时间为10分钟,一个code只能成功换取一次access_token 即失效。code的临时性和一次性保障了微信授权登录的安全性。

③ 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

这里以谷粒学院账号演示,可以成功。

  1. 配置相应的数据
# 微信开放平台 appid
wx.open.app_id=wxed9954c01bb89b47
# 微信开放平台 appsecret
wx.open.app_secret=a7482517235173ddb4083788de60b90e
# 微信开放平台 重定向url
wx.open.redirect_url=http://localhost:8160/api/wx/callback

这是谷粒学院的示例。跳转到自己的接口。

    @GetMapping("login")
    @ApiOperation("wxlogin")
    public String getWxCode() {
        //1.url中的%s就相当于问号(?),代表占位符
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";
        //2.对redirect_url进行URLEncode编码 
        String redirect_url = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try {
            redirect_url = URLEncoder.encode(redirect_url, "utf-8");
        } catch(Exception e) {

        }
        //3.给baseUrl中的占位符(%s)赋值
        String url = String.format(
                baseUrl,
                ConstantWxUtils.WX_OPEN_APP_ID,
                redirect_url,
                "0" //state目前没啥用
        ); //方法的返回值就是完整的带有参数的url地址
        //4.重定向到请求微信地址里面
        log.info(url);
        return "redirect:" + url;
    }

wx.open.redirect_url也是对应的回调接口地址。

回调接口,跳转到对应的网站。

    @GetMapping("callback")
    @ApiOperation("callback")
    public String callback(String code, String state) throws Exception {
        try {
            //1.拿着code(这是一个临时票据,类似于验证码)请求微信给的固定地址
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";
            //1.1给地址拼接三个参数:id、秘钥、code值
            String accessTokenUrl = String.format(
                    baseAccessTokenUrl,
                    ConstantWxUtils.WX_OPEN_APP_ID,
                    ConstantWxUtils.WX_OPEN_APP_SECRET,
                    code);
            //1.2使用httpclient请求拼接好的地址,得到openid和access_token
            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
            //输出accessTokenInfo看一下这个字符串长什么样,以便分析后面的业务逻辑
            log.info(accessTokenInfo);
            //从json字符串中获取openid和access_token
            //使用json工具把accessTokenInfo字符串转换为map集合,根据map中的key获取对应的值
            Gson gson = new Gson();
            HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
            String access_token = (String)mapAccessToken.get("access_token");
            String openid = (String)mapAccessToken.get("openid");



            //4.微信扫码登录时注册的过程不再由用户完成,而是由我们后端直接实现
            //所以我们此时需要将用户的openid、昵称、头像加到数据库中

            //根据openid判断数据库中是否已有该用户
            UcenterMember member = memberService.getOpenIdMember(openid);
            if (member == null) { //数据表中没有这个openid,进行添加
                //2.拿着access_token和openid请求微信给的固定地址
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "?access_token=%s" +
                        "&openid=%s";
                //2.1给地址拼接两个参数:access_token、openid
                String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
                //2.2使用httpclient请求拼接好的地址
                String userInfo = HttpClientUtils.get(userInfoUrl);
                //输出userInfo看一下这个字符串长什么样,以便分析后面的业务逻辑

                log.info(userInfo);
                //3.获取扫码人信息
                //3.1将json字符串转换为map集合
                HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
                String nickname = (String) userInfoMap.get("nickname"); //用户昵称
                String headimgurl = (String) userInfoMap.get("headimgurl"); //用户头像
                member = new UcenterMember();
                member.setOpenid(openid);
                member.setNickname(nickname);
                member.setAvatar(headimgurl);
                member.setGmtCreate(new Date());
                member.setGmtModified(new Date());
                memberService.save(member);
            }

            //使用jwt根据member对象(对象中有用户信息)生成token字符串
            String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());

            //返回到首页面(通过路径传递token字符串)
            return "redirect:对应的网址" + jwtToken;
            

        } catch (Exception e) {
            throw new Exception(e);
        }

    }

工具类

@Component
public class ConstantWxUtils implements InitializingBean {

    @Value("${wx.open.app_id}") //value注解用spring包里面的,别导错包
    private String appId;
    @Value("${wx.open.app_secret}")
    private String appSecret;
    @Value("${wx.open.redirect_url}")
    private String redirectUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_OPEN_APP_ID = appId;
        WX_OPEN_APP_SECRET = appSecret;
        WX_OPEN_REDIRECT_URL = redirectUrl;
    }
}

sql表

CREATE TABLE `ucenter_member` (
  `id` char(19) NOT NULL COMMENT '会员id',
  `openid` varchar(128) DEFAULT NULL COMMENT '微信openid',
  `mobile` varchar(11) DEFAULT '' COMMENT '手机号',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
  `sex` tinyint unsigned DEFAULT NULL COMMENT '性别 1 女,2 男',
  `age` tinyint unsigned DEFAULT NULL COMMENT '年龄',
  `avatar` varchar(255) DEFAULT NULL COMMENT '用户头像',
  `sign` varchar(100) DEFAULT NULL COMMENT '用户签名',
  `is_disabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否禁用 1(true)已禁用,  0(false)未禁用',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员表';

如果网站既有pc又有app可以通过UnionID实现互通。

利用UnionID机制就能实现用户信息互通,因为只要是同一个微信开放平台账号下的移动应用、网站应用、公众号、小程序,用户的UnionID是唯一的(即同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的),openID是应用内唯一。

微信公众号微信登录

一. 开发准备

1. 申请测试账号

mp.weixin.qq.com/debug/cgi-b…

微信扫码登录,获得测试账号可以测试

2. 内网穿透

内网穿透,natapp,ip地址映射-方便开发微信公众号,小程序等_natapp域名后面的真实ip-CSDN博客

二. 实现逻辑

1. 测试账号配置对应值

接口配置信息,通过调用后端接口,看是否会返回要求的值。可以通过这个调试。

微信调试工具 (qq.com)

// 获取AccessToken

api.weixin.qq.com/cgi-bin/tok…

如果仅是测试的话,可以直接返回不做校验。

根据内网穿透的外网地址配置。

    @GetMapping("checkWxCode")
    @ApiOperation("checkWxCode")
    @ResponseBody
    public String checkWxCode(HttpServletRequest request, HttpServletResponse response) {
        // 接收微信服务器发送请求时传递过来的参数
        String echostr = request.getParameter("echostr");//随机字符串
        log.info(echostr);
        // 直接返回echostr,即可
        return echostr;
    }

开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

参数描述
signature微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp时间戳
nonce随机数
echostr随机字符串

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

1)将token、timestamp、nonce三个参数进行字典序排序

2)将三个参数字符串拼接成一个字符串进行sha1加密

3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信。

这是正常的校验方法。

 @GetMapping("/checkWxCode")
    @ResponseBody
    public String checkWxCode(HttpServletRequest request, HttpServletResponse response) {
        // 接收微信服务器发送请求时传递过来的参数
        String signature = request.getParameter("signature"); // 微信加密签名
        String timestamp = request.getParameter("timestamp"); // 时间戳
        String nonce = request.getParameter("nonce"); // 随机数
        String echostr = request.getParameter("echostr"); // 随机字符串

        log.info("Received echostr: " + echostr);

        // 校验签名
        if (validateSignature(signature, timestamp, nonce)) {
            return echostr;
        } else {
            return "Invalid signature";
        }
    }

    private boolean validateSignature(String signature, String timestamp, String nonce) {
        String[] arr = new String[]{TOKEN, timestamp, nonce};
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (String s : arr) {
            content.append(s);
        }
        String localSignature = byteToHex(calculateSha1(content.toString()));
        return localSignature.equals(signature);
    }

    private String calculateSha1(String content) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.getBytes());
            return byteToHex(digest);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-1 algorithm not found", e);
        }
    }

    private static String byteToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

2. 扫描二维码配置测试账号

3. 设置回调页面域名

在yml里面配置

wx.open.redirect_url=http://wvk5uj.natappfree.cc/api/路径/callback

在登录方法修改一下对应url

 String baseUrl = "https://open.weixin.qq.com/connect/oauth2/authorize" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_base" +
                "&state=%s" +
                "#wechat_redirect";

只有微信页面可以跳转到对应的网址上。

其他代码和网页登录类似。

参考文章:一文搞定web微信第三方登录-腾讯云开发者社区-腾讯云 (tencent.com)