我在Windows服务器上编写和托管用于内部网的web应用程序。我的服务器堆栈使用Sinatra(它使用Rack), Thin和(在某些情况下)Apache仅用于反向代理。
我想在activedirectory支持的域中支持单点登录(使用NTLM或Kerberos)。我已经看到,当我在Apache后面执行NTLM身份验证时,我可以使用mod_ntlm
或mod_auth_kerb
。我还没有试过,但我想它会起作用的。
我的问题是关于NTLM或Kerberos身份验证,当我不支持Apache,只使用Thin和Sinatra。我看过rack-ntlm,但是使用细节非常少。
请在Sinatra或Rack下提供已知的工作代码,显示如何在服务器端上使用NTLM或Kerberos,使用ActiveDirectory进行身份验证(可能通过net-ldap
)。
Edit:强调所需的答案,因为到目前为止没有答案接近于提供这个问题所要求的明确帮助。用户应该能够找到这个答案,并有一个有效的解决方案,而不是指向外部库的指针,他们必须弄清楚如何使用。
我编写了一个实现NTLM SSO的Rack::Auth
模块。这可能有点粗糙,但对我有用。它完成了NTLM所需的所有挑战/响应内容,并将REMOTE_USER
设置为浏览器提交的任何内容。
代码如下:
要使此工作,必须将浏览器设置为向服务器发送NTLM内容。在我的环境中,只有当服务器地址在可信域列表中时才会发生这种情况。对于Firefox,域名必须添加到分配给密钥network.automatic-ntlm-auth.trusted-uris
的列表中,该列表可以通过about:config
访问。
虽然我没有任何代码可以共享,也没有AD服务器进行测试,但我将发布一些一般信息,这些信息在使用rack-ntlm(这将是目前最好的路由)时可能会有所帮助。
首先要理解的是NTLM实际上从来没有给您用户密码。你不需要在应用程序中验证用户,NTLM已经完成了。rack-ntlm将为您提供一个可以使用的域+用户。
rack-ntlm对这些信息做一些额外的工作,这些工作可能对您有价值,也可能没有价值。您为它提供一个AD服务器、端口和一组凭证。它将获取该用户对象(因为没有更好的词),并通过LDAP调用在AD中查找它们。
rack-ntlm在设置中请求的凭据将是您的凭据(或者最理想的是,在具有有限查询访问权限的域中特定于应用程序的凭据)。使用该查询,您将从AD获得该用户的详细信息(组成员资格、电子邮件地址等)。您可以使用它来进一步使用用户详细信息填充数据库。
需要注意的一点是,如果你使用IE以外的任何浏览器(在某些情况下,甚至使用IE),你的用户将得到一个HTTP身份验证对话框。根据您的站点是否在"内部网"上,IE将自动通过NTLM凭据。这是根据每个浏览器控制的,所以你可能没有任何控制。在firefox中,有一个"about:config"设置,可以让你填充受信任的站点。
所以如果我们回到rack-ntlm,流看起来像这样:
- browser -> sinatra app
- (挥手挑战/响应工作
) - rack-ntlm现在通过AD查找用户LDAP
- sinatra应用程序现在有来自LDAP在一些散列
- sinatra app创建一个基本用户 存储用户名(无密码)
- sinatra设置cookie为"已登录"或无论
(因为你没有)本地数据存储中的基本功能集
如果您愿意,您可以将AD组映射到某些功能的应用程序角色,例如,域管理员自动添加到您的admin角色。
有人回答这个关于身份验证/安全的问题,他们给出了完全错误和误导性的信息,这是潜在的非常危险的。NTLM是一个两阶段的过程。您已经获得了客户端到web服务器的协商,它从客户端获取详细信息,例如声称的用户名和使用用户密码散列加密的令牌。许多人认为,只要您可以与客户机进行NTLM对话,身份验证就已经发生了。我不知道为什么人们会做出这样的假设,也许是因为NTLM握手过程相对复杂。
如果你在第一阶段结束后停止,不执行实际的身份验证,你信任客户端/用户,在这种情况下,你也可以不做任何身份验证,只是放一个消息说"如果你不被允许,请不要使用这个web应用程序"。
第二阶段是实际的身份验证。web服务器将客户端提供的详细信息(加密令牌)发送给域控制器。域控制器知道用于加密令牌的用户密码的散列,因此对密码散列执行相同的加密。如果它与客户端的值匹配,那么我们就知道客户端使用了正确的密码哈希。web服务器永远不会看到散列密码,它只会看到一个用散列密码加密的令牌作为加密密钥。
不幸的是,支持实际验证NTLM令牌所需的NETLOGON功能的LDAP库并不多,这可能是因为它是非平凡的专有垃圾。Samba(实际上是winbind)是少数几个可以做到这一点的库之一。目前还没有一个Ruby库能够进行NTLM身份验证,尽管有很多库可以让你获得客户端报告的用户名,尽管客户端可以报告任何它喜欢的用户名。
根据经验,如果您的NTLM库没有要求域控制器的详细信息,那么它就没有办法进行任何类型的身份验证。这些简单库的许多开发人员自己都不知道他们在做什么。
我使用OmniAuth在ActiveDirectory LDAP接口之外进行身份验证。文档非常好,并且很容易与Rack挂钩。
我成功地使用了您提到的Apache Kerberos模块(http://modauthkerb.sourceforge.net/)然后,它提供与基本认证相同的API,同时提供Kerberos的所有优点。您只需要使用普通的Rack::Auth::Basic,就可以了。
对于普通的机架验证,您可能可以使用https://github.com/djberg96/rack-auth-kerberos,但我没有亲自尝试过。不过,代码看起来很直接。
显然,在这两种情况下,您都必须将服务器引入AD。
我在没有机架和NTLM解决方案的情况下工作。
对于身份验证,请参阅我的回答:是否有一种方法可以使用ruby on rails读取客户端的windows登录名
授权可以通过net-ldap gem检查安全组的成员资格来完成。
这只在服务器/服务启动时运行一次,唯一的缺点是当组中的成员更改时需要重新启动服务。您当然可以将授权用户保存在数据库表中。
我的代码。
在Sinatra应用程序
require 'net-ldap'
HOST = "XXXXXX"
PORT = 389
LDAP = Net::LDAP.new(:host => HOST, :port => PORT)
# get account info somewhere safe
LDAP.auth(CONFIG.admin_user, CONFIG.admin_password)
if LDAP.bind
log "ldap logged in"
else
log "ldap login failed"
abort
end
# CONFIG.permitted_users is the name of the apps security group
$members = get_members CONFIG.permitted_users
和帮助文件
def get_ldap_username cn
treebase = "ou=xxxxxx,ou=xxxxxx,ou=xxxxxxx,ou=xxxxxx,dc=xxx,dc=xx"
filter = Net::LDAP::Filter.eq("cn", cn)
LDAP.search(:filter => filter, :base => treebase) do |item|
return item.sAMAccountName.first
end
end
def get_members name, members = []
treebase = "ou=xxxxxxx,ou=xxxxxxx,ou=xxxxxxx,ou=xxxxxx,dc=xxx,dc=xx"
filter = Net::LDAP::Filter.eq("cn", name)
LDAP.search(:filter => filter, :base => treebase) do |item|
item.each do |attribute, values|
if attribute == :member
values.each do |value|
cn = value[/CN=([^,]+),/,1]
# my groups all begin with a letter/number sequence
# recurse this method if member is a group itself
if cn[0..2].downcase == "xxx" # xxx something else of course
get_members cn, members
else
members << get_ldap_username(cn)
end
end
end
end
end
members # an array of permitted usernames
end
before do
# authentication code
# see https://stackoverflow.com/questions/5506932/is-there-a-way-to-read-a-clients-windows-login-name-using-ruby-on-rails/48407500#48407500
# authorisation
unless $members.include? @username
halt "No access"
end
end