将单点登录应用于您的应用程序






3.13/5 (21投票s)
2003年9月19日
8分钟阅读

129897
阐述了将单点登录(SSO)集成到应用程序中的优点和缺点,以及通过 VB.NET 和 LDAP 实现 SSO 的代码实现。
简述
在这个不断发展的技术世界中,应用程序日益复杂,但随着企业用户希望更快地将产品推向市场并高度重视易用性,规模经济效应却并未随之增长。
分布式计算的概念已经存在了近十年。然而,分布式计算主要局限于组织内部,并且最重要的是,局限于特定供应商的平台。
近年来,专业机构联手各大平台供应商,共同制定了广泛认可的标准。突然之间,平台不再是通信的主要障碍。当前带宽供应的激增,加上利用带宽成本的降低,以及其他原因,也导致了局域网(LAN)和广域网(WAN)之间的界限模糊。我们现在正见证着一项技术演进,一项单一应用程序可以跨越多个平台,并超越物理障碍、参数和边界。
虽然我不想详细阐述上述内容,但本文的主题将触及应用程序开发的一个方面,我认为这个方面非常适合通过分布式计算来解决,那就是身份管理。
如今的企业经营日益困难和复杂,并且业务范围还扩展到其他商业领域。软件供应商发现难以满足需求,因为经营方式一直在变化。构建一个完整的企业资源规划套件,如果没有第三方工具和支持的参与,将会非常困难。之后,企业将要求部署的系统能够与其他现有或新的应用程序无缝协作。其中一个问题是应用程序的入口点——登录/登出界面。如果企业要为用户部署一个门户网站,让他们选择并使用所需的任何应用程序,那么问题就来了:谁掌握着用户身份?如果应用程序 A 掌握着用户数据库的主要密钥,那么应用程序 B 和 C 如何与之无缝通信?当然,通过广泛采用的 Web 服务和 XML,下一个问题将是应用程序 A、B 和 C 是否在应用程序架构中以松耦合的方式实现了身份管理?如果是这样,谁应该持有密钥,数据库中应该存放什么信息,最重要的是,谁来管理它?
理想
我在此推荐的一种方法是使用组织目录。目录与数据库的一个关键区别在于数据的使用频率以及数据的变化速率。目录非常类似于电话黄页。您不会每时每刻都查阅它(使用频率),它一年只发布一次(变化速率)。所有基本的用户身份信息都存储在公司 IT 基础设施内的目录中。这可以是 IBM 或 Unix 大型机、Novell 服务器或 Windows Active Directory。需要的是一种查询和查找这些目录索引的方法来找到用户并进行身份验证。这通过一种通用、广泛采用且被普遍接受的标准来实现,称为轻量级目录访问协议(LDAP)。就像结构化查询语言(SQL)用于查询数据库一样,LDAP 用于查询目录以返回特定信息。
在很多方面,让应用程序存储一份它所在的平台已有的用户数据的副本是没有意义的。LDAP 目录由系统所有者负责管理,他/她还将为每个应用程序所需的不同角色分配正确的权限。每个用户只需在登录时通过其用户凭据进行一次身份验证。从那时起,用户会话就映射到其用户凭据、策略和授权。
启动应用程序将涉及检索其当前用户会话详细信息,然后使用这些信息查询 LDAP 目录以获取其用户角色和权限。这一切对用户来说都是透明的,一旦用户通过 LDAP 查询获得身份验证和授权,他/她将能够访问应用程序的全部或部分内容,具体取决于用户的角色。一旦发生这种情况,就实现了单点登录。
批评者
当然,单点登录在身份管理方面的概念也有其批评者。最主要的缺陷是,一旦用户在入口点登录,只要他们还在公司的域内,他们就一直处于登录状态。理论上讲,如果他们不注销自己的机器,会话将始终处于身份验证状态,无论之后谁使用同一个会话。换句话说,虽然用户身份未被泄露,但当前用户会话可能会被劫持。当用户离开未锁定的机器,而另一个人接管计算机和会话时,这很容易发生。单点登录的主要标准是用户只需要登录一次(顾名思义),因此不应该有注销按钮。拥有它没有意义,因为经过身份验证的是会话,而不是用户。用户只是用于创建一个有效的会话。如果注销后出现登录,那么单点登录的规则就被打破了。
另一个缺陷是用户跨域登录时的身份验证。当用户通过 Internet 接口使用位于公司域中的同一个应用程序时,这很可能会发生。用户可能在家或其他地方使用他们自己的个人域,或者使用他人的机器,那么他们自己的个人用户凭据将与存储在组织域中的凭据不同。这将需要用户进行多次登录,这违背了单点登录的理念。
其中一些缺陷可以通过用户教育来解决。教育用户在离开机器时保护好自己的设备,通过密码保护的屏幕保护程序或其他类似方式锁定会话。至于第二个缺陷,在安全且被接受的隧道协议能够跟随用户无论他们去何处之前,这个理想可能还很遥远。
优点
然而,好处是巨大的。用户权限、用户和公司策略都集中在组织目录中的一个地方。用户只需要维护一个身份(用户 ID 和密码)。企业主可以安心地知道,所有应用程序和数据的入口点都位于一个安全的 LDAP 目录中,并遵循公司的用户策略和密码策略。这应该由企业所有者来决定,而不是由软件供应商来决定。不同的应用程序都将查询一个单一的用户目录进行身份验证,如果它们需要更多应用程序特定的数据和字段,则会将用户重定向到它们那里收集更具体的信息。其他信息的存储最终将映射到 LDAP 目录中的用户通用名。
解决方案
因此,目前最好的方法是结合这两种场景,取其优缺点。应用程序架构应采用一个位于组织 LDAP 目录中的单一用户存储库,但允许用户使用相同的用户凭据再次登录以增加安全性。这可能会解决当前的问题。理想的解决方案仍然可以而且应该实现。想象一下,您的客户端电子邮件应用程序每次发送或接收电子邮件时都要求您输入密码。
Microsoft .NET 通过在配置文件中进行一些命令切换,使得这两种方法都变得容易。因此,部署哪种解决方案取决于客户需求,所需工作量不大。
我将通过一些 VB.NET 代码来解释这两种方法如何实现。
场景 A:纯粹的单点登录
- 在应用程序启动时,查询 LDAP 并检查用户是否属于访问应用程序的正确用户组。当然,我假设组分配给应用程序,而不是个人。
Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim p As WindowsPrincipal = Thread.CurrentPrincipal Dim a As New LDAPQuery Dim objCook As New HttpCookie("LDAPCookie") 'Check for authentication and write into Cookie Dim sLDAPCN As String = (a.GetCNByLDAP(p.Identity.Name)) 'Check for LDAP Authentication First If sLDAPCN <> "" And _ (a.blnFindCNinGroups(a.GetCNByLDAP(p.Identity.Name), _ "AppAUserGroups")) Then objCook.Value = (sLDAPCN) Response.Cookies.Add(objCook) 'Check for LDAP User Existence in Application Database 'If User exists in Application DB, Start Application If (FindLDAPCNinDB(sLDAPCN)) Then 'DROP Authentication Cookie token 'Compute a hashed value of the current session ID 'into a cookie to signal authentication success 'Be sure to check for this cookie for every page load 'if value doesnt match or cookie doesnt exists, 'Re-do the whole authentication process again. 'Single-sign-on should continue, the cookies drop 'prevent authenticated LDAP Users from not having 'their CN mapped with other application-specific 'fields or data. Response.Redirect("Welcome.aspx") Else 'If False, do Database mapping Response.Redirect("CollectOtherInfo.aspx") End If Else Response.Redirect("LoginError.htm") End If End Sub
Private Function FindLDAPCNinDB(ByVal sLDAPCN As String) As Boolean 'For ease of setup, I am using a Flat XML File as a database 'However, a proper way would be to use a relational database 'You may use the ADO.NET to query and get information from the databse Dim xmldoc As New XmlDocument Dim xmlnode As XmlNode Dim xmlnodelist As XmlNodeList xmldoc.Load("D:\WebDeploy\LDAPQuery\FlatDB.xml") xmlnodelist = xmldoc.SelectNodes("/Users/User") For Each xmlnode In xmlnodelist 'Find LDAPCN If xmlnode.ChildNodes.Item(0).InnerText = sLDAPCN Then Return True End If Next Return False End Function '########### '########### Imports System.DirectoryServices Public Class LDAPQuery 'This function returns the Common Name (CN) 'of the Login information of the 'LDAP Directory. The CN is an unique identity 'and cannot be changed or edited in LDAP Public Function GetCNByLDAP(ByVal strLogin As String) As String Dim str As String = "" 'Parse the string to check if domain name is present. Dim idx As Integer = strLogin.IndexOf("\") If (idx = -1) Then idx = strLogin.IndexOf("@") End If Dim strDomain As String Dim strName As String If (idx <> -1) Then strDomain = strLogin.Substring(0, idx) strName = strLogin.Substring(idx + 1) Else strDomain = Environment.MachineName strName = strLogin End If Dim obDirEntry As DirectoryEntry = Nothing Try Dim strPath As String = "LDAP://DC=Softwaremaker,DC=net" obDirEntry = New DirectoryEntry(strPath) Dim rootSearch As New DirectorySearcher(obDirEntry) Dim SearchResult As SearchResult Dim spn As String = strName & "@" & strDomain rootSearch.Filter = _ ("(&(objectCategory=user)(userPrincipalName=" & spn & "))") For Each SearchResult In rootSearch.FindAll 'Or FindOne Dim i As Integer 'Check here - Should only return ONE result For i = 0 To SearchResult.Properties("cn").Count - 1 str += SearchResult.Properties("cn")(i) Next Next Catch ex As Exception str = ex.Message End Try Return str End Function
'Function finds and returns if User if in the specified user group Public Function blnFindCNinGroups(ByVal sLogonUserCN _ As String, ByVal sGroup As String) As Boolean Try Dim sDirEnt As String = "LDAP://server/CN= " & _ sLogonUserCN&",CN=Users,DC=Softwaremaker,DC=net" Dim user As DirectoryEntry = New DirectoryEntry(sDirEnt) Dim pcoll As PropertyCollection = user.Properties Dim i As Integer Dim s As String 'The loop will return all Groups of which the CN is a member Of For i = 0 To pcoll("memberOf").Count - 1 s = pcoll("memberOf")(i).ToString If QueryLDAP(s, sGroup) = True Then Return True Next Return False Catch ex As Exception Return False End Try End Function
Public Function QueryLDAP(ByVal strQuery _ As String, ByVal sGroup As String) As Boolean Dim obDirEntry As DirectoryEntry = Nothing Try Dim strPath As String = "LDAP://" & strQuery Dim s As String obDirEntry = New DirectoryEntry(strPath) Dim rootSearch As New DirectorySearcher(obDirEntry) Dim SearchResult As SearchResult For Each SearchResult In rootSearch.FindAll Dim i As Integer 'Check here - Should only return ONE result For i = 0 To SearchResult.Properties("cn").Count - 1 s += SearchResult.Properties("cn")(i) Next If s = sGroup Then Return True Next Catch ex As Exception Return False End Try End Function End Class '###########
- 我不是 LDAP 查询的专家或行家。我确定我为了查询用户是否在 LDAP 目录中的指定用户组内而采取的步骤过多。但我是在推销理念,而不是代码,所以你应该明白我的意思 :)
- 可以应用的方面包括:
- 在 IIS 设置中使用 Windows 身份验证。
- 在 web.config 文件级别
'########### <identity impersonate="true" /> <authentication mode="Windows" /> <authorization> <allow roles="SoftwaremakerNet\AppAUserGroup" /> </authorization> '###########
场景 B:混合表单身份验证与 LDAP
请参阅此 Microsoft 链接 获取详细的工作版本。
结论
上述示例仅展示了 Microsoft .NET 平台上的代码实现。您也可以使用 J2EE 来实现相同的想法。只要您的应用程序具备 LDAP 功能,它就能正常工作。
我希望您能看到如何将组织目录的用户身份管理集成到您的应用程序中,并将其映射到您自己的数据库。用户仍然会存在于数据库中,但他们的身份管理应该集中在一个实现公司策略和安全性的 LDAP 目录中。有几种方法可以将纯粹的单点登录应用于多个域。然而,这些方法采用了相对较新且昂贵的技术,例如用作您指纹的物理身份验证令牌。我将把这个话题留待以后进一步讨论。