当前位置: 首页 > news >正文

Golang对接Ldap(保姆级教程:概念搭建实战)

Golang对接Ldap(保姆级教程:概念&搭建&实战)

最近项目需要对接客户的LDAP服务,于是趁机好好了解了一下。LDAP实际是一个协议,对应的实现,大家可以理解为一个轻量级数据库。用户查询。比如:我要查询某个用户有没有对应的访问权限。

  • Windows的AD域就是LDAP的一个具体实现,当然AD域除了实现LDAP还实现了其他协议。

🚄本文教程所用代码地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ldap_demo

0 Ldap(Light Directory Access Portocol)

我们日常的办公系统是不是有多个?每个系统之间是不是都有独立的账号密码?密码多了,有时候半天想不起来哪个密码对应哪个系统?

  • 如今大家再也不用为上面的的问题头疼了,因为“LDAP统一认证服务”已经帮助大家解决这些问题了

1 LDAP(轻量级目录访问协议,查询快,特殊的数据库)

LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。

  • 是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
  • 目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。

LDAP目录服务是由目录数据库和一套访问协议组成的系统。

2 LDAP主流厂商

细心的朋友应该会主要到,LDAP的中文全称是:轻量级目录访问协议,说到底LDAP仅仅是一个访问协议,那么我们的数据究竟存储在哪里呢?
在这里插入图片描述

3 核心概念&术语

核心概念:

  1. 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目。
  2. 条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。
  3. 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。
  4. 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。
①dc(Domain Component):域名部分,dc=com

域名的部分,其格式是将完整的域名分成几部分,如域名为example.com变成dc=example,dc=com(一条记录的所属位置)

②uid(User Id):用户Id,uid=ziyi.zhou

用户ID ziyi.zhou(一条记录的ID)

③ou(Organization Unit):组织,ou=develop

组织单位,组织单位可以包含其他各种对象(包括其他组织单元),如“oa组”(一条记录的所属组织)

④cn(Common Name):公共名称,cn=jack
⑤sn(Surname):姓,sn=周
⑥dn(Distinguished Name):类比URL。一条记录的唯一标识。uid=ziyi.zhou,ou=oa组,dc=example,dc=com。

“uid=ziyi.zhou,ou=oa组,dc=example,dc=com”,一条记录的位置(唯一)

  • 类比URL:唯一定位Ldap服务中的一条记录。
⑦rdn(Relative dn):类比文件系统相对路径

相对辨别名,类似于文件系统中的相对路径,它是与目录树结构无关的部分,如“uid=tom”或“cn= Thomas Johansson”

汇总表

使用LDAP流程:

  1. 连接到LDAP服务器;
  2. 绑定到LDAP服务器;
  3. 在LDAP服务器上执行所需的任何操作;
  4. 释放LDAP服务器的连接;
    在这里插入图片描述

参考:https://www.cnblogs.com/wilburxu/p/9174353.html

1 Linux搭建Ldap

以Centos为例搭建openldap。

1.1 搭建Ldap服务(Server端)

1. 安装openldap
# 安装openldap
yum -y install openldap-servers openldap-clients 
# 对管理员密码进行加密
slappasswd -s 123456
#加密后的密码(后面需要用到): {SSHA}VHNPrmccIO/QRS1IOBdwp++K/FkIkFac
2. 新建Ldap配置文件
# 新建Ldap配置文件
vim /etc/openldap/schema/changes.ldif

changes.ldif:

# 修改域名
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
# 注意修改
olcSuffix: dc=yi,dc=com# 修改管理员用户
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootDN
# 注意修改修改管理员用户 (olcRootDN):将管理员账户从原来的cn=admin,dc=ldap,dc=com改为cn=admin,dc=yi,dc=com。
olcRootDN: cn=admin,dc=yi,dc=com# 修改管理员密码
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootPW
# 替换为 slappasswd 生成后的结果
olcRootPW: {SSHA}VHNPrmccIO/QRS1IOBdwp++K/FkIkFac# 修改访问权限
dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to *by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" readby dn.base="cn=admin,dc=yi,dc=com" readby * none
3. 创建域和组织
# 创建配置文件
vim /etc/openldap/schema/basedomain.ldif

basedomain.ldif:

# 配置文件内容,dn、dc都改成自己的配置
# 下列目录:DC=redmond,DC=wa,DC=microsoft,DC=com      
# 如果我们类比文件系统的话,可被看作如下文件路径:   
# Com/Microsoft/Wa/Redmond  
# 例如:CN=test,OU=developer,DC=domainname,DC=com
# 在上面的代码中 cn=test 可能代表一个用户名,ou=developer 代表一个 active directory 中的组织单位。这句话的含义可能就是说明 test 这个对象处在domainname.com 域的 developer 组织单元中dn: dc=yi,dc=com
dc: yi
objectClass: top
objectClass: domaindn: ou=People,dc=yi,dc=com
ou: People
objectClass: top
objectClass: organizationalUnitdn: ou=Group,dc=yi,dc=com
ou: Group
objectClass: top
objectClass: organizationalUnit

应用配置:

# 应用域和配置,回车之后输入未加密前的密码:123456
ldapadd -x -D cn=admin,dc=yi,dc=com -W -f  /etc/openldap/schema/basedomain.ldif# 查看用户列表,观察是否创建成功
ldapsearch -x -b "ou=People,dc=yi,dc=com" | grep dn

通过配置文件新建一个ou

# 新建配置文件
vim testGroup.ldif# 配置文件内容ou:指明为Test Group
dn: ou=Test,dc=yi,dc=com
ou: Test
objectClass: top
objectClass: organizationalUnit# 应用配置(cn=admin,dc=yi,dc=com:admin账户)
# 回车后输入加密前的密码:123456
ldapadd -x -D "cn=admin,dc=yi,dc=com" -W -f testGroup.ldif

在这里插入图片描述

  • 执行命令前:
    在这里插入图片描述
  • 执行命令,应用配置文件新增一个ou
    在这里插入图片描述

1.2 docker搭建可视化工具

1. 安装docker环境
yum install -y yum-utils
yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo
yum install docker
systemctl start docker
2. 开放ldap端口(399)
# 开放ldap server389端口或关闭防火墙
firewall-cmd --zone=public --add-port=389/tcp --permanent
systemctl restart firewalld
3. docker搭建可视化Ldap管理工具
# 配置主机地址&不开启HTTPS(默认是开启)
docker run -d --privileged -p 10004:80 --name myphpldapadmin \--env PHPLDAPADMIN_HTTPS=false --env PHPLDAPADMIN_LDAP_HOSTS=10.16.64.147  \--detach osixia/phpldapadmin

在这里插入图片描述

4. 浏览器访问

浏览器访问http://ip:10004

账户:

  • dn:cn=admin,dc=yi,dc=com
  • 密码:123456(我们生成密钥之前的明文,slappasswd -s 123456)

登录:
在这里插入图片描述
在这里插入图片描述

1.3 其他客户端工具推荐

1. windows:ldapadmin

官网地址:http://www.ldapadmin.org/

页面效果:
在这里插入图片描述

2. mac:LDAP Browser For MAC

官网地址:https://ldapbrowsermac.com/

在这里插入图片描述

使用效果:
在这里插入图片描述

2 Go操作Ldap

  1. 连接到LDAP服务器并绑定到LDAP服务器;(一般以管理员用户绑定,权限更大)
  2. 在LDAP服务器上执行所需的任何操作;
  3. 释放LDAP服务器的连接;

2.1 连接并以相应角色绑定LDAP服务器

安装依赖:

// 安装go操作ldap库
go get "github.com/go-ldap/ldap/v3"
func loginBind(config *LdapConfig) (*ldap.Conn, error) {l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))if err != nil {panic(err)return nil, err}_, err = l.SimpleBind(&ldap.SimpleBindRequest{Username: config.BindUserDn, //"cn=admin,dc=yi,dc=com"Password: config.BindUserPassword, //"123456"})if err != nil {fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)return nil, err}fmt.Println("bind success...")return l, nil
}

2.2 执行对应操作(add、select、del等)

1. add

以添加用户为例

func addUser(conn *ldap.Conn, user User) error {//添加用户addRequest := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=QA,dc=yi,dc=com", user.username), nil)addRequest.Attribute("objectClass", []string{"inetOrgPerson"})addRequest.Attribute("ou", []string{"QA Group"})addRequest.Attribute("cn", []string{"41234123"})addRequest.Attribute("sn", []string{"xx2"})addRequest.Attribute("uid", []string{"10001"})addRequest.Attribute("userPassword", []string{user.password})err := conn.Add(addRequest)if err != nil {fmt.Println("add user error: ", err)return err}return nil
}

执行添加前:
在这里插入图片描述
运行main,在ou=QA下添加一条记录:

package mainimport ("crypto/tls""fmt""github.com/go-ldap/ldap/v3""github.com/ziyifast/log"
)// ldap:未加密
// ldaps:加密
var ldapURL = "ldap://10.100.xx.xxx"type LdapConfig struct {Addr             stringBindUserDn       stringBindUserPassword stringBaseDn           stringLoginName        stringObjectClass      []string
}type User struct {username    stringpassword    stringtelephone   stringemailSuffix stringsnUsername  stringuid         stringgid         string
}func loginBind(config *LdapConfig) (*ldap.Conn, error) {l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))if err != nil {panic(err)return nil, err}_, err = l.SimpleBind(&ldap.SimpleBindRequest{Username: config.BindUserDn,Password: config.BindUserPassword,})if err != nil {fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)return nil, err}fmt.Println("bind success...")return l, nil
}// 创建用户
func addUser(conn *ldap.Conn, user User) error {//添加用户addRequest := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=QA,dc=yi,dc=com", user.username), nil)addRequest.Attribute("objectClass", []string{"inetOrgPerson"})addRequest.Attribute("ou", []string{"QA Group"})addRequest.Attribute("cn", []string{"41234123"})addRequest.Attribute("sn", []string{"xx2"})addRequest.Attribute("uid", []string{"10001"})addRequest.Attribute("userPassword", []string{user.password})err := conn.Add(addRequest)if err != nil {fmt.Println("add user error: ", err)return err}return nil
}
func main() {//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)config := new(LdapConfig)config.Addr = "ldap://10.100.xx.xxx"config.BaseDn = "dc=yi,dc=com"config.BindUserDn = "cn=admin,dc=yi,dc=com"config.LoginName = "uid"config.BindUserPassword = "123456"config.ObjectClass = []string{"inetOrgPerson"}//与建立ldap服务建立连接(方便后续查询新增删除项)conn, err := loginBind(config)if err != nil {panic(err)}defer conn.Close()TestAddUser(conn)
}// TestAddUser 测试添加用户
func TestAddUser(conn *ldap.Conn) {//添加用户user := User{username: "wangmazi",password: "123456",}err := addUser(conn, user)if err != nil {panic(err)}fmt.Println("add success...")
}

在这里插入图片描述
效果:

添加成功

在这里插入图片描述

2. select
  • 拼接查询条件
    • 单个条件(cn=jack):查询cn为jack的资源
    • 多个条件(&(cn=wangmazi)(ou=QA)):查询cn为wangmazi并且ou为QA的资源
  • ldap.NewSearchRequest(fmt.Sprintf(“%s”, config.BaseDn)调用查询接口
// 查询用户
func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {//多个条件:(&(cn=wangmazi)(ou=QA))filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),ldap.ScopeWholeSubtree,ldap.NeverDerefAliases,0,0,false,filter,[]string{"userPassword"},nil,)searchResult, err := conn.Search(request)if err != nil {fmt.Println("search user error: ", err)return nil, err}return searchResult, nil
}

运行:

package mainimport ("crypto/tls""fmt""github.com/go-ldap/ldap/v3""github.com/ziyifast/log"
)// ldap:未加密
// ldaps:加密
var ldapURL = "ldap://10.100.xx.xxx"type LdapConfig struct {Addr             stringBindUserDn       stringBindUserPassword stringBaseDn           stringLoginName        stringObjectClass      []string
}type User struct {username    stringpassword    stringtelephone   stringemailSuffix stringsnUsername  stringuid         stringgid         string
}func loginBind(config *LdapConfig) (*ldap.Conn, error) {l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))if err != nil {panic(err)return nil, err}_, err = l.SimpleBind(&ldap.SimpleBindRequest{Username: config.BindUserDn,Password: config.BindUserPassword,})if err != nil {fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)return nil, err}fmt.Println("bind success...")return l, nil
}// 查询用户
func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {//多个条件:(&(cn=wangmazi)(ou=QA))filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),ldap.ScopeWholeSubtree,ldap.NeverDerefAliases,0,0,false,filter,[]string{"userPassword"},nil,)searchResult, err := conn.Search(request)if err != nil {fmt.Println("search user error: ", err)return nil, err}return searchResult, nil
}
func main() {//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)config := new(LdapConfig)config.Addr = "ldap://10.100.xx.xxx"config.BaseDn = "dc=yi,dc=com"config.BindUserDn = "cn=admin,dc=yi,dc=com"config.LoginName = "uid"config.BindUserPassword = "123456"config.ObjectClass = []string{"inetOrgPerson"}//与建立ldap服务建立连接(方便后续查询新增删除项)conn, err := loginBind(config)if err != nil {panic(err)}defer conn.Close()TestFindUser(conn, config)
}// TestFindUser 测试查询用户
func TestFindUser(conn *ldap.Conn, config *LdapConfig) {user := &User{username: "wangmazi",}searchResult, err := findUser(conn, config, *user)if err != nil {panic(err)}for _, entry := range searchResult.Entries {fmt.Println("find user: ", entry.DN)for _, v := range entry.Attributes {fmt.Println(v.Name, v.Values)}}return
}

效果:
在这里插入图片描述

3. del
  • 拼接要删除的DN(唯一标识,定位一个资源的具体位置)
  • ldap.NewDelRequest(dn, nil)调用删除请求
// 删除用户
func deleteUser(conn *ldap.Conn, config *LdapConfig, user User) error {dn := fmt.Sprintf("cn=%s,ou=QA,%s", user.username, config.BaseDn)log.Infof("del dn %v", dn)delRequest := ldap.NewDelRequest(dn, nil)err := conn.Del(delRequest)if err != nil {fmt.Printf("Failed to delete user %s: %v\n", dn, err)return err}fmt.Printf("User %s successfully deleted.\n", dn)return nil
}

删除前:
在这里插入图片描述
运行TestDeleteUser删除该记录:

package mainimport ("crypto/tls""fmt""github.com/go-ldap/ldap/v3""github.com/ziyifast/log"
)// ldap:未加密
// ldaps:加密
var ldapURL = "ldap://10.100.xx.xxx"type LdapConfig struct {Addr             stringBindUserDn       stringBindUserPassword stringBaseDn           stringLoginName        stringObjectClass      []string
}type User struct {username    stringpassword    stringtelephone   stringemailSuffix stringsnUsername  stringuid         stringgid         string
}func loginBind(config *LdapConfig) (*ldap.Conn, error) {l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))if err != nil {panic(err)return nil, err}_, err = l.SimpleBind(&ldap.SimpleBindRequest{Username: config.BindUserDn,Password: config.BindUserPassword,})if err != nil {fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)return nil, err}fmt.Println("bind success...")return l, nil
}// 查询用户
func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {//多个条件:(&(cn=wangmazi)(ou=QA))filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),ldap.ScopeWholeSubtree,ldap.NeverDerefAliases,0,0,false,filter,[]string{"userPassword"},nil,)searchResult, err := conn.Search(request)if err != nil {fmt.Println("search user error: ", err)return nil, err}return searchResult, nil
}func main() {//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)config := new(LdapConfig)config.Addr = "ldap://10.100.xx.xxx"config.BaseDn = "dc=yi,dc=com"config.BindUserDn = "cn=admin,dc=yi,dc=com"config.LoginName = "uid"config.BindUserPassword = "123456"config.ObjectClass = []string{"inetOrgPerson"}//与建立ldap服务建立连接(方便后续查询新增删除项)conn, err := loginBind(config)if err != nil {panic(err)}defer conn.Close()TestDeleteUser(conn, config)
}

运行后效果:
在这里插入图片描述
在这里插入图片描述

2.3 释放连接

func main() {//与建立ldap服务建立连接(方便后续查询新增删除项)conn, err := loginBind(config)if err != nil {panic(err)}err = conn.Close()if err != nil {panic(err)}
}

全部代码

代码地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/ldap_demo

package mainimport ("crypto/tls""fmt""github.com/go-ldap/ldap/v3""github.com/ziyifast/log"
)// ldap:未加密
// ldaps:加密
var ldapURL = "ldap://10.16.xx.xx"type LdapConfig struct {Addr             stringBindUserDn       stringBindUserPassword stringBaseDn           stringLoginName        stringObjectClass      []string
}type User struct {username    stringpassword    stringtelephone   stringemailSuffix stringsnUsername  stringuid         stringgid         string
}func loginBind(config *LdapConfig) (*ldap.Conn, error) {l, err := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))if err != nil {panic(err)return nil, err}_, err = l.SimpleBind(&ldap.SimpleBindRequest{Username: config.BindUserDn,Password: config.BindUserPassword,})if err != nil {fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)return nil, err}fmt.Println("bind success...")return l, nil
}// 创建用户
func addUser(conn *ldap.Conn, user User) error {//添加用户addRequest := ldap.NewAddRequest(fmt.Sprintf("cn=%s,ou=QA,dc=yi,dc=com", user.username), nil)addRequest.Attribute("objectClass", []string{"inetOrgPerson"})addRequest.Attribute("ou", []string{"QA Group"})addRequest.Attribute("cn", []string{"41234123"})addRequest.Attribute("sn", []string{"xx2"})addRequest.Attribute("uid", []string{"10001"})addRequest.Attribute("userPassword", []string{user.password})err := conn.Add(addRequest)if err != nil {fmt.Println("add user error: ", err)return err}return nil
}// 查询用户
func findUser(conn *ldap.Conn, config *LdapConfig, user User) (*ldap.SearchResult, error) {//多个条件:(&(cn=wangmazi)(ou=QA))filter := fmt.Sprintf("(cn=%s)", ldap.EscapeFilter(user.username))request := ldap.NewSearchRequest(fmt.Sprintf("%s", config.BaseDn),ldap.ScopeWholeSubtree,ldap.NeverDerefAliases,0,0,false,filter,[]string{"userPassword"},nil,)searchResult, err := conn.Search(request)if err != nil {fmt.Println("search user error: ", err)return nil, err}return searchResult, nil
}// 删除用户
func deleteUser(conn *ldap.Conn, config *LdapConfig, user User) error {dn := fmt.Sprintf("cn=%s,ou=QA,%s", user.username, config.BaseDn)log.Infof("del dn %v", dn)delRequest := ldap.NewDelRequest(dn, nil)err := conn.Del(delRequest)if err != nil {fmt.Printf("Failed to delete user %s: %v\n", dn, err)return err}fmt.Printf("User %s successfully deleted.\n", dn)return nil
}func main() {//Ldap Config(用于校验后续的操作,包括查询用户是否存在、添加、删除等)config := new(LdapConfig)config.Addr = "ldap://10.16.xx.xx"config.BaseDn = "dc=yi,dc=com"config.BindUserDn = "cn=admin,dc=yi,dc=com"config.LoginName = "uid"config.BindUserPassword = "123456"//客户不配置username,我们需要根据配置的ObjectClass查询出对应的用户。//因为如果用户配置的是cn,那么可能会查询出一些组织、其他设备等,所以为了将Ldap第三方用户纳管过来,我们需要添加ObjectClassconfig.ObjectClass = []string{"inetOrgPerson"}//与建立ldap服务建立连接(方便后续查询新增删除项)conn, err := loginBind(config)if err != nil {panic(err)}defer conn.Close()TestDeleteUser(conn, config)
}// TestAddUser 测试添加用户
func TestAddUser(conn *ldap.Conn) {//添加用户user := User{username: "wangmazi",password: "123456",}err := addUser(conn, user)if err != nil {panic(err)}fmt.Println("add success...")
}// TestFindUser 测试查询用户
func TestFindUser(conn *ldap.Conn, config *LdapConfig) {user := &User{username: "wangmazi",}searchResult, err := findUser(conn, config, *user)if err != nil {panic(err)}for _, entry := range searchResult.Entries {fmt.Println("find user: ", entry.DN)for _, v := range entry.Attributes {fmt.Println(v.Name, v.Values)}}return
}func TestDeleteUser(conn *ldap.Conn, config *LdapConfig) {user := User{username: "wangmazi",}err := deleteUser(conn, config, user)if err != nil {panic(err)}}

3 项目对接思路

项目登录对接:支持LDAP登录,用户可直接输入LDAP服务端存在的用户,直接登录系统

  1. 页面提供入口配置LDAP服务
    • Addr:LDAP服务端地址
    • BindUserDn:LDAP管理员用户dn
    • BindUserPassword:LDAP管理员密码
    • BaseDn:操作范围(dc=yi,dc=com表明操作这个范围下的数据)
    • LoginName:配置以哪个参数登录
  2. 页面输入LDAP对应账号
  3. 根据LDAP配置连接LDAP服务端,查询用户输入的账号是否存在,密码是否正确
  4. 可以直接纳管LDAP用户到我方系统,建立对应关系。比如:用户审计…
type LdapConfig struct {Addr             stringBindUserDn       stringBindUserPassword stringBaseDn           stringLoginName        stringObjectClass      []string
}

在这里插入图片描述

相关文章:

Golang对接Ldap(保姆级教程:概念搭建实战)

Golang对接Ldap(保姆级教程:概念&搭建&实战) 最近项目需要对接客户的LDAP服务,于是趁机好好了解了一下。LDAP实际是一个协议,对应的实现,大家可以理解为一个轻量级数据库。用户查询。比如&#xff…...

Java23种设计模式-创建型模式之工厂方法模式

工厂方法模式(Factory Method Pattern) 一种创建型设计模式,它定义了一个用于创建对象的接口,让子类决定将哪一个类实例化,从而将产品的实例化推迟到子类中。这种模式的主要角色包括: 角色1:抽…...

Oracle故障处理:ORA-00600错误处理思路

提前说明: 该故障,我只是旁观者。 但处理该故障的DBA工程师,思路很清晰,我非常受教!在此也将经验分享。 目录 项目场景 问题分析 优化建议 项目场景 在某项目数据库运维群,有现场同事发了张报错截图如下…...

微信小程序使用 Vant Weapp 中 Collapse 折叠面板 的问题!

需求:结合Tab 标签页 和 Collapse 折叠面板 组合成显示课本和章节内容,并且用户体验要好点! 如下图展示: 问题:如何使用Collapse 折叠面板 将内容循环展示出来? js中的数据是这样的 代码实现&#xff1…...

论文写作神器:用ChatGPT写论文的5大高效技巧

在人工智能日渐成熟的今天,ChatGPT已经成为学术界、业界乃至日常生活中不可或缺的工具之一。尤其是对于学生和研究人员而言,ChatGPT能大幅度提高论文写作的效率和质量。然而,许多人尚未掌握如何高效利用这一工具,很多人用chatgpt写…...

微信小程序展示倒计时

html <view class"countdown"> <text>倒计时&#xff1a;</text> <text wx:for"{{countdown}}" wx:key"index">{{item}}</text> </view> ts data: {countdown: [], // 存放倒计时数组 targetTime:…...

什么是用户体验(UX)文案,为什么它很重要?

网上购物如今比以往任何时候都更加相关。所以我们将以此为例说明什么是用户体验&#xff08;UX&#xff09;文案&#xff0c;以及为什么它很重要。 假设你去了一个在线商店。你需要执行一系列操作&#xff1a; 找到合适的部分选择你感兴趣的产品弄清楚它们是什么&#xff0c;…...

算法06链表

算法06链表 一、链表概述1.1概述1.2链表的组成部分&#xff1a;1.3链表的优缺点&#xff1a; 二、链表典例力扣707.设计链表难点分析&#xff1a;&#xff08;1&#xff09;MyLinkedList成员变量的确定&#xff1a;&#xff08;2&#xff09;初始化自定义链表&#xff1a;&…...

第十七章 数据管理和组织变革管理

17.2 变革法则 1&#xff09;组织不变革&#xff0c;人就变。 2&#xff09;人们不会抗拒变革&#xff0c;但抵制被改变。 3&#xff09;事情之所以存在是惯性所致。 4&#xff09;除非有人推动变革&#xff0c;否则很可能止步不前。 5&#xff09;如果不考虑人的因素&#xf…...

基于harris角点和RANSAC算法的图像拼接matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ....................................................................... I1_harris fu…...

C++感受6-Hello World 交互版

变量、常量输入、输出、流getline() 函数读入整行输入Hello() 函数复习新定义函数 Input() 实现友好的人机交互还有 “痘痘” 为什么挤不到的分析…… 1. DRY 原则简介 上一节课&#xff0c;我们写了两版“问候”程序。第一版的最大问题是重复的内容比较多&#xff0c;每一次问…...

02_c/c++开源库ZeroMQ

1.安装 C库 libzmq sudo apt install libzmq3-dev 实例: https://zeromq.org/get-started/?languagec&librarylibzmq# 编译依赖: pkg-config --cflags --libs libzmq or cat /usr/lib/x86_64-linux-gnu/pkgconfig/libzmq.pc -isystem /usr/include/mit-krb5 -I/usr/in…...

计算机视觉 CV 八股分享 [自用](更新中......)

目录 一、深度学习中解决过拟合方法 二、深度学习中解决欠拟合方法 三、梯度消失和梯度爆炸 解决梯度消失的方法 解决梯度爆炸的方法 四、神经网络权重初始化方法 五、梯度下降法 六、BatchNorm 七、归一化方法 八、卷积 九、池化 十、激活函数 十一、预训练 十二…...

【MHA】MySQL高可用MHA源码1-主库故障监控

1 阅读之前的准备工作 1 一个IDE工具 &#xff0c;博主自己尝试了vscode安装perl的插件&#xff0c;但是函数 、变量 、模块等都不能跳转&#xff0c;阅读起来不是很方便。后来尝试使用了pycharm安装perl插件&#xff0c;阅读支持跳转&#xff0c;自己也能写一些简单的测试样例…...

如何一键清除文件目录下所有的node_modules

如何一键清除文件目录下所有的node_modules 快速删除目录下的node_modules&#xff0c;下面附上windows和mac的脚本指令 windows脚本 FOR /d /r . %d in (node_modules) DO IF EXIST "%d" rm -rf "%d"mac脚本 find . -name "node_modules" -…...

【产品经理修炼之道】- 导航架构设计

目录 一、导航是什么 二、导航的作用 三、导航的分类 四、导航菜单的广度与深度 五、导航的颜色 六、导航的形态 七、导航的研究 八、导航的设计 九、导航改版案例分享 总结 每个网页的设计都需要包括导航&#xff0c;那么导航架构该如何设计&#xff1f;作者结合之前…...

本地部署和运行大型语言模型(Large Language Models, LLMs)的工具Ollama

文章目录 本地部署和运行大型语言模型&#xff08;Large Language Models, LLMs&#xff09;的工具Ollama背景什么是Ollama主要功能优势 使用场景Ollama LangChain 实现本地运行Llama 3 本地部署和运行大型语言模型&#xff08;Large Language Models, LLMs&#xff09;的工具…...

Python-100-Days: Day01

Day01 Python简介 1.1989年Guido von Rossum在圣诞节之夜开始着手python语言编译器的编写。 2.1991年2月 Python v1 编译器诞生&#xff0c;使用C实现的&#xff0c;此时可以调用C的库函数。 3.1994年1月&#xff0c;Python v1.0 正式版发布。 4.2000年10月16日&#xff0…...

g 对象:Flask 应用中的“临时口袋”

文章目录 g对象的理解Flask 中的 g 对象g 对象的特点:使用 g 对象: 示例 g对象的理解 想象一下&#xff0c;你在逛超市。你需要一个购物篮来装你挑选的商品。这个购物篮就像 Flask 应用中的 g 对象&#xff0c;它是一个临时存放东西的地方&#xff0c;方便你在购物过程中随时取…...

JavaEE初阶——多线程(七)——定时器

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 小比特 大梦想 此篇文章与大家分享多线程的第七篇文章——关于定时器 如果有不足的或者错误的请您指出! 目录 4.定时器4.1标准库提供的定时器4.2自己实现一个定时器4.2.1任务类4.2.2Timer类4.2.3 有一个线程来负…...

嵌入式4-24

作业&#xff1a; 整理思维导图 定义一个矩形类Rec&#xff0c;包含私有属性length&#xff0c;width&#xff0c;有以下成员函数&#xff1a; void set_length(int l); //设置长度 void set_width(int w); //设置宽度 int get_length(); //获取长度 int get_width(); //获取宽…...

跟我学C++中级篇——临时对象

一、临时对象 Temporary object&#xff0c;临时对象。一听名字就明白&#xff0c;这个对象的意义不大&#xff0c;只是临时中转一下或者存在一下&#xff0c;有的可能连个存在感都刷不到就消失了。但不要小看这种临时对象&#xff0c;对C/C这种以效率严苛为前提的编程环境下&…...

【S32K3 MCAL配置】-7.1-GPT Driver:定时器中断-创建一个周期执行的任务

"><--返回「Autosar_MCAL高阶配置」专栏主页--> 案例背景:常用于周期点亮/关闭一个LED灯;或者精度一般的占空比为50% PWM方波;或者周期调用一个函数,在该函数中我们可以执行一些软件策略(简易的OS)。 目录(共15页精讲,基于评估板: NXP S32K312EVB-Q172,…...

java可盈保险合同管理系统的设计与实现(springboot+mysql源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的可盈保险合同管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于Spring Boot的…...

【智能算法】囊状虫群算法(TSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2020年&#xff0c;S Kaur等人受到囊状虫群自然行为启发&#xff0c;提出了囊状虫群算法&#xff08;Tunicate Swarm Algorithm, TSA&#xff09;。 2.算法原理 2.1算法思想 TSA模拟了囊状虫群在导…...

python基础——正则表达式

&#x1f4dd;前言&#xff1a; 这篇文章主要想讲解一下python中的正则表达式&#xff1a; 1&#xff0c;什么是正则表达式 2&#xff0c;re模块三匹配 3&#xff0c;元字符匹配 4&#xff0c;具体示例 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&am…...

T1级,生产环境事故—Shell脚本一键备份K8s的YAML文件

大家好&#xff0c;我叫秋意零。 最近对公司进行日常运维工作时&#xff0c;出现了一个 T1 级别事故。导致公司的“酒云网”APP的无法使用。我和我领导一起搞了一个多小时&#xff0c;业务也停了一个多小时。 起因是&#xff1a;我的部门直系领导&#xff0c;叫我**删除一个 …...

C语言程序设计:预处理命令

预处理命令 基础知识 预处理命令简介 C语言的预处理命令是指编译之前由预处理器执行的指令&#xff0c;用于在源代码中进行一些预处理操作。 常见预处理命令 (1) #define 定义一个宏&#xff0c;用于替换源代码中的标识符为指定的文本。 #define MAX_NUM 100 int arr[MAX_NU…...

C++ 中的 struct 和 Class

通常struct用于表示一组相关的数据&#xff0c;而Class用于表示一个封装了数据和操作的对象。如果只是用于来组织一些数据&#xff0c;而不涉及复杂的封装和继承关系&#xff0c;则struct更为直观&#xff1b;如果需要封装、继承等面向对象编程的特性&#xff0c;可以选择使用C…...

基于Qt的二维码生成与识别

基于Qt的二维码生成与识别 一、获取QZxing开源库 1.通过封装的QZxing开源库生成和识别二维码&#xff0c;下载地址&#xff1a;GitCode - 开发者的代码家园https://gitcode.com/mirrors/ftylitak/qzxing/tree/master。 2.下载解压后&#xff0c;使用Qt Creator xx&#xff0…...

docker 基本命令

目录 一、docker 镜像操作命令 1.1.查询软件镜像 1.2.docker pull&#xff1a;下载镜像 1.3.docker push&#xff1a;上传镜像 1.4.docker images&#xff1a;查看本地镜像 1.5.docker inspect &#xff1a;获取镜像详细信息 1.6.docker tag&#xff1a;添加镜像标签 …...

h5键盘弹出收起时引起的页面变化

h5键盘弹出收起时引起的页面变化 键盘弹出时会导致窗口发生变化&#xff0c;置于底部的操作项会被顶上来&#xff0c;所以在键盘弹出的时候处理一下页面节点 通过监听页面窗口大小变化判断键盘状态键盘弹出时隐藏底部操作项在页面加载完成时执行即可 export function keyboa…...

Redis入门到实战教程(基础篇)笔记

教学来源&#xff1a; Redis课程介绍导学_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1cr4y1671t?p1一、Redis 入门 1.认识NoSQL 2.Redis在虚拟机中的安装和开机自启 Redis在虚拟机中安装和配置开机自启-CSDN博客https://blog.csdn.net/qq_69183322/article/deta…...

启动MySQL服务

在 Windows 系统上&#xff1a; 首先&#xff0c;找到 MySQL 安装目录&#xff0c;一般默认是在 C:\Program Files\MySQL 文件夹下。进入该目录下的 bin 文件夹。找到 mysqld.exe 文件&#xff0c;双击运行它。 在 Linux 系统&#xff08;以 CentOS 为例&#xff09;&#xff…...

Windows上构建 Chisel-Bootcamp

windows环境构建本地Chisel-Bootcamp 安装摘要Chisel-boocamp环境搭建安装java安装Anaconda安装scala 下载Chisel-bootcamp 环境Reference 安装摘要 在windows上安装chisel-boocamp&#xff0c;与linux过程类似。 安装java8安装anaconda安装scala下载Chisel-bootcamp环境 Ch…...

Spring Bean依赖注入-Spring入门(二)

1、SpringBean概述 在Spring中&#xff0c;一切Java对象都被视为Bean&#xff0c;用于实现某个具体功能。 Bean的依赖关系注入的过程&#xff0c;也称为Bean的装配过程。 Bean的装配方式有3种&#xff1a; XML配置文件注解Java类 Spring中常用的两种装配方式分别是基于XML的…...

java中spring底层核心原理解析(1)

相关系列 java中spring底层核心原理解析(2)-CSDN博客 总起 本章主要是讲以下的内容 Bean的生命周期底层原理依赖注入底层原理初始化底层原理推断构造方法底层原理 先看spring入门代码&#xff1a; ClassPathXmlApplicationContext context new ClassPathXmlApplicationCo…...

Neo-reGeorg明文流量

Neo-reGeorg 1 同IP对&#xff0c;同一个URI&#xff0c;第一个TCP流是“GET”请求&#xff0c;随后的TCP流请求为“POST”。&#xff08;jsp\jspx\php&#xff09; 2 第一个TCP流中&#xff0c;GET只有一个会话。&#xff08;jsp\jspx\php&#xff09;&#xff0c;响应body79…...

科技渔业,智慧守护:4G+北斗太阳能定位终端准确定位,防拆卸报警,夯实渔业管理水平

如何高效地管理渔船&#xff0c;有效监控禁渔区域&#xff0c;4G北斗太阳能定位终端应运而生&#xff0c;成为渔业管理的重要应用工具。 我国作为全球渔业的重要国家&#xff0c;渔业一直是沿海地区传统的支柱产业&#xff0c;对经济的繁荣和民生的稳定起着至关重要的作用。因…...

【Elasticsearch】Elasticsearch 从入门到精通(二):基础使用

《Elasticsearch 从入门到精通》共包含以下 2 2 2 篇文章&#xff1a; Elasticsearch 从入门到精通&#xff08;一&#xff09;&#xff1a;基本介绍Elasticsearch 从入门到精通&#xff08;二&#xff09;&#xff1a;基础使用 &#x1f60a; 如果您觉得这篇文章有用 ✔️ 的…...

基于DEAP数据集的四种机器学习方法的情绪分类

在机器学习领域&#xff0c;KNN&#xff08;K-Nearest Neighbors&#xff09;、SVM&#xff08;Support Vector Machine&#xff09;、决策树&#xff08;Decision Tree&#xff09;和随机森林&#xff08;Random Forest&#xff09;是常见且广泛应用的算法。 介绍 1. KNN&am…...

离散数学之一阶逻辑基本概念与等值演算思维导图+大纲笔记(期末复习,考研,学习笔记,知识点总结)

大纲笔记 基本概念 一阶逻辑命题符号化 个体词 个体常项 个体变项 个体域 个体总域 谓词 谓词常项 谓词变项 零元谓词 特性谓词 引入规则 量词 全称量词 存在量词 一阶逻辑1公式及解释 基本概念 原子公式 谓词公式 自由变元与约束变元 自由变元 换名规则 约束变元 带入规则 闭…...

nvm的下载与安装

nvm&#xff08;Node Version Manager&#xff09;是一个用于管理 Node.js 版本的工具&#xff0c;它允许您在同一台计算机上安装和切换不同的 Node.js 版本。 一、下载地址 https://github.com/coreybutler/nvm-windows/releases 二、安装nvm 三、设置环境变量 在命令提示…...

代码随想录算法训练营第五十二天|300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组

文档链接&#xff1a;https://programmercarl.com/ LeetCode300.最长递增子序列 题目链接&#xff1a;https://leetcode.cn/problems/longest-increasing-subsequence/ 思路&#xff1a;每次遇到一个新系列总是想不出来。 dp[i]&#xff1a;以nums[i]为尾的最长递增子序列的…...

SQL表连接详解:JOIN与逗号(,)的使用及其性能影响

省流版 在这个详细的解释中&#xff0c;我们将深入探讨SQL中表连接的概念&#xff0c;特别是JOIN和逗号&#xff08;,&#xff09;在连接表时的不同用法及其对查询性能的影响。通过实际示例和背后的逻辑分析&#xff0c;我们将揭示在不同场景下选择哪种连接方式更为合适。 1.…...

docker如何查看对外暴露接口

在Docker的世界里&#xff0c;了解如何查看容器对外暴露的接口是管理Docker应用的关键技能之一。暴露接口允许外部世界与容器内部的服务进行通信&#xff0c;是实现服务交互和持续集成/持续部署&#xff08;CI/CD&#xff09;管道的基础。本文将深入探讨Docker中暴露接口的机制…...

RTU遥测终端为城市排水安全保驾护航!

近年来&#xff0c;全球气候变迁与城市化进程不断加速&#xff0c;导致强降雨事件频发&#xff0c;道路低洼地带、下穿式立交桥和隧道等区域在暴雨中常易积水&#xff0c;严重阻碍了人民的出行&#xff0c;甚至危及生命与财产安全。而传统的排水管网管理方式已难以适应现代城市…...

【002_音频开发_基础篇_Linux音频架构简介】

002_音频开发_基础篇_Linux音频架构简介 文章目录 002_音频开发_基础篇_Linux音频架构简介创作背景Linux 音频架构ALSA 简介ASoC 驱动硬件架构软件架构MachinePlatformCodec ASoC 驱动 PCMALSA设备文件结构 ALSA 使用常用概念alsa-libALSA Open 流程ALSA Write 流程2种写入方法…...

C++对象的初始化和处理

生活中我们买的电子产品都基本会有出厂设置!在某一天我们不用时候也会删除一些自己信息数据保证安全。 C中的面向对象来源于生活&#xff0c;每个对象也都会有初始设置以及对象销毁前的清理数据的设置。 构造函数和析构函数 对象的初始化和清理也是两个非常重要的安全问题 一…...

【开源】使用Python+Flask+Mysql快速开发一个用户增删改查系统

项目演示 项目本身很简单&#xff0c;增删改查是几乎所有系统的骨架。正所谓万丈高楼平地起&#xff0c;学会了增删改查&#xff0c;航母就指日可待了&#xff1a;&#xff09;&#xff0c;光速入门&#xff0c;直接看演示图&#xff1a; 项目地址 https://github.com/mudf…...

线性回归模型之套索回归

概述 本案例是基于之前的岭回归的案例的。之前案例的完整代码如下&#xff1a; import numpy as np import matplotlib.pyplot as plt from sklearn.linear_model import Ridge, LinearRegression from sklearn.datasets import make_regression from sklearn.model_selectio…...

专“蜀”盛会!CGT Asia 2024 第六届亚洲细胞与基因治疗创新峰会(成都站)7月火热相邀

在细胞与基因治疗领域&#xff0c;我们正站在一个科技革命的风口上。中国的CGT市场预计将持续快速增长。根据相关分析&#xff0c;预计到2025年整体市场规模将达到25.9亿美元&#xff0c;显示出276%的复合年增长率。这一增长趋势预计将持续到2030年&#xff0c;细胞与基因治疗领…...

Pytorch入门实战 P10-使用pytorch实现车牌识别

目录 前言 一、MyDataset文件 二、完整代码&#xff1a; 三、结果展示&#xff1a; 四、添加accuracy值 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 本周的学习内容是&#xff0…...

python读取excel数据写入mysql

概述 业务中有时会需要解析excel中的数据&#xff0c;按照要求处理后&#xff0c;写入到db中&#xff1b; 用python处理这个正好简便快捷 demo 没有依赖就 pip install pymysql一下 import pymysql from pymysql.converters import escape_string from openpyxl import loa…...

Google I/O 2024:探索未来AI技术的无限可能

近日&#xff0c;Google I/O 2024大会圆满落幕&#xff0c;带给我们一场关于人工智能的盛宴。在这场大会上&#xff0c;Google推出了一系列令人激动的AI新功能和工具&#xff0c;让我们得以一窥未来的科技发展。今天&#xff0c;就让我来为大家总结一下这些亮点吧&#xff01; …...

java设计模式八 享元

享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过共享技术有效地支持大量细粒度的对象。这种模式通过存储对象的外部状态在外部&#xff0c;而将不经常变化的内部状态&#xff08;称为享元&#xff09;存储在内部&#xff0c;以此来减…...

沉钒废水回收钒

沉钒废水处理与钒回收的重要性 沉钒废水是含钒元素的特殊废水&#xff0c;钒在工业生产中广泛应用&#xff0c;但其排放造成资源浪费与环境威胁。为实现钒的有效回收&#xff0c;研究和实践了多种处理技术。 沉钒废水处理技术 1. 化学沉淀法&#xff1a;添加沉淀剂&#xff…...

【C】求Sn=a+aa+aaa+aaaa+aaaaa的前n项之和

问题 求Snaaaaaaaaaaaaaaa的前n项之和&#xff0c;其中a是一个数字。 例如&#xff1a;当a为2&#xff0c;n为5&#xff1a;Sn222222222222222 整体分析 像之前的水仙花数一样&#xff08;如果你看过这篇的话&#xff09;&#xff0c;我们可以先把这个问题拆分为一个个小的问…...

0.98T优于10米高程DEM数据

我们在《全球30米100%水陆覆盖高程》一文中&#xff0c;为大家分享了全球100%覆盖&#xff0c;且包括海底高程的30米DEM数据。 该数据虽然全球无死角覆盖&#xff0c;但分辨率只有30米。 这里&#xff0c;再为大家分享一个优于10米的高程数据&#xff0c;但目前仅覆盖全国范围…...

通信网络时钟同步(PTP网络授时服务器)技术探讨

通信网络时钟同步&#xff08;NTP网络授时服务器&#xff09;技术探讨 通信网络时钟同步&#xff08;NTP网络授时服务器&#xff09;技术探讨 1、着移动通信业务的发展和移动用户的快速增长&#xff0c; 移动网络架构向IP化、宽带化进展。为了适应业务IP化发展趋势&#xff0c…...

单片机烧录程序时“DTR的低电平复位,RTS高电平进入bootloader”有关的串口Modem联络信号

烧录程序时常见DTR和RTS引脚 参考&#xff0c;参考视频 因为常常使用的都是串口下载程序&#xff0c;常用的芯片CH340系列&#xff0c;下图中标红的引脚是MODEM联络信号&#xff0c;其中常见的DTR和RTS就是常见的串口Modem网络输出信号&#xff0c;也就是通过烧录软件控制的接…...

代码随想录-算法训练营day39【动态规划02:不同路径】

代码随想录-035期-算法训练营【博客笔记汇总表】-CSDN博客 第九章 动态规划part02● 62.不同路径 ● 63. 不同路径 II 今天开始逐渐有 dp的感觉了&#xff0c;题目不多&#xff0c;就两个 不同路径&#xff0c;可以好好研究一下详细布置 62.不同路径 本题大家掌握动态规划的方…...