概述

forecast+ 是一个使用VB开发的项目,用户的身份验证使用的是access数据库,将用户的信息保存在access数据库中,只有当用户位于某个group中时,才可以打开app。

现在想改成LDAP认证,即在LDAP中创建一个group,当某用户试图打开app时,会连接LDAP,并判断当前用户是否在此group中。

参考链接

Microsoft中对VB连接LDAP原理的通俗介绍:
https://docs.microsoft.com/en-us/previous-versions/technet-magazine/cc137985(v=msdn.10)

https://docs.microsoft.com/en-us/previous-versions/tn-archive/ee692830(v=technet.10)

ADSI(活动目录服务接口)介绍:
http://webcache.googleusercontent.com/search?q=cache:nMeiw23hWO4J:www.voidcn.com/article/p-erjgncal-pq.html+&cd=10&hl=en&ct=clnk&gl=us

ADSI getObject 方法介绍:
https://docs.microsoft.com/en-us/windows/desktop/adsi/binding-with-getobject-and-adsgetobject

ADSI with vb script: 使用VB操纵LDAP 基础教程讲解
https://docs.microsoft.com/en-us/windows/desktop/adsi/so-what-is-active-directory

使用VB连接LDAP详细讲解:
http://www.selfadsi.org/bind.htm

http://etutorials.org/Server+Administration/Active+directory/Part+III+Scripting+Active+Directory+with+ADSI+ADO+and+WMI/Chapter+25.+Using+ADSI+and+ADO+from+ASP+or+VB/25.4+Binding+to+Objects+Via+Authentication/

ADSI openObject方法介绍:
https://docs.microsoft.com/zh-cn/windows/desktop/api/iads/nf-iads-iadsopendsobject-opendsobject

VBS脚本常用命令:
https://webcache.googleusercontent.com/search?q=cache:Z1LZizaGJS0J:https://my.oschina.net/chengzz/blog/1439104+&cd=8&hl=en&ct=clnk&gl=us

使用Visual studio 进行vb script调试:
https://webcache.googleusercontent.com/search?q=cache:dwEsv5DU3mIJ:https://www.cnblogs.com/BeyondTechnology/archive/2011/01/10/1932437.html+&cd=6&hl=en&ct=clnk&gl=us

在access 中调试 vb: https://www.techonthenet.com/access/tutorials/vbadebug2010/debug05.php

示例代码

了解ADSI:
Active Directory Service Interfaces Active Directory Service Interfaces conform to the Component Object Model (COM) and support standard COM features.

基础代码:

On Error Resume Next
Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName
Set objUser = GetObject("LDAP://" & strUser)
WScript.Echo "First Name: " & objUser.givenName
WScript.Echo "Last Name: " & objUser.sn
WScript.Echo "Display Name: " & objUser.displayName
WScript.Echo "User Principal Name: " & objUser.userPrincipalName
WScript.Echo "SAM Account Name: " & objUser.sAMAccountName
WScript.Echo "Distinguished Name: " & objUser.distinguishedName

代码讲解:
To begin with, we create an instance of the ADSystemInfo object and then assign the value of the UserName property to a variable named strUser: Once we have the user’s distinguishedName, we can then bind to his or her user account using this line of code: Set objUser = GetObject(“LDAP://” & strUser)

  1. ADSystemInfo对象

接口:IADsADSystemInfo
对象:ADSystemInfo
用来获取登陆用户及所登陆电脑的信息。

The IADsADSystemInfo interface is implemented on the ADSystemInfo object residing in adsldp.dll, which is included with the standard installation of ADSI on Windows 2000. You must explicitly create an instance of the ADSystemInfo object in order to call the methods on the IADsADSystemInfo interface.

在VB中实现如下: You can use the New operator in Visual Basic.

Dim adSys as New ADSystemInfo 

Or you can call the CreateObject function in a scripting environment, supplying “ADSystemInfo” as the ProgID.

Dim adSys
Set adSys = CreateObject("ADSystemInfo")

The following Visual Basic code example retrieves the Windows system information.

Dim sys As New ADSystemInfo
Debug.print "User: " & sys.UserName
Debug.print "Computer: " & sys.ComputerName
Debug.print "Domain: " & sys.DomainDNSName
Debug.print "PDC Role Owner: " & sys.PDCRoleOwner

ADSystemInfo对象可以提供很多信息,我们只关心UserName属性,因为
it just happens to correspond to the distinguishedName property。distinguishedName (for example, CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com) enables us to uniquely identify a user account in Active Directory. In turn, that enables us to bind to that user account. And once we’ve made that connection, we can retrieve any information we want about a user—including who he is.

  1. GetObject函数 用来连接目录服务,并获取目录中用户信息。

The GetObject and ADsGetObject functions are used to bind to directory service objects with no authentication. The application is not required to provide credentials when accessing directory service data. ADSI uses the security context of the calling thread.
However, if secure authentication fails, ADSI attempts to perform a simple bind with a null user name and null password. If the simple bind succeeds, the user context for the binding is Guest. A simple bind uses plaintext authentication. Because no user name or password is transmitted over the network, this is not a security issue.
The GetObject function is used to bind to directory service objects in languages that support automation, such as Visual Basic. The GetObject function requires a moniker string. In ADSI, the binding string is the moniker string.

Vb实例代码:
Dim myUser as IADs
Set myUser = GetObject("LDAP://CN=jeffsmith,DC=fabrikam,DC=com")

注意两点:

  1. LDAP中的“组”概念:
    LDAP中使用Group(组)和Role(角色)来动态的管理节点之间的关系。

例如,建立一个“管理员”的组或角色,然后可以将不同组 织结构下的人员放到“管理员”这个逻辑结构下。那么通过“管理员”这个虚拟的组织结构可以将不同物理组织下的人员动态的组合在一起。

例如给“管理员”赋予特殊的权力,使他们能够修改本机构中的人员信息。过了一段时间,如果需要将“管理员”这个逻辑组织取消或者改动,只需要在LDAP的单个节点上操作,而实际“管理员”逻辑结构的成员们,不需要作任何的变化,原来是在哪个物理组织,现在也不用变化。

Group(组)和Role(角色)的功能基本相同,操作起来也很类似。那么Group(组)和Role(角色)到底有什么区别呢?
事实上,由于实现方法不同,Group(组)和Role(角色)在某些操作上体现出不同的性能。
Group(组)的实现机制很简单,每个Group本身就是LDAP的一个实体。这个实体有个member的属性。每次将一个成员添加到这个 Group中就会在Member属性中添加一条记录(成员的DN)。如果这个Group有200个成员,那么它的member属性中会有200条记录。
这种实现方式非常适合下面的操作:
1.列举当前Group下的所有成员。
2.将一些成员从当前的Group中删除或添加。
因为所有的成员都记录在Group实体的属性里;这种实现方式非常不适合下面的操作:
查找某个用户所属的所有Group。这个操作可能要去每个Group中查找信息。

Role(角色)的实现机制就比较复杂了。其实每个LDAP实体本身都有一个“nsrole”的属性。如果某个角色赋予这个用户的话,就会在这个用户的“nsrole”的属性中添加一个角色的记录。如果当前用户有10个角色(例如,他又是领导,又是员 工,又是管理员,又是教授….),那么在这个用户的”nsrole”属性中会有10条记录(Role的DN)。
这种实现方式非常适合下面的操作:
列举当前用户拥有的所有的角色,
因为在用户的“nsrole”属性中记录了所有的角色,因此给一个用户授权通常使用角色,这是因为从用户信息中就能很快获 得当前用户所有的角色,以及相关的权限。

具体实现

之前的逻辑:
首先判断user是在当前DAO的workspace中的,
然后再遍历此用户的所有groups,看一看里面是否存在appUsers组。

代码中的.Users, User.Groups都是使用的内置的DAO对象。

DAO: date access object
数据访问对象,是一种应用程序编程接口(API),存在于微软的Visual Basic中,它允许程序员请求对微软的Access数据库的访问。DAO是微软的第一个面向对象的数据库接口。DAO对象封闭了Access的Jet函数。通过Jet函数,它还可以访问其他的结构化查询语言(SQL)数据库。它允许 Visual Basic 开发者通过 ODBC 像直接连接到其他数据库一样,直接连接到 Access 表。DAO 最适用于单系统应用程序或小范围本地分布使用。

DAO 参考链接

https://msdn.microsoft.com/en-us/library/aa266932(v=vs.60).aspx

https://books.google.com/books?id=FYAqAAAAQBAJ&pg=PA66&lpg=PA66&dq=Data+Access+Object%E4%B8%AD%E7%9A%84Users&source=bl&ots=6LCshAoMWr&sig=XRwFbAhwh9YG2jIC_iRjJGNTPzY&hl=en&sa=X&ved=2ahUKEwi8o_2Zt6PeAhUnxoUKHbV_AeQQ6AEwBnoECAEQAQ#v=onepage&q=Data%20Access%20Object%E4%B8%AD%E7%9A%84Users&f=false

http://ptgmedia.pearsoncmg.com/images/0672321025/downloads/appendix_c.pdf

LADP中如何判断某个用户是否在某个group中:

第一种,获取组的成员信息与目标用户做比较;

首先从LDAP中获取 appUsers组;
然后获取group所有的成员,将当前用户和所有的成员进行比较,如果存在其中就位于此组中。

问题:

第一,默认的端口是389,如果不制定就会使用这个端口,而我们使用的是636,因此需要指定端口,必须为 LDAP://corpldap.xx.com:636

第二,在使用getObject方法时,默认使用的是当前用户的账户,当前账户如果没有权限,就会获取到empty,若要指定用户,需用openObject方法。 http://www.selfadsi.org/bind.htm
At this, the ID of the user that runs the script is used automatically for authentication. This method can only be used for accesses within the own forest, if possessing sufficient permissions.

第二种,根据组信息搜索此用户,有结果就是在此组中;

https://stackoverflow.com/questions/1032351/how-to-write-ldap-query-to-test-if-user-is-member-of-a-group 但是这里用到一个 memberOf 的属性,这个属性在LDAP中并没有看到,在打印的时候也没有值,所以只有LDAP中定义了这个memberOf属性时才能够使用这种方法。
比较常用的还是上一种。

实现代码

Public Function InGroup(GroupName As String) As Boolean
Dim ActualUsr, ActualGrp As String
Dim adsOUPath, strUsername, strPassword As String
Set wks = DBEngine.Workspaces(0)
Dim objNamespace
Dim objGroup

adsOUPath = "LDAP://corpldap.xx.com:636/CN=ssc_gct_authors,OU=groups,OU=Users,DC=xx,DC=com"
strUsername = "uid=userxxx,ou=system,OU=Users,DC=xx,DC=com"
strPassword = "pwdxxx"

InGroup = False
  
With wks
  '1st step: connect to LDAP server
  Set objNamespace = GetObject("LDAP:")
  Set objGroup = objNamespace.OpenDSObject(adsOUPath, strUsername, strPassword, 0)

  '2nd step: check if current user in group
  For Each strUser In objGroup.uniqueMember
      If InStr(strUser, UserName) > 0 Then
        InGroup = True 
        Exit Function
      End If
  Next strUser
        
End With
End Function

调试VB:
打断点; 点击运行,step into
找到start up,点击运行,即运行到断点位置