Skip to content
Charlie edited this page Jan 5, 2021 · 1 revision

v2.0 版本

注意,此版本与 v1.0.4 在配置上存在很大差异,如果您之前使用的是 1.0.4 版本,建议先查看下 2.0 的变化

前提

  1. 开发者自行在钉钉开发者平台中注册 企业内部应用-小程序 (可选)
  2. 开发者自行在钉钉开发者平台中注册 移动接入应用-登陆

基本用法

  1. 引入 nuget package: Install-Package Charlie.AspNetCore.Authentication.DingTalk
  2. 增加如下代码
    services.AddAuthentication()
        .AddDingTalk(opts =>
        {
            opts.ClientId = 钉钉扫码登录 的 AppKey
            opts.ClientSecret = 钉钉扫码登录的 AppSecret;
        
            // 以下为非必填项
            opts.IncludeUserInfo = 是否包含该用户在企业内的用户信息 (默认为 false);        
            opts.AppKey = 企业内部开发小程序的 App Key;
            opts.AppSecret = 企业内部开发小程序的 App Secret;
         }

具体使用方法

下面以2种扫码方式进行分别说明

使用钉钉自带的扫码页面

  1. 创建一个 Asp.Net Core Web 应用程序(MVC)
  2. 修改身份验证,选择 个人用户账户
  3. Startup.csConfigureServices 中,增加如下代码:
    services.AddAuthentication()
        .AddDingTalk(opts =>
        {
            opts.ClientId = 钉钉扫码登录 的 AppKey
            opts.ClientSecret = 钉钉扫码登录的 AppSecret;
        
            // 以下为非必填项
            opts.IncludeUserInfo = 是否包含该用户在企业内的用户信息 (默认为 false);        
            opts.AppKey = 企业内部开发小程序的 App Key;
            opts.AppSecret = 企业内部开发小程序的 App Secret;
         }

使用自定义的扫码页面

相比钉钉自带的扫码页面,使用自定义页面需要额外几个步骤

  1. 【同上】创建一个 Asp.Net Core Web 应用程序(MVC)
  2. 【同上】修改身份验证,选择 个人用户账户
  3. 右键单击项目,选择 添加-新搭建基架的项目...,然后选择 标识,在弹出框中,选择 Account\Login。这个步骤会在项目中创建 Asp.Net Core Identity 的 Login Razor Page
  4. Areas\Identity\Pages\Account 目录中,添加一个 DingTalkLogin Razor Page。这个 Page 就是用来渲染自己的 QR 页面,在 cshtml 中加入:
     <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
     <script>
         var dingtalk = "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=@(Model.AppId)&response_type=@(Model.ResponseType)&scope=@(Model.Scope)&state=@(Model.State)&redirect_uri=@(Model.RedirectUri)";
    
             DDLogin({
                 id: "code-img",
                 goto: encodeURIComponent(dingtalk),
                 style: "border:none;background-color:#FFFFFF;margin-top:-40px;",
                 width: "400",
                 height: "300"
             });
             var handleMessage = function (event) {
                 if (event.origin == "https://login.dingtalk.com") {
                     window.top.location.href = dingtalk + "&loginTmpCode=" + event.data;
                 }
             };
             if (typeof window.addEventListener != 'undefined') {
                 window.addEventListener('message', handleMessage, false);
             } else if (typeof window.attachEvent != 'undefined') {
                 window.attachEvent('onmessage', handleMessage);
             }
    
     </script>
  5. 修改 Areas\Identity\Pages\Account 目录中的 Login.cshtml, 增加一个 Iframe 用于显示扫码页面:
        <div style="height:300px">
             <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" target="ifmDingTalk" style="display:none">
                 <div>
                     <p>
                         <button id="dingTalkLoginBtn" type="submit" class="btn btn-primary" name="provider" value="DingTalk"></button>
                     </p>
                 </div>
             </form>
             <iframe name="ifmDingTalk" scrolling="no" style="width:450px;height:300px">
             </iframe>
         </div>
        
         @section Scripts {
             <script>
                 $("#dingTalkLoginBtn").click();
             </script>
         }
  6. 修改 Startup.csConfigureServices 中增加:
     services.AddAuthentication().AddDingTalk(opts =>
     {
         opts.ClientId = 钉钉扫码登录 的 AppKey
         opts.ClientSecret = 钉钉扫码登录的 AppSecret;
         
         // 以下为非必填项
         opts.IncludeUserInfo = 是否包含该用户在企业内的用户信息 (默认为 false);        
         opts.AppKey = 企业内部开发小程序的 App Key;
         opts.AppSecret = 企业内部开发小程序的 App Secret;
    
         opts.SignInScheme = IdentityConstants.ExternalScheme;
    
         // 由于是使用自己的 扫码 页面,则必须定义自己的 授权节点
         opts.AuthorizationEndpoint = "/Identity/Account/DingTalkLogin";
    
         // 演示如何把外部登陆的错误信息显示在 Razor Page 上
         opts.Events.OnRemoteFailure = async ctx =>
         {
             var tempDataProvider = ctx.HttpContext.RequestServices.GetRequiredService<ITempDataProvider>();
    
             tempDataProvider.SaveTempData(ctx.HttpContext, new Dictionary<string, object>
             {
                    { "ErrorMessage",ctx.Failure.Message }
             });
             ctx.Response.Redirect("/Identity/Account/Login");
             ctx.HandleResponse();
    
             await Task.CompletedTask;
         };
     });
    

获得钉钉返回的用户信息 (IncludeUserInfo = true)

  1. 默认情况下,Asp.Net Core Identity 在外部登录成功后,只会把 namenameidentifier 两个 claim 的信息作为用户的 claim 保存下来,如果想获取更多的外部登录用户的信息,需要进行如下操作:
  2. 右键单击项目,选择 添加-新搭建基架的项目...,然后选择 标识,在弹出框中,选择 Account\ExternalLogin。这个步骤会在项目中创建 Asp.Net Core Identity 的 ExternalLogin Razor Page
  3. ExternalLogin.cshtml.cs 中的 OnPostConfirmationAsync 方法里,当 AddLoginAsync 成功后,可以通过 info.Prinicpal.Claims 获取钉钉返回的所有 Claim。开发者可以自行决定如何使用这些 Claim,如:
    public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
    {
        // 省略部分无关代码
        result = await _userManager.AddLoginAsync(user, info);
        if (result.Succeeded)
        {
            _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);

            // 增加来自于 DingTalk 的额外的 claim
            await _userManager.AddClaimsAsync(user, info.Principal.Claims);
            
            //省略部分无关代码
          }
      }

钉钉返回的 Claim

    /// <summary>
    /// 钉钉特有的 Claim
    /// </summary>
    public static class DingTalkClaimTypes
    {
        /// <summary>
        /// 员工在当前开发者企业账号范围内的唯一标识
        /// </summary>
        public const string UnionId = "urn:dingtalk:unionid";

        /// <summary>
        /// 员工工号
        /// </summary>
        public const string JobNumber = "urn:dingtalk:jobno";

        /// <summary>
        /// 职位信息
        /// </summary>
        public const string Position = "urn:dingtalk:position";

        /// <summary>
        /// 是否是高管
        /// </summary>
        public const string IsSenior = "urn:dingtalk:is_senior";

        /// <summary>
        /// 员工的企业邮箱
        /// </summary>
        public const string OrgEmail= "urn:dingtalk:ogr_email";

        /// <summary>
        /// 是否实名认证
        /// </summary>
        public const string RealAuthed = "urn:dingtalk:real_authed";

        /// <summary>
        /// 是否是老板
        /// </summary>
        public const string IsBoss = "urn:dingtalk:is_boss";

        /// <summary>
        /// 是否为企业的管理员
        /// </summary>
        public const string IsAdmin = "urn:dingtalk:is_admin";

        /// <summary>
        /// 在对应的部门中是否为主管:Map结构的json字符串,key是部门的id,value是人员在这个部门中是否为主管,true表示是,false表示不是
        /// </summary>
        public const string IsLeaderInDepts = "urn:dingtalk:is_leader_in_depts";

        /// <summary>
        /// 入职时间。Unix时间戳
        /// </summary>
        public const string HiredDate = "urn:dingtalk:hired_date";

        /// <summary>
        /// 扩展属性
        /// </summary>
        public const string Extattr = "urn:dingtalk:ext_attr";
    }

Demo

详见解决方案中对应的 Demo 项目