在《钉钉开发系列(八)二维码扫描登陆的实现》介绍了一种扫码登陆的方式,该方式是本身产生二维码,二维码中的URL指到自身的服务器页面,在该页面中以JSSDK的方式来获取钉钉用户的信息。钉钉官方提供了另外两种扫码登陆的方式,能够参见钉钉官网。javascript
先申请获取相应的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,以便于调整窗体的大小。css
同时设置DocumentCompleted事件,以便在扫描成功后,读取返回的数据,代码以下。html
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代码以下java
<%@ 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="width: 611px; height: 600px; background-color: #2F4F4F;position:absolute;"> <div style="margin-left: 123px; margin-top: 74px; width: 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 }
相关的其余类以下web
DDAppAccessTokenapi
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
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; }
http://XXX.com/ddqrlogin.aspx?code=9ccba352e7043c3face9da66ddba7a5f&state=STATE
附上ConfigTool.cs代码
markdown
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>
欢迎打描左侧二维码打赏。
app
转载请注明出处。