企业微信扫码登录业务实现

概要

现在很多企业都会和企业微信做集成绑定,通过企业微信扫码登录到内部系统,这样提高系统安全性的同时,也带来了方便,下面说说如何实现企业微信扫码登录业务。

前提
  1. 了解企业微信协议
  2. 熟悉nginx代理配置
  3. 熟悉vue2
  4. 熟悉java

整体架构流程

  1. 前端生成二维码,携带state,定时刷新获取登录状态
  2. 后端实现企业微信回调函数,绑定用户和state
  3. 获取到state与用户绑定关系,登录跳转

技术细节

  1. 前端生成二维码,携带state,定时刷新获取登录状态
    这里使用vue2实现的,大家也可以用其他前端语言,上核心代码
    页面显示
<template>
  <div style="">
    <div style="text-align: center; margin-bottom: 30px;">
      <span style="font-size: 26px;">二维码登录</span>
      <p style="font-size: 16px; color:#ff0000;">请使用本司的企业微信扫码</p>
    </div>
    <div v-show="time" style="padding-left: 35px;">
      <div ref="qrCodeUrl" />
    </div>
    <div v-show="!time" style="text-align: center;line-height: 120px;cursor: pointer;" @click="resetQr">
      二维码失效
    </div>
  </div>
</template>

关键函数:
生成二维码

getUrlWx() {
        this.state = uuidv4() //import { v4 as uuidv4 } from 'uuid'        
        this.qruel = 'http://xxx/qrcode?csrfToken=' + this.state + '&portalhost=' + window.location.host//portalhost是内外网判断使用
        this.createQrcode()
      },
createQrcode() {
        this.$refs.qrCodeUrl.innerHTML = ''
		//import QRCode from 'qrcodejs2'
        new QRCode(this.$refs.qrCodeUrl, {
          text: this.qruel,
          width: 240, // 二维码的宽度
          height: 240, // 二维码的高度
          colorLight: '#ffffff', // 二维码背景色
          colorDark: 'black',
          correctLevel: QRCode.CorrectLevel.L // 纠错等级
        })
      },            
  1. 后端实现企业微信回调函数,绑定用户和state
    登录企业微信后台,此时你必须有管理员权限,找到工具,通用配置,应用开发
    在这里进入登录授权
    点开通用开发参数可以看到
    cropid和corpsecret,记住

我用的是旧的登录授权填上域名和域名加端口
例如 xxx.com xxx.com:5005,如果你没有固定的域名和ip,企业微信已升级,就会不支持

@GetMapping("/qrcode")
    @ApiOperation(value = "获取企业微信登录二维码连接")
    public String qrcode(HttpServletRequest request)
    {
        String csrfToken = request.getParameter("csrfToken");
        String portalhost = request.getParameter("portalhost");//门户访问地址的主机,用于区分内外网络
        String url = wxLoginService.WxLogin(csrfToken, portalhost);
        return "<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "<title>企业名称</title>\n" +
                "<script>window.location.href='" + url + "'</script>" +
                "</html>";
    }
public String WxLogin(String csrfToken, String portalhost) {
		//此处通过http调用微信接口http://xxxcom:5005/cgi-bin/gettoken?corpid=xxx&corpsecret=xxx
        // 同时把获取的access_token存储到redis,方便获取        
        //得到网页授权链接
        String url = WxUtil.getCodeUrl(WxParameterConstant.redirectUri + "?portalhost=" + portalhost,false, csrfToken);
        return url;
    }   
 /**
     * @description: 得到微信code,构造网页授权链接
     * @author: 
     * @date: 2023/3/27 17:48
     * @param: [redirectUri, isBecomeSilent, csrfToken]
     * @return: java.lang.String
     **/
    //https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE&agentid=AGENTID#wechat_redirect
    public static String getCodeUrl(String redirectUri,boolean IsSnsapiBase,String csrfToken){

        StringBuilder url = new StringBuilder("https://open.weixin.qq.com/connect/oauth2/authorize?appid=");

        //企业的CorpID
        url.append(WxParameterConstant.corpid);

        //授权后重定向的回调链接地址,请使用urlencode对链接进行处理
        //就是微信显示的页面
        url.append("&redirect_uri=").append(redirectUri);

        //返回类型,此时固定为:code
        url.append("&response_type=code");

        //应用授权作用域  snsapi_base:静默授权,可获取成员的基础信息(UserId与DeviceId); snsapi_privateinfo:手动授权,可获取成员的详细信息,包含头像、二维码等敏感信息。
        if(IsSnsapiBase){
            url.append("&scope=").append("snsapi_base");
        }else {
            url.append("&scope=").append("snsapi_privateinfo");
        }

        if (StringUtils.isNotEmpty(csrfToken)){
            url.append("&state=").append(csrfToken);
        }

        // 应用agentid,建议填上该参数(如果为第三方应用或者代开发自建应用,未填该参数不会触发接口许可自动激活)。snsapi_privateinfo时必填否则报错;
        url.append("&agentid=").append(WxParameterConstant.agentId);

        //终端使用此参数判断是否需要带上身份信息
        url.append("#wechat_redirect");

        return url.toString();

    }

企业微信接口文档阅读 https://developer.work.weixin.qq.com/document/path/91039

企业微信回调函数

@GetMapping ("/wxCallBack")
    @ApiOperation(value = "企业微信回调链接")
    public String wxCallBack(HttpServletRequest request) {
        String code = request.getParameter("code");

        String state = request.getParameter("state");
		
		//redis中获取state
        Object tokenJsonFirst = strOps.get(state);
        
       JSONObject userBasics = new JSONObject();

        /**
         * state在Redis有对应的token信息,说明该state已经被使用过了
         */
        if (tokenJsonFirst != null){
            userBasics.put("errmsg", "Other users have scanned the code");
            return WxResponseUtil.bad(userBasics);
        }

        JSONObject userResult = new JSONObject();

        if (StringUtil.isNotEmpty((String) strOps.get(RedisConstant.WX_APP_ACCESS_TOKEN))){
            //获取访问用户身份 
            //https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE 获取访问用户身份
            String url = WxUtil.getUserIdentityUrl((String) strOps.get(RedisConstant.WX_APP_ACCESS_TOKEN),code);
            userBasics= JSONObject.parseObject(HttpClient4Util.getResponse4GetAsString(url,"utf-8"));
        }else {
            userBasics.put("errmsg", "wx app accesstoken is empty");
            return WxResponseUtil.bad(userBasics);
        }

        if ("ok".equals(userBasics.getString("errmsg"))){
            //获得成员信息
            String urlMemBerUrl = WxUtil.getUserMemberUrl((String) strOps.get(RedisConstant.WX_APP_ACCESS_TOKEN),userBasics.getString("userid"));
            userResult = JSONObject.parseObject(HttpClient4Util.getResponse4GetAsString(urlMemBerUrl,"utf-8"));
        }else {
            log.error("getuserinfo is not ok: " + userBasics.toJSONString());
            return WxResponseUtil.bad(userBasics);
        }
                try
                {
                    // 系统登录认证,此处代码省略,各位各自自己业务来处理
                    //主体思路是state和用户绑定,方便下一步页面请求state时知道是哪个用户
                    return WxResponseUtil.success();
                }                
                catch (Exception e)
                {
                    log.error("{}", e);
                    return WxResponseUtil.bad(null);
                }

            } 

        return WxResponseUtil.bad(null);

    }
  1. 获取到state与用户绑定关系,登录跳转
    定时获取登录状态
getWxToken() {
        this.count = 100
        this.timerState = setInterval(() => {
          this.loginWx()
          this.count-- // 递减
          if (this.count <= 0) {
            this.state = null
            clearInterval(this.timerState)
          }
        }, 1500)
      },
     // 企业微信登录,Wxlogin根据自己业务来处理如何登录
      async  loginWx() {
        this.Wxlogin(this.state).then((res) => {
          if (res.code === 200) {
            clearInterval(this.timerRefresh)
            clearInterval(this.timerState)
            this.$router.push({
              path: '/'
            })
          }
        }).catch((err) => {
          console.log(err)
        });
      },            

小结

提示:1. 熟悉企业微信通讯协议 2. 熟悉nginx反向代理 3.了解回调函数

参考链接
企业微信接口文档地址

### 企业微信登录功能的集成方法 #### 1. 准备工作 在开始之前,确保您已经完成以下准备工作: - 注册并创建企业微信应用,获取到 `CorpID` 和 `Secret`。 - 下载并配置企业微信的 SDK。企业微信官方提供了适用于不同平台的 SDK,对于 Qt 应用程序来说,通常需要使用其 Web API 或者封装好的 HTTP 请求库来处理。 #### 2. 获取企业微信授权链接 企业微信登录功能是通过 OAuth2.0 协议实现的。首先,您需要生成一个授权链接,引导用户进行登录。 ```cpp #include <QUrl> #include <QString> QString generateAuthorizationUrl(const QString& corpId, const QString& redirectUri, const QString& state) { QUrl url("https://open.weixin.qq.com/connect/oauth2/authorize"); url.addQueryItem("appid", corpId); url.addQueryItem("redirect_uri", redirectUri); url.addQueryItem("response_type", "code"); url.addQueryItem("scope", "snsapi_base"); // 或者 snsapi_userinfo 根据需求选择 url.addQueryItem("state", state); url.addQueryItem("#wechat_redirect", ""); return url.toString(); } ``` - **corpId**: 您的企业微信应用的唯一标识。 - **redirectUri**: 用户授权后将被重定向到的 URL。 - **state**: 自定义的状态参数,用于防止 CSRF 攻击,建议每次请求时生成随机值。 调用此函数后,将会生成一个完整的授权链接,您可以将其嵌入到 WebView 或浏览器中,让用户进行登录。 #### 3. 处理回调 当用户成功并授权后,企业微信会将用户重定向到您指定的 `redirect_uri`,并在 URL 中附带一个 `code` 参数和 `state` 参数。您需要解析这个 `code` 并向企业微信服务器发起请求以获取用户的访问令牌和用户信息。 ```cpp #include <QNetworkAccessManager> #include <QNetworkRequest> #include <QNetworkReply> #include <QJsonDocument> #include <QJsonObject> #include <QDebug> void handleCallback(const QString& code, const QString& corpId, const QString& secret) { QNetworkAccessManager manager; QUrl url("https://qyapi.weixin.qq.com/cgi-bin/gettoken"); // 获取 Access Token url.addQueryItem("corpid", corpId); url.addQueryItem("corpsecret", secret); QNetworkRequest request(url); QNetworkReply* reply = manager.get(request); QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); if (reply->error() == QNetworkReply::NoError) { QByteArray response = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(response); QJsonObject obj = doc.object(); if (obj.contains("access_token")) { QString accessToken = obj["access_token"].toString(); // 使用 Access Token 获取用户信息 QUrl userInfoUrl("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"); userInfoUrl.addQueryItem("access_token", accessToken); userInfoUrl.addQueryItem("code", code); QNetworkRequest userInfoRequest(userInfoUrl); QNetworkReply* userInfoReply = manager.get(userInfoRequest); QEventLoop userInfoLoop; connect(userInfoReply, &QNetworkReply::finished, &userInfoLoop, &QEventLoop::quit); userInfoLoop.exec(); if (userInfoReply->error() == QNetworkReply::NoError) { QByteArray userInfoResponse = userInfoReply->readAll(); QJsonDocument userInfoDoc = QJsonDocument::fromJson(userInfoResponse); QJsonObject userInfoObj = userInfoDoc.object(); if (userInfoObj.contains("UserId")) { QString userId = userInfoObj["UserId"].toString(); qDebug() << "User ID:" << userId; } else { qDebug() << "Failed to get user info:" << userInfoResponse; } } else { qDebug() << "Failed to get user info reply:" << userInfoReply->errorString(); } } else { qDebug() << "Failed to get access token:" << response; } } else { qDebug() << "Failed to get access token reply:" << reply->errorString(); } reply->deleteLater(); } ``` - **code**: 用户授权后返回的临时授权。 - **corpId**: 您的企业微信应用的唯一标识。 - **secret**: 您的应用的凭证密钥。 这段代展示了如何通过 `code` 获取用户的访问令牌,并进一步获取用户信息。如果一切顺利,您将获得用户的 `UserId`,这可以用于后续的身份验证和业务逻辑处理。 #### 4. 安全性考虑 为了确保安全性,建议采取以下措施: - **State 参数**: 在生成授权链接时,使用随机生成的 `state` 参数,并在回调时验证该参数,以防止 CSRF 攻击。 - **Token 管理**: 获取到的 `access_token` 是敏感信息,应妥善保管,避免泄露。 - **HTTPS**: 所有的网络请求都应使用 HTTPS 协议,以保证数据传输的安全性。 #### 5. 集成到 Qt 应用 为了在 Qt 应用中展示页面,您可以使用 `QWebEngineView` 来加载授权链接,并监听 URL 变化以捕获回调地址中的 `code` 和 `state` 参数。 ```cpp #include <QWebEngineView> #include <QWebEnginePage> #include <QWebEngineProfile> #include <QWebEngineSettings> #include <QUrl> #include <QMessageBox> class WeChatLoginWebView : public QWebEngineView { Q_OBJECT public: explicit WeChatLoginWebView(QWidget* parent = nullptr) : QWebEngineView(parent) { setWindowTitle("企业微信登录"); resize(400, 600); connect(page(), &QWebEnginePage::urlChanged, this, &WeChatLoginWebView::onUrlChanged); } void loadAuthorizationUrl(const QString& authorizationUrl) { load(QUrl(authorizationUrl)); show(); } private slots: void onUrlChanged(const QUrl& url) { if (url.toString().contains("code=")) { QString code = extractCodeFromUrl(url); emit loginSuccess(code); close(); } } signals: void loginSuccess(const QString& code); private: QString extractCodeFromUrl(const QUrl& url) { QStringList queryItems = url.query().split("&"); for (const QString& item : queryItems) { if (item.startsWith("code=")) { return item.mid(5); } } return ""; } }; ``` - **loadAuthorizationUrl**: 加载生成的授权链接。 - **onUrlChanged**: 监听 URL 变化,当检测到包含 `code` 参数时,提取 `code` 并关闭窗口。 - **extractCodeFromUrl**: 从 URL 中提取 `code` 参数。 通过这种方式,您可以轻松地在 Qt 应用中集成企业微信登录功能。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞瑞绮绮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值