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

news/2024/9/27 21:19:07

  很多公司电脑都是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";              //这里也可以是ipvar port = 389;                      //端口默认389//用户名有三种形式var account = "Common Name";         //用户姓名全名,即cn属性(Common Name)//var account = "account@demo.cn";   //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] ,例如:test@demo.cn,这里域名要使用全域名,但是不用大写,见下图(但是第二种貌似是接受度和使用的最多的)

  

  封装使用

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

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";              //这里也可以是ipvar port = 389;                      //端口默认389//用户名有三种形式var account = "Common Name";         //用户姓名全名,即cn属性(Common Name)//var account = "account@demo.cn";   //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","test@demo.com")));//修改属性connection.Modify(dn, new LdapModification(LdapModification.Replace, new LdapAttribute("mail", "user@demo.com")));//删除属性connection.Modify(dn, new LdapModification(LdapModification.Delete, new LdapAttribute("mail")));//删除用户connection.Delete(dn);

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/65093.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

三,MyBatis-Plus 的各种查询的“超详细说明”,比如(等值查询,范围查询,模糊查询...)

三,MyBatis-Plus 的各种查询的“超详细说明”,比如(等值查询,范围查询,模糊查询...) @目录三,MyBatis-Plus 的各种查询的“超详细说明”,比如(等值查询,范围查询,模糊查询...)1. 条件构造器介绍2. 准备工作:3. 等值查询3.1 eq (条件筛选属性 = ?)3.2 allEq(满足多个条…

git 清除二进制文件的 changes 状态

问题:某个分支上修改了二进制文件,导致 changes 一直存在,切换到主分支也仍然存在,点击 Discard 也没用使用 git reset --hard 还原到初始状态,也不行,不过输出结果会给出错误信息Encountered 7 file(s) that should have been pointers, but werent:解决方法: 根据这个…

软件工程第一次结队作业

这个作业属于哪个课程 软件工程这个作业要求在哪里 作业要求这个作业的目标 需求分析与原型设计,初步实践软件开发合作学号 172209028合作伙伴 102202129林伟宏原型地址:墨刀原型在线展示体验 102202129 林伟宏 172209028伊晓 一、《构建之法》阅读成果 第3章 软件工程师的成…

MySQL 库、表的操作与使用

目录数据库的编码集与校验集表的基本结构库的操作(DDL)创建数据库带字符集创建带校验集创建查看数据库查看自己正在使用的是哪一个数据库显示创建语句删除数据库使用数据库查看当前使用的数据库数据库备份与还原备份还原查看数据库连接数表的操作(DDL)建表查看数据库中的表查看…

密码学承诺之原理和应用 - sigma承诺

微信公众号:密码应用技术实战 博客园首页:https://www.cnblogs.com/informatics/ GIT地址:https://github.com/warm3snow 简介 在上一篇文章《密码学承诺之原理和应用 - 概览》中,我们详细介绍了常见的密码学承诺原理,本节我们将重点介绍Sigma承诺的实现和应用。 Sigma承诺…

MiniMax、商汤科技、面壁智能、西湖心辰、声网都来了!RTE 大会「实时互动和大模型」专场开启报名

当大模型进化到 实时多模态 ,将诞生什么样的新场景和玩法?Voice AI 实现 human-like 的最后一步是什么?AI 视频爆炸增长,新一代编解码技术 将面临何种挑战?所有 AI Infra 都在探寻规格和性能的最佳平衡,如何构建高可用的云边端协同架构?AI 加持下,空间计算和新硬件 也迎…

企业级反向代理 HAProxy

企业级反向代理 HAProxy haproxy只做代理,不提供其他功能。可以做四层,七层代理。有些公司会用haproxy做四层代理, haproxy比lvs好在功能更强大2 HAProxy 简介 企业版(收费) 社区版社区版网站:http://www.haproxy.org/ github:https://github.com/haproxy #选偶数版本,长…

题解:P4288 [SHOI2014] 信号增幅仪

很好一题目,使我的最小圆覆盖旋转。 先假设 \(p = 1\)。这是最简单的情况。这个时候我们就得到了一个裸的最小圆覆盖。 当 \(p \not= 1\),但是 \(a = 0\) 的时候。圆就变成了对称轴与坐标轴平行的椭圆,运用高中知识仿射一下,又回到了最小圆覆盖。 在一般的情况下,我们先通…