• 钉钉开发系列(十一)钉钉网页扫码登录


    在《钉钉开发系列(八)二维码扫描登录的实现》介绍了一种扫码登录的方式,该方式是自己产生二维码,二维码中的URL指到自身的服务器页面,在该页面中以JSSDK的方式来获取钉钉用户的信息。钉钉官方提供了另外两种扫码登录的方式,可以参见钉钉官网

    先申请获取相应的appid和appsecret,然后架设一个服务端,比如有页面ddqrlogin.aspx,然后将该页面的URL使用URL编码,对应到https://oapi.dingtalk.com/connect/qrconnect?appid=APPID&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=REDIRECT_URI中的REDIRECT_URI,即用该URL编码后的值替代REDIRECT_URI。然后将该URL嵌入到web页面中。如果是winform的,可以直接用webbrowser,将其URL设置为前面拼成的一长串URL。同时将ScriptErrorsSuppressed设置为false,以屏蔽JS错误时的弹窗,设置ScrollBarsEnabled为false,以便于调整窗体的大小。


    同时设置DocumentCompleted事件,以便在扫描成功后,读取返回的数据,代码如下。

       private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
            {
                if (webBrowser1.Document.Url.AbsolutePath.Contains("ddqrlogin"))
                {
                    var dduseridPackageJson = $"{webBrowser1.Document.InvokeScript("GetDDUserId")}";
     MessageBox.Show(dduseridPackageJson );
    }
    }
    其中webBrowser1.Document.InvokeScript("GetDDUserId")调用的是ddqrlogin.aspx的JS函数GetDDUserId.

    在服务端ddqrlogin.aspx代码如下

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ddqrlogin.aspx.cs" Inherits="DingDingQRLogin.ddqrlogin" %>
    
    <!DOCTYPE html>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title></title>
        <script type="text/javascript">
            function GetDDUserId() {
                try {
                    var hiddenField = document.getElementById("<%=HiddenFieldDDUserId.ClientID%>");
                    var ddUserId = hiddenField.value;
                    return ddUserId;
                } catch (e) {
                    alert(e.message);
                }
            }
        </script>
    </head>
    <body>
        <form id="form1" runat="server">
            <asp:HiddenField ID="HiddenFieldDDUserId" runat="server" />
            <div style=" 611px; height: 600px; background-color: #2F4F4F;position:absolute;">
                <div style="margin-left: 123px; margin-top: 74px;  365px; height: 292px; background-color: #F9F9F9; text-align: center; position: absolute;">
                    <div id="loginResultInfo" style="position: absolute; top: 50%; left: 50%;"
                        runat="server">
                    </div>
                </div>
            </div>
        </form>
    </body>
    </html>
    
    服务端后台代码如下

     protected void Page_Load(object sender, EventArgs e)
            {
                if (!this.IsPostBack)
                {
                    var tempAuthCode = Request.QueryString["code"];
                    var state = Request.QueryString["state"];
                    var userIdPackage = SdkTool_QRLogin.FetchDDUserIdTempAuthCode(tempAuthCode);
                    HiddenFieldDDUserId.Value = userIdPackage.ToJSON();
                    loginResultInfo.InnerText = (userIdPackage.IsOK()) ? "登录成功" : userIdPackage.ErrMsg;
                }
            }
    其中FetchDDUserIdTempAuthCode是获取钉钉的用户id,具体代码如下。

    public static class SdkTool_QRLogin
        {
            #region 全局变量
            /// <summary>
            /// 基于appid获取的票据
            /// </summary>
            public static DDAppAccessToken AppAccessToken = DDAppAccessToken.GetInstance();
            #endregion
    
            #region UpdateQRAccessToken
            /// <summary>
            /// 更新应用票据
            /// </summary>
            /// <returns></returns>
            public static void UpdateAppAccessToken(bool forceUpdate = false)
            {
                if (!forceUpdate && !AppAccessToken.IsExpired())
                {//没有强制更新,并且没有超过缓存时间  
                    return;
                }
    
                string appId = ConfigTool.FetchAppId();
                string appSecret = ConfigTool.FetchAppSecret();
                string TokenUrl = QRUrls.SNS_GET_TOKEN;
                string apiurl = $"{TokenUrl}?{QRKeys.appid}={appId}&{QRKeys.appsecret}={appSecret}";
                DDTokenPackage tokenResult = DDRequestAnalyzer.Get<DDTokenPackage>(apiurl);
                if (tokenResult.IsOK())
                {
                    AppAccessToken.Value = tokenResult.Access_token;
                    AppAccessToken.Begin = DateTime.Now;
                }
            }
            #endregion
    
            #region FetchPersistentCode Function   
            /// <summary>
            /// 获取持久授权码
            /// </summary>
            /// <param name="tempAuthCode"></param>
            /// <returns></returns>
            public static DDPersistentCode FetchPersistentCode(string tempAuthCode)
            {
                string apiUrl = FormatApiUrlWithAppToken(QRUrls.SNS_GET_PERSISTENT_CODE);
                var data = new { tmp_auth_code = tempAuthCode };
                DDPersistentCode result = DDRequestAnalyzer.Post<DDPersistentCode>(apiUrl, data.ToJSON());
                return result;
            }
            #endregion
    
            #region FetchSnsToken Function   
            /// <summary>
            /// 获取SNS票据
            /// </summary>
            /// <param name="openId"></param>
            /// <param name="persistentCode"></param>
            /// <param name="forceUpdate"></param>
            public static DDSnsToken FetchSnsToken(string openId, string persistentCode, bool forceUpdate)
            {
                string apiUrl = FormatApiUrlWithAppToken(QRUrls.SNS_GET_SNS_TOKEN);
                var data = new
                {
                    openid = openId,
                    persistent_code = persistentCode
                };
                DDSnsToken SnsToken = new DDSnsToken();
                var result = DDRequestAnalyzer.Post<DDSnsToken>(apiUrl, data.ToJSON());
                if (result.IsOK())
                {
                    SnsToken.ExpiresIn = result.ExpiresIn;
                    SnsToken.Value = result.Value;
                    SnsToken.Begin = DateTime.Now;
                }
                return SnsToken;
            }
            #endregion
    
    
            #region FetchUserInfo Function
            /// <summary>
            /// 基于临时获权码获取用户信息
            /// </summary>
            /// <param name="tempAuthCode">临时授权码</param>
            /// <returns></returns>
            public static DDSnsUserInfo FetchUserInfo(string tempAuthCode)
            {
                var persistentCodePackage = FetchPersistentCode(tempAuthCode);
                DDSnsUserInfo snsUserInfoPackage = new DDSnsUserInfo();
                if (!persistentCodePackage.IsOK())
                {
                    snsUserInfoPackage.ErrCode = DDErrCodeEnum.Unknown;
                    snsUserInfoPackage.ErrMsg = $"使用tempAuthCode({tempAuthCode})获取";
                    return snsUserInfoPackage;
                }
    
                var snsToken = FetchSnsToken(persistentCodePackage.OpenId, persistentCodePackage.PersistentCode, false);
                string apiUrl = $"{QRUrls.SNS_GET_USER_INFO}?{QRKeys.sns_token}={snsToken.Value}";
                snsUserInfoPackage = DDRequestAnalyzer.Get<DDSnsUserInfo>(apiUrl);
                return snsUserInfoPackage;
            }
            #endregion
    
            #region FetchUserInfo Function
            /// <summary>
            /// 基于临时获取DDUserId
            /// </summary>
            /// <param name="tempAuthCode">临时授权码</param>
            /// <returns></returns>
            public static DDUserIdPackage FetchDDUserIdTempAuthCode(string tempAuthCode)
            {
                var snsUserInfoPackage = FetchUserInfo(tempAuthCode);
                DDUserIdPackage userIdPackage = new DDUserIdPackage();
                if (!snsUserInfoPackage.IsOK())
                {
                    userIdPackage.ErrCode = snsUserInfoPackage.ErrCode;
                    userIdPackage.ErrMsg = snsUserInfoPackage.ErrMsg;
                    return userIdPackage;
                }
                userIdPackage = FetchDDUserIdByUnionId(snsUserInfoPackage.user_info.unionid);
                return userIdPackage;
            }
            #endregion
    
            #region FetchDDUserIdByUnionId Function
            /// <summary>
            /// 基于UnionId获取DDUserId
            /// </summary>
            /// <param name="unionid">用户在当前钉钉开放平台账号范围内的唯一标识,同一个钉钉开放平台账号可以包含多个开放应用,同时也包含ISV的套件应用及企业应用</param>
            /// <returns></returns>
            public static DDUserIdPackage FetchDDUserIdByUnionId(string unionid)
            {
                DDUserIdPackage userIdPackage = new DDUserIdPackage();
                var accessTokenPackage = AuthService.GetAccessToken();
                if (!accessTokenPackage.IsOK())
                {
                    userIdPackage.ErrCode = DDErrCodeEnum.Unknown;
                    userIdPackage.ErrMsg = accessTokenPackage.Message;
                    return userIdPackage;
                }
                DDAccessToken accessTokenOfCorpId = accessTokenPackage.Data;
                if (accessTokenOfCorpId == null)
                {
                    userIdPackage.ErrCode = DDErrCodeEnum.Unknown;
                    userIdPackage.ErrMsg = "accessTokenOfCorpId is null";
                    return userIdPackage;
                }
                string apiUrl = $"{QRUrls.USER_GET_USERID_BY_UNIONID}?{QRKeys.access_token}={accessTokenOfCorpId.Value}";
                apiUrl += $"&{QRKeys.access_token}={AppAccessToken.Value}&{QRKeys.unionid}={unionid}";
                userIdPackage = DDRequestAnalyzer.Get<DDUserIdPackage>(apiUrl);
                return userIdPackage;
            }
            #endregion
    
            #region FormatApiUrlWithAppToken Function
            public static String FormatApiUrlWithAppToken(String url, bool forceUpdate = false)
            {
                UpdateAppAccessToken(forceUpdate);
                string apiurl = $"{url}?{QRKeys.access_token}={AppAccessToken.Value}";
                return apiurl;
            }
            #endregion
    
        }

    DDRequestAnalyzer请参照前面系列文章的代码。

    相关的其他类如下

    DDAppAccessToken

     public class DDAppAccessToken : DDAccessToken
        {
            #region 内部变量
    
            private static readonly object _lockObj = new object();
    
            private static DDAppAccessToken _instance = null;
    
            #endregion
            private DDAppAccessToken()
            {
    
            }
    
            #region GetInstance
            /// <summary>
            /// 获取实例(单例)
            /// </summary>
            /// <returns></returns>
            public static DDAppAccessToken GetInstance()
            {
                if (_instance != null)
                {
                    return _instance;
                }
    
                lock (_lockObj)
                {
                    if (_instance == null)
                    {
                        _instance = new DDAppAccessToken();
                    }
                }
    
                return _instance;
            }
            #endregion
    
            #region IsExpired
            /// <summary>
            /// 是否过期
            /// </summary>
            /// <returns></returns>
            public bool IsExpired()
            {
                if (Begin.AddSeconds(ConstVars.APP_ACCESS_TOKEN_CACHE_TIME) >= DateTime.Now)
                {
                    return false;
                }
                return true;
            }
            #endregion
        }
    其中DDAccessToken可以参看前面系列的代码。

    DDPersistenCode.cs

     /// <summary>
        /// 持久授权码
        /// </summary>
        public class DDPersistentCode : DDBaseResult
        {
            /// <summary>
            /// 用户在当前开放应用内的唯一标识
            /// </summary>
            [JsonProperty("openid")]
            public String OpenId { get; set; }
    
            /// <summary>
            /// 用户给开放应用授权的持久授权码,此码目前无过期时间
            /// </summary>
            [JsonProperty("persistent_code")]
            public string PersistentCode { get; set; }
    
            /// <summary>
            /// 用户在当前钉钉开放平台账号范围内的唯一标识,同一个钉钉开放平台账号可以包含多个开放应用,同时也包含ISV的套件应用及企业应用
            /// </summary>
            [JsonProperty("unionid")]
            public string UnionId { get; set; }
        }
    其中JsonProperty是JSON库Newtonsoft的。

    DDSnsToken.cs

    public class DDSnsToken : DDBaseResult
        {
            /// <summary>
            /// sns_token的过期时间
            /// </summary>
            [JsonProperty("expires_in")]
            public int ExpiresIn { get; set; }
    
            /// <summary>
            ///用户授权的token
            /// </summary>
            [JsonProperty("sns_token")]
            public string Value { get; set; }
    
            /// <summary>
            /// 票据的开始时间
            /// </summary>
            public DateTime Begin { get; set; }
    
            #region IsExpired
            /// <summary>
            /// 是否过期
            /// </summary>
            /// <returns></returns>
            public bool IsExpired()
            {
                if (Begin.AddSeconds(ExpiresIn) >= DateTime.Now)
                {
                    return false;
                }
                return true;
            }
            #endregion
        }
    DDSnsUserInfo.cs

     public class DDSnsUserInfo : DDBaseResult
        {
            /// <summary>
            /// 企业信息(默认不返回)
            /// </summary>
            public SnsCorpInfo[] corp_info { get; set; }
    
            /// <summary>
            /// 用户信息
            /// </summary>
            public SnsUserInfo user_info { get; set; }
        }
    
        #region SnsCorpInfo
        /// <summary>
        /// 企业信息(默认不返回)
        /// </summary>
        public class SnsCorpInfo
        {
            /// <summary>
            /// 企业名称(默认不返回)
            /// </summary>
            public string corp_name { get; set; }
    
            /// <summary>
            /// 企业是否经过钉钉认证(默认不返回)
            /// </summary>
            public bool is_auth { get; set; }
    
            /// <summary>
            /// 当前用户是否为该企业的管理人员(默认不返回)
            /// </summary>
            public bool is_manager { get; set; }
    
            /// <summary>
            /// 该企业的权益等级(默认不返回)
            /// </summary>
            public int rights_level { get; set; }
        }
        #endregion
    
        #region SnsUserInfo
        /// <summary>
        /// 用户信息
        /// </summary>
        public class SnsUserInfo
        {
            /// <summary>
            /// 经过处理的手机号(默认不返回)
            /// </summary>
            public string maskedMobile { get; set; }
    
            /// <summary>
            /// 用户在钉钉上面的昵称
            /// </summary>
            public string nick { get; set; }
    
            /// <summary>
            /// 用户在当前开放应用内的唯一标识
            /// </summary>
            public string openid { get; set; }
    
            /// <summary>
            /// 用户在当前开放应用所属的钉钉开放平台账号内的唯一标识
            /// </summary>
            public string unionid { get; set; }
    
            /// <summary>
            ///钉钉Id
            /// </summary>
            public string dingId { get; set; }
        }
        #endregion

    QRUrl.cs

     public sealed class QRUrls
        {
            public const string SNS_GET_TOKEN = "https://oapi.dingtalk.com/sns/gettoken";
    
            public const string SNS_GET_PERSISTENT_CODE = "https://oapi.dingtalk.com/sns/get_persistent_code";
    
            public const string SNS_GET_SNS_TOKEN = "https://oapi.dingtalk.com/sns/get_sns_token";
    
            public const string SNS_GET_USER_INFO = "https://oapi.dingtalk.com/sns/getuserinfo";
    
            /// <summary>
            /// 根据unionid获取成员的userid
            /// </summary>
            public const string USER_GET_USERID_BY_UNIONID = "https://oapi.dingtalk.com/user/getUseridByUnionid";
        }
    QRKeys.cs
       public class QRKeys
        {
            public const string appid = "appid";
    
            public const string appsecret = "appsecret";
    
            public const string tmp_auth_code = "tmp_auth_code";
    
            public const string sns_token = "sns_token";
    
            public const string unionid = "unionid";
    
            public const string access_token = "access_token";
        }
    ConstVars.cs

     public class ConstVars
        {
            /// <summary>
            /// 缓存时间
            /// </summary>
            public const int APP_ACCESS_TOKEN_CACHE_TIME = 5000;
        }

    在扫码成功后,会跳转到ddqrlogin.aspx,同时后面会带上code和state,比如

    http://XXX.com/ddqrlogin.aspx?code=9ccba352e7043c3face9da66ddba7a5f&state=STATE

    其中code就是临时授权码tmpAuthCode。

    附上ConfigTool.cs代码

    public class ConfigTool
        {
            #region FetchAppId Function 
            /// <summary>
            /// 获取AppId
            /// </summary>
            /// <returns></returns>
            public static String FetchAppId()
            {
                return FetchValue("appId");
            }
            #endregion
    
            #region FetchAppSecret Function 
            /// <summary>
            /// 获取appSecret
            /// </summary>
            /// <returns></returns>
            public static String FetchAppSecret()
            {
                return FetchValue("appSecret");
            }
            #endregion
    
            #region FetchValue Function              
            public static String FetchValue(String key)
            {
                String value = ConfigurationManager.AppSettings[key];
                if (value == null)
                {
                    throw new Exception($"{key} is null.请确认配置文件中是否已配置.");
                }
                return value;
            }
            #endregion
        }
    在Web.config上配置appid和appsecrect

    <appSettings>
        <add key="appId" value="XX" />
        <add key="appSecret" value="XXXXXXXXXXXXXXXXXXXXXX" />
      </appSettings>

    经过这样的处理后,扫码成功时将能够获取dduserid的数据。下面是结果图。



    欢迎打描左侧二维码打赏。

    转载请注明出处。

  • 相关阅读:
    关于HTML面试题汇总之H5
    HTML5的页面资源预加载技术(Link prefetch)加速页面加载
    linux下搭建SVN服务器完全手册
    HTML5标签学习
    22个HTML5的初级技巧
    h5 audio播放音频文件
    html5适应屏幕的方案
    富文本编辑器的使用
    Array.prototype.filter()
    安装谷歌助手教程
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7604918.html
Copyright © 2020-2023  润新知