首页 > 编程语言 >C#(.NetCore)接入AD域用户的实现

C#(.NetCore)接入AD域用户的实现

时间:2024-09-26 19:11:47浏览次数:1  
标签:account return AD NetCore C# value var public string

  很多公司电脑都是windows,而对用户的管理则很多采用AD域的形式来管理,本文简单的来介绍一下.NetCore中怎么接入AD域来实现登录等操作。

  首先,我这里使用的是.net6,其它版本类似。

  其次,这里假设你已经对AD域有了基本的了解,比如AD域所使用的LDAP、属性等,如果不了解先自行百度下。

  接着,接入AD域自然首先需要一个域,怎么搭建一个域控制器,网上太多介绍了,可以自行百度下,加入我现在已经有一个域:demo.cn 

  基本使用

  这里我们基于LDAP协议来连接使用AD域,那么我们需要安装一个包:Novell.Directory.Ldap.NETStandard

  一个简单的用户验证的例子:

    public static void Main(params string[] args)
    {
        var domain = "demo.cn";              //这里也可以是ip
        var port = 389;                      //端口默认389
        //用户名有三种形式
        var account = "Common Name";         //用户姓名全名,即cn属性(Common Name)
        //var account = "[email protected]";   //window 2000之后的版本
        //var account = "DEMO\\account";     //window 2000之前的版本
        var password = "123456";

        //创建实例
        using var connection = new Novell.Directory.Ldap.LdapConnection();
        //连接
        connection.Connect(domain, port);
        //绑定用户(认证)
        connection.Bind(account, password);
        //得到用户的账号信息
        var res = connection.WhoAmI();
        var authzId = res.AuthzIdWithoutType;
        Console.WriteLine($"当前账号:{authzId}");
        //获取根信息
        var root = connection.GetRootDseInfo();
        //根据authzId的格式来处理一下,方便后续查询
        if (authzId.Contains("\\"))
        {
            account = authzId.Split('\\', StringSplitOptions.RemoveEmptyEntries).Last();
        }
        else if (authzId.Contains("@"))
        {
            account = authzId.Split(new string[] { "@" }, StringSplitOptions.RemoveEmptyEntries).First();
        }
        //过滤,规则可以自行百度下,这里根据account来查询
        var filter = string.Format("(&(|(cn={0})(sAMAccountName={0}))(objectCategory=person)(objectClass=user))", account);
        var result = connection.Search(root.DefaultNamingContext, LdapConnection.ScopeSub, filter, null, false);
        //列出所有属性
        while (result.HasMore())
        {
            try
            {
                var entry = result.Next();
                var set = entry.GetAttributeSet();
                Console.WriteLine($"entry: {entry.Dn}{Environment.NewLine}attr count: {set.Count}");
                int index = 1;
                foreach (var attr in set)
                {
                    if (attr.StringValueArray.Length <= 1)
                    {
                        Console.WriteLine($"attr{index}: {attr.Name} = {attr.StringValue}");
                    }
                    else
                    {
                        Console.WriteLine($"attr{index}: {attr.Name} = [{string.Join(", ", attr.StringValueArray)}]");
                    }
                    index++;
                }
            }
            catch { }
        }
    }

  这个简单的例子可以用于打印域用户的所有信息。

  这里简单说下这里认证的账号的三种格式:

  第一种,直接会用CommonName ,就是通用名称,AD域中要求CommonName 是唯一的,上面的例子输出后,可以看到有cn属性,就表示可以使用CommonName 进行授权绑定,那么CommonName 怎么设置呢,其实就是我们在创建域用户的时候由姓名组成的部分,见下图

  第二种,采用sAMAccountName加域名的形式,如果是windows 2000之前的,可以使用这个格式来登录:[domain]\[account] 比如:DEMO\test ,注意这里的域名需要大写,而且是不包含cn、com等后缀,见下图

  第三种,采用userPrincipalName ,格式:[account]@[domain] ,例如:[email protected],这里域名要使用全域名,但是不用大写,见下图(但是第二种貌似是接受度和使用的最多的)

  

  封装使用

  为了方便使用,我这里做了一个封装:

ActiveDirectoryInfo
     public class ActiveDirectoryInfo
    {
        string defaultNamingContext = string.Empty;

        /// <summary>
        /// 域
        /// </summary>
        public string Domain { get; }
        /// <summary>
        /// 端口
        /// </summary>
        public int Port { get; }
        /// <summary>
        /// 过滤
        /// </summary>
        public string Filter { get; set; } = "(&(|(cn={0})(sAMAccountName={0}))(objectCategory=person)(objectClass=user))";

        public ActiveDirectoryInfo(string domain, int port = 389)
        {
            Domain = domain;
            Port = port;
        }

        private string GetAccount(string value)
        {
            if (value.Contains("\\"))
            {
                value = value.Split('\\', StringSplitOptions.RemoveEmptyEntries).Last();
            }
            else if (value.Contains("@"))
            {
                value = value.Split(new string[] { "@" }, StringSplitOptions.RemoveEmptyEntries).First();
            }
            return value;
        }

        public DomainUserInfo Authorize(string account, string password)
        {
            //创建实例
            using var connection = new Novell.Directory.Ldap.LdapConnection();
            //连接
            connection.Connect(Domain, Port);
            //绑定用户(认证)
            connection.Bind(account, password);
            //得到用户的账号信息
            var res = connection.WhoAmI();
            var authzId = res.AuthzIdWithoutType;

            if (string.IsNullOrEmpty(defaultNamingContext))
            {
                //获取根信息
                var root = connection.GetRootDseInfo();
                defaultNamingContext = root.DefaultNamingContext;
            }

            //根据account的格式来处理一下,方便后续查询
            if (authzId.Contains("\\"))
            {
                account = authzId.Split('\\', StringSplitOptions.RemoveEmptyEntries).Last();
            }
            else if (authzId.Contains("@"))
            {
                account = authzId.Split(new string[] { "@" }, StringSplitOptions.RemoveEmptyEntries).First();
            }
            //过滤,规则可以自行百度下,这里根据account来查询
            var filter = string.Format(Filter, account);
            var result = connection.Search(defaultNamingContext, LdapConnection.ScopeSub, filter, null, false);
            LdapAttributeSet attributes = new LdapAttributeSet();
            //列出所有属性
            if (result.HasMore())
            {
                try
                {
                    var entry = result.Next();
                    attributes = entry.GetAttributeSet();
                }
                catch { }
            }
            return new DomainUserInfo(account, authzId, attributes);
        }
    }
DomainUserInfo
     public class DomainUserInfo
    {
        ConcurrentDictionary<string, List<byte[]>> entryDict = new ConcurrentDictionary<string, List<byte[]>>(StringComparer.OrdinalIgnoreCase);

        internal DomainUserInfo(string account, string authzId, LdapAttributeSet set)
        {
            Account = account;
            AuthzId = authzId;

            foreach (var attr in set)
            {
                entryDict[attr.Name] = attr.ByteValueArray.Select(f => f.ToArray()).ToList();
            }
        }


        /// <summary>
        /// 属性个数
        /// </summary>
        public int AttrCount => entryDict.Count;
        /// <summary>
        /// 属性键
        /// </summary>
        public ICollection<string> Keys => entryDict.Keys;
        /// <summary>
        /// Common Name
        /// </summary>
        public string CommonName => Get("cn");
        /// <summary>
        /// SAM Account Name
        /// </summary>
        public string SAMAccountName => Get("sAMAccountName");
        /// <summary>
        /// Dn
        /// </summary>
        public string Dn => Get("distinguishedName");
        /// <summary>
        /// userPrincipalName
        /// </summary>
        public string UserPrincipalName => Get("userPrincipalName");

        /// <summary>
        /// 账号
        /// </summary>
        public string Account { get; }
        /// <summary>
        /// 用户登录账号
        /// </summary>
        public string AuthzId { get; }


        /// <summary>
        /// 获取字符串
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string Get(string key)
        {
            if (entryDict.TryGetValue(key, out var list) && list.Any())
            {
                return Encoding.UTF8.GetString(list.First());
            }
            return string.Empty;
        }
        /// <summary>
        /// 获取字符串数组数据
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string[] GetArray(string key)
        {
            if (entryDict.TryGetValue(key, out var list) && list.Any())
            {
                return list.Select(Encoding.UTF8.GetString).ToArray();
            }
            return Array.Empty<string>();
        }
        /// <summary>
        /// 获取指定基础值类型的数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            return (T)Get(key, typeof(T));
        }
        /// <summary>
        /// 获取指定基础值类型的数据
        /// </summary>
        /// <param name="key"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public object Get(string key, Type type)
        {
            var value = Get(key);
            if (string.IsNullOrEmpty(value))
            {
                return Activator.CreateInstance(type);
            }

            var underlying = Nullable.GetUnderlyingType(type) ?? type;
            if (underlying.IsEnum)
            {
                if (int.TryParse(value, out var result))
                {
                    return Enum.ToObject(underlying, result);
                }
                else
                {
                    return Enum.Parse(underlying, value, true);
                }
            }

            return Convert.ChangeType(value, type);
        }
        /// <summary>
        /// 获取指定格式的数组类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T[] GetArray<T>(string key)
        {
            var array = GetArray(key);
            var elementType = typeof(T);
            if (!array.Any())
            {
                return Array.Empty<T>();
            }

            var underlying = Nullable.GetUnderlyingType(elementType) ?? elementType;
            if (underlying.IsEnum)
            {
                return array.Select(value =>
                {
                    if (int.TryParse(value, out var result))
                    {
                        return (T)Enum.ToObject(underlying, result);
                    }
                    else
                    {
                        return (T)Enum.Parse(underlying, value, true);
                    }
                }).ToArray();
            }

            return array.Select(value => (T)Convert.ChangeType(value, elementType)).ToArray();
        }
        /// <summary>
        /// 获取字节数组
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public byte[] GetBuffer(string key)
        {
            if (entryDict.TryGetValue(key, out var bufferList) && bufferList.Any())
            {
                return bufferList.First().ToArray();
            }
            return Array.Empty<byte>();
        }
        /// <summary>
        /// 获取Dn并返回键值对
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public KeyValuePair<string, string>[] GetDn(string key)
        {
            var value = Get(key);
            if (string.IsNullOrEmpty(value))
            {
                return Array.Empty<KeyValuePair<string, string>>();
            }

            return value.Split(",", StringSplitOptions.RemoveEmptyEntries)
                .Select(f =>
                {
                    var array = f.Split('=');
                    return new KeyValuePair<string, string>(array[0], array[1]);
                }).ToArray();
        }
    }

  使用的简单例子:

    public static void Main(params string[] args)
    {
        var domain = "demo.cn";              //这里也可以是ip
        var port = 389;                      //端口默认389
        //用户名有三种形式
        var account = "Common Name";         //用户姓名全名,即cn属性(Common Name)
        //var account = "[email protected]";   //window 2000之后的版本
        //var account = "DEMO\\account";     //window 2000之前的版本
        var password = "123456";

        var ad = new ActiveDirectoryInfo(domain, port);
        var userInfo = ad.Authorize(account, password);

        Console.WriteLine($"当前账号:{userInfo.AuthzId}");
        Console.WriteLine($"entry: {userInfo.Dn}{Environment.NewLine}attr count: {userInfo.AttrCount}");
        int index = 1;
        foreach (var attr in userInfo.Keys)
        {
            var array = userInfo.GetArray(attr);
            if (array.Length <= 1)
            {
                Console.WriteLine($"attr{index}: {attr} = {array.FirstOrDefault()}");
            }
            else
            {
                Console.WriteLine($"attr{index}: {attr} = [{string.Join(", ", array)}]");
            }
            index++;
        }
    }

 

   总结

  最近客户要求我们系统对接到他们的AD域,避免他们一个个去添加用户,很麻烦,而且用户也不愿意管理多套账号密码,这里是我的一些笔记。

  另一方面,我这里只是使用到登录认证,也就是读信息,但是不写,但是有些系统可能还需要修改AD域用户信息,这其实使用Novell.Directory.Ldap.NETStandard 这个包也是可以实现的,例如下面的方法,因为我暂时没有用到,就不过多介绍了,感兴趣的可以自己试试:

    //添加属性
    connection.Modify(dn, new LdapModification(LdapModification.Add, new LdapAttribute("mail","[email protected]")));
    //修改属性
    connection.Modify(dn, new LdapModification(LdapModification.Replace, new LdapAttribute("mail", "[email protected]")));
    //删除属性
    connection.Modify(dn, new LdapModification(LdapModification.Delete, new LdapAttribute("mail")));
    //删除用户
    connection.Delete(dn);

 

标签:account,return,AD,NetCore,C#,value,var,public,string
From: https://www.cnblogs.com/shanfeng1000/p/18342067

相关文章

  • git 清除二进制文件的 changes 状态
    问题:某个分支上修改了二进制文件,导致changes一直存在,切换到主分支也仍然存在,点击Discard也没用使用gitreset--hard还原到初始状态,也不行,不过输出结果会给出错误信息Encountered7file(s)thatshouldhavebeenpointers,butweren't:解决方法:根据这个线索......
  • JavaScript 值比较 严格相等和严格不相等
    严格相等和严格不相等相等在进行比较时候,会发生类型转换,因此像比较0和false、空字符串和false时,会判定两者相等。但是严格相等和严格不相等,在比较时候不会发生类型转换。例子:lets1=0==false;lets2=''==false;lets3=0===false;lets4=''===false;lets5=1==true;let......
  • VulnStack-红日靶机二
    红日靶机二环境搭建只需要把虚拟机的host-only(仅主机)网卡改为10.10.10.0网段,如下配置把NAT网卡,改为192.168.96.0网段,如下首先恢复到v1.3快照让后点击放弃,放弃后再开机,用其他用户.\de1ay:1qaz@WSX凭证登陆,密码过期修改密码就登陆成功了完成后开启WEB服务器中......
  • redis自身查询很慢 排查redis-benchmark
    redis-benchmark 是一个用于测试Redis性能的基准测试工具,可以帮助开发人员评估和比较Redis在不同配置或负载情况下的吞吐量和延迟。通过 redis-benchmark 的测试结果,你可以获得qps、平均延迟、错误率等性能指标,从而根据需要进行调优和优化,确保Redis在实际生产环境中具有良......
  • 文心一言 VS 讯飞星火 VS chatgpt (355)-- 算法导论24.2 1题
    一、请在图24-5上运行DAG-SHORTEST-PATHS,使用结点r作为源结点。如果要写代码,请用go语言。文心一言:在Go语言中实现一个DAG(有向无环图)上的最短路径算法(例如,DAG-SHORTEST-PATHS,这通常可以通过简单的拓扑排序加动态规划或者简单的迭代方法来实现,因为DAG中没有环)并不复杂。但......
  • 使用Xcode制作天气APP
    注:请自行封装API数据请求接口下载开发工具Xcode新建项目输入自己的项目名称等信息,这里使用的是Storyboard界面创建HttpSender类,新建Swift文件。本次提供POST请求。importFoundationclassHttpSender{//JSON解析器privatestaticletdecoder=JSONDecoder()......
  • ECU电控软件开发及测试介绍
        伴随着电动化、智能化、网联化等技术发展的时代背景,各行各业电子电气架构都在发生深度变革。新型架构逐渐取代传统架构,比如汽车、工程机械、储能、船舶等领域,电子电气架构从传统分布式向域集中式,甚至向着中央集中式发展,控制器功能呈现集中化、复杂化的特点。为了提升开......
  • WEB服务器——Tomcat
    服务器是可以使用java完成编写,是可以接受页面发送的请求和响应数据给前端浏览器的,而在开发中真正用到的Web服务器,我们不会自己写的,都是使用目前比较流行的web服务器。如:Tomcat1.简介Tomcat是一个开源的轻量级Web服务器,主要用于运行JavaWeb应用。它支持Servlet和JS......
  • 信息学奥赛复赛复习04-CSP-J2019-04-加工零件-位运算、整数映射0或1、结构体、初始化
    PDF文档回复:20240926<12019CSP-J题目4加工零件[题目描述]凯凯的工厂正在有条不紊地生产一种神奇的零件,神奇的零件的生产过程自然也很神奇。工厂里有n位工人,工人们从1∼n编号。某些工人之间存在双向的零件传送带。保证每两名工人之间最多只存在一条传送带如果......
  • sidecar机制在k8s中的使用场景
    在Kubernetes中,Sidecar模式可以用于多种场景,除了日志收集外,以下是一些常见的应用场景:1.代理和负载均衡Sidecar可以充当服务代理,处理入站和出站的流量,进行负载均衡和请求路由。例如,使用Envoy或Linkerd作为Sidecar,可以实现服务间的负载均衡、熔断和重试机制。2.监控和......