很多公司电脑都是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