Ruby/Rack中的单点登录服务器认证

时间:2021-09-13 03:01:49

I write and host web applications on Windows servers for intranet usage. My server stack uses Sinatra (which uses Rack), Thin, and (in some cases) Apache for reverse-proxying only.

我在Windows服务器上编写和托管web应用程序以供内部网使用。我的服务器堆栈使用Sinatra(使用机架)、Thin和Apache(在某些情况下)来进行反向代理。

I want to support Single Sign-on (using NTLM or Kerberos) within our ActiveDirectory-backed domain. I have seen that I can use mod_ntlm or mod_auth_kerb when I'm behind Apache to perform my NTLM authentication. I haven't tried this yet, but I assume it will work.

我希望在activedirectory支持的域中支持单点登录(使用NTLM或Kerberos)。在Apache之后,我可以使用mod_ntlm或mod_auth_kerb执行NTLM身份验证。我还没试过,但我认为它会起作用。

My question is about NTLM or Kerberos authentication when I'm not behind Apache, using only Thin and Sinatra. I've seen rack-ntlm, but the usage details there are exceedingly sparse.

我的问题是关于NTLM或Kerberos身份验证,当我不支持Apache时,只使用Thin和Sinatra。我见过rack-ntlm,但是这里的使用细节非常稀少。

Please provide known-working code under Sinatra or Rack that shows how to use NTLM or Kerberos on the server-side, authenticating with ActiveDirectory (presumably via net-ldap).

请在Sinatra或机架下提供已知的工作代码,显示如何在服务器端使用NTLM或Kerberos,并使用ActiveDirectory(大概是通过net-ldap)进行身份验证。

Edit: Emphasized the desired answers, as no answers so far come close to providing the explicit help this question is asking for. Users should be able to find this answer and have a working solution, not pointers to external libraries that they must figure out how to use.

编辑:强调想要的答案,因为到目前为止还没有任何答案能提供这个问题所要求的明确帮助。用户应该能够找到这个答案并有一个有效的解决方案,而不是指向外部库的指针,而这些库是用户必须找到的。

6 个解决方案

#1


9  

I wrote a Rack::Auth module that implements NTLM SSO. It's maybe a little rough but it works for me. It does all that challenge/response stuff that's required for NTLM and sets REMOTE_USER to whatever the browser submitted.

我编写了一个Rack: Auth模块,它实现了NTLM SSO。这可能有点粗糙,但对我来说行得通。它完成了NTLM所需的所有挑战/响应工作,并将REMOTE_USER设置为浏览器提交的任何内容。

Here's the code.

这里的代码。

To make this work, the browser must be set up to send NTLM stuff to the server. In my environment this only happened when the server address was in the list of trusted domains. For Firefox, the domain has to be added to the list assigned to the key network.automatic-ntlm-auth.trusted-uris that can be accessed via about:config.

要使此工作,必须设置浏览器以向服务器发送NTLM内容。在我的环境中,这只发生在服务器地址位于受信任域列表中时。对于Firefox,域必须添加到分配给key network.automatic-ntlm-auth的列表中。托管uri可以通过about:config访问。

#2


7  

While I don't have any code to share and don't have an AD server to test against, I'll post some general information that others might find helpful when using rack-ntlm (which would be the best route at this point).

虽然我没有任何代码可以共享,也没有可以测试的广告服务器,但是我将发布一些在使用rack-ntlm时其他人可能会觉得有用的一般信息(这是目前最好的方法)。

First thing to understand is that NTLM never actually gives you the user password. You don't NEED to authenticate the user inside your app. NTLM has already done that. What rack-ntlm will give you is a domain + user that you can then work with.

首先要理解的是NTLM从来没有给你用户密码。您不需要对应用程序中的用户进行身份验证。NTLM已经这样做了。rack-ntlm将为您提供一个域+用户,然后您可以使用它。

rack-ntlm does some additional work with that information that may or may not be valuable to you. You provide it with an AD server, port and a set of credentials. It will the take that user object (for lack of a better word) and look them up in AD via an LDAP call.

rack-ntlm对这些信息做一些附加工作,这些信息可能对您有价值,也可能对您没有价值。您向它提供一个AD服务器、端口和一组凭据。它将获取用户对象(因为没有更好的词)并通过LDAP调用在AD中查找它们。

The credentials that rack-ntlm is asking for in settings would be YOUR credentials (or optimally, application-specific credentials in the domain that have limited query access). With that query, you would get back the details of that user from AD (group membership, email addresses, whatever). You can use that to further populate your database with user details.

rack-ntlm在设置中要求的凭据将是您的凭据(或在具有有限查询访问的领域中最优的特定于应用程序的凭据)。有了这个查询,您就可以从AD(组成员、电子邮件地址等)中获取该用户的详细信息。您可以使用它来使用用户细节进一步填充数据库。

One thing to note is that if you're using any browser OTHER than IE (and in some cases, even with IE), your users will get an HTTP authentication dialog. Depending on if your site is on the "intranet" or not, IE will passthrough the NTLM credentials automatically. This is controlled on per-browser basis so you may not have any control. In firefox, there's an "about:config" setting that will let you populate trusted sites.

需要注意的是,如果您使用的是除IE之外的任何浏览器(在某些情况下,甚至是IE),您的用户将获得一个HTTP身份验证对话框。根据您的站点是否在“内部网”上,IE将自动通过NTLM凭据。这是基于每个浏览器的控制,所以您可能没有任何控制。在firefox中,有一个“about:config”设置,可以让您填充受信任的站点。

So if we're going back to rack-ntlm, the flow would look something like this:

如果我们回到rack-ntlm,流看起来是这样的

  • browser -> sinatra app
  • 浏览器- > sinatra程序
  • (handwaving challenge/response work
    here)
  • (挥臂挑战/响应在这里工作)
  • rack-ntlm now looks up user in AD via LDAP
  • rack-ntlm现在通过LDAP查找AD中的用户
  • sinatra app now has user details from LDAP in some hash
  • sinatra应用现在有一些来自LDAP的用户细节
  • sinatra app creates a base user
  • sinatra应用程序创建一个基本用户。
  • store username (no password
    because you don't HAVE it) with some basic set of abilities in local datastore
  • 在本地数据存储中存储具有一些基本功能的用户名(因为没有密码)
  • sinatra sets cookie to "logged-in" or whatever
  • sinatra将cookie设置为“登录”或其他。

If you wanted, you could map AD groups to application roles in some capacity so that, say, domain admins automatically were added to your admin role.

如果需要,可以将广告组映射到应用程序角色,例如,将域管理员自动添加到管理角色中。

#3


2  

There are people responding to this question on authentication/security who are giving completely false and misleading information, which is potentially very dangerous. NTLM is a two-phase process. You've got the client-to-web-server negotiation, which gets details from the client such as the purported username and a token which is encrypted with a hash of the users password. A lot of people think as long as you can talk NTLM with the client, somehow authentication has happened. I have no idea why people make this assumption, maybe because the NTLM hand-shake process is relatively convoluted.

有一些人对这个问题作出了回应,他们提供了完全错误和误导性的信息,这可能是非常危险的。NTLM是一个两阶段的过程。您已经获得了从客户端到web服务器的协商,该协商从客户端获取详细信息,比如所谓的用户名和使用用户密码散列加密的令牌。许多人认为,只要能与客户端进行NTLM对话,身份验证就会以某种方式发生。我不知道为什么人们会做出这样的假设,可能是因为NTLM握手过程相对复杂。

If you stop after the first phase and don't perform the actual authentication, you're trusting the client/user, in which case you may as well not do any authentication and just put a message saying "Please don't use this web application if you're not allowed to".

如果您在第一个阶段之后停止,并没有执行实际的身份验证,那么您将信任客户端/用户,在这种情况下,您可能不会进行任何身份验证,并只会发出一条消息:“如果您不允许,请不要使用这个web应用程序”。

The second phase is the actual authentication. The web server sends the details provided by the client (the encrypted token) to the domain controller. The domain controller knows the hash of the users password used to encrypt the token, and so performs the same encryption of the password hash. If it matches the value of the client, then we know the client used the correct password hash. The web server never sees the hashed password, it only sees a token that was encrypted with the hashed password as the encryption key.

第二阶段是实际的身份验证。web服务器将客户端提供的详细信息(加密的令牌)发送到域控制器。域控制器知道用于加密令牌的用户密码的哈希,因此执行密码哈希的相同加密。如果它匹配客户端的值,那么我们知道客户端使用了正确的密码散列。web服务器永远不会看到散列密码,它只看到一个用散列密码加密的令牌作为加密密钥。

Unfortunately, there are not many LDAP libraries that support the NETLOGON capabilities required to actually authenticate an NTLM token, probably because it's non-trivial proprietary crap. Samba (well actually winbind) is one of only a small handful of libraries that can do it. There isn't currently a Ruby library capable of NTLM authentication, though there's plenty of libraries that'll get you the username reported by the client, though the client can report any username it likes.

不幸的是,并没有很多LDAP库支持NETLOGON功能,它们实际上需要对NTLM令牌进行身份验证,这可能是因为它不是微不足道的专有垃圾。Samba(实际上是winbind)是仅有的几个能够做到这一点的库之一。目前还没有能够进行NTLM身份验证的Ruby库,尽管有很多库可以让您获得客户端报告的用户名,但是客户端可以报告它喜欢的任何用户名。

As a rule of thumb, if you're NTLM library isn't asking for details of your domain controller, then there's no way it's doing any kind of authentication. A lot of the developers of these simple libraries themselves have no idea what they're doing.

根据经验,如果NTLM库没有要求域控制器的详细信息,那么它不可能进行任何类型的身份验证。这些简单库的许多开发人员自己都不知道自己在做什么。

#4


1  

I use OmniAuth to do authentication off of an ActiveDirectory LDAP interface. Documentation is pretty good and it hooks easily into Rack.

我使用OmniAuth对ActiveDirectory LDAP接口进行身份验证。文档很好,很容易挂在架子上。

#5


1  

I successfully used the Apache Kerberos module that you mentioned (http://modauthkerb.sourceforge.net/) It then presents the same API as would basic auth, while providing all the goodies of Kerberos. You'll just have to use a plain Rack::Auth::Basic, and that's it.

我成功地使用了您提到的Apache Kerberos模块(http://modauthkerb.sourceforge.net/),然后它提供与基本auth相同的API,同时提供Kerberos的所有优点。你只需要使用一个普通的架子::Auth::Basic,就这样。

For plain Rack auth, you could probably use https://github.com/djberg96/rack-auth-kerberos, but I haven't personally tried it. The code looks straight forward, though.

对于plain Rack auth,您可能可以使用https://github.com/djberg96/rack-auth-kerberos,但我还没有亲自尝试过。不过,代码看起来很直接。

Obviously in both cases you'll have to introduce your server to AD.

显然,在这两种情况下,都需要向AD介绍服务器。

#6


0  

I have this working without the rack and NTLM solutions.

我有这个工作没有机架和NTLM解决方案。

For authentication see my answer here: is there a way to read a clients windows login name using ruby on rails

要进行身份验证,请参阅我的答案:是否有一种方法可以使用ruby on rails读取客户端windows登录名

Authorisation can then be done through the net-ldap gem by checking membership of security groups.

然后,通过检查安全组的成员身份,可以通过net-ldap gem完成授权。

This runs only once when the server/service starts, only downside to this is you need to restart the service when the members in the group change. You could of course keep the authorised users in a database table.

这只在服务器/服务启动时运行一次,惟一的缺点是,当组中的成员发生更改时,您需要重新启动服务。当然,您可以将授权用户保存在数据库表中。

Here my code.

我的代码。

In the Sinatra app

辛纳屈的应用

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

and in the helper file

在帮助文件中

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://*.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

#1


9  

I wrote a Rack::Auth module that implements NTLM SSO. It's maybe a little rough but it works for me. It does all that challenge/response stuff that's required for NTLM and sets REMOTE_USER to whatever the browser submitted.

我编写了一个Rack: Auth模块,它实现了NTLM SSO。这可能有点粗糙,但对我来说行得通。它完成了NTLM所需的所有挑战/响应工作,并将REMOTE_USER设置为浏览器提交的任何内容。

Here's the code.

这里的代码。

To make this work, the browser must be set up to send NTLM stuff to the server. In my environment this only happened when the server address was in the list of trusted domains. For Firefox, the domain has to be added to the list assigned to the key network.automatic-ntlm-auth.trusted-uris that can be accessed via about:config.

要使此工作,必须设置浏览器以向服务器发送NTLM内容。在我的环境中,这只发生在服务器地址位于受信任域列表中时。对于Firefox,域必须添加到分配给key network.automatic-ntlm-auth的列表中。托管uri可以通过about:config访问。

#2


7  

While I don't have any code to share and don't have an AD server to test against, I'll post some general information that others might find helpful when using rack-ntlm (which would be the best route at this point).

虽然我没有任何代码可以共享,也没有可以测试的广告服务器,但是我将发布一些在使用rack-ntlm时其他人可能会觉得有用的一般信息(这是目前最好的方法)。

First thing to understand is that NTLM never actually gives you the user password. You don't NEED to authenticate the user inside your app. NTLM has already done that. What rack-ntlm will give you is a domain + user that you can then work with.

首先要理解的是NTLM从来没有给你用户密码。您不需要对应用程序中的用户进行身份验证。NTLM已经这样做了。rack-ntlm将为您提供一个域+用户,然后您可以使用它。

rack-ntlm does some additional work with that information that may or may not be valuable to you. You provide it with an AD server, port and a set of credentials. It will the take that user object (for lack of a better word) and look them up in AD via an LDAP call.

rack-ntlm对这些信息做一些附加工作,这些信息可能对您有价值,也可能对您没有价值。您向它提供一个AD服务器、端口和一组凭据。它将获取用户对象(因为没有更好的词)并通过LDAP调用在AD中查找它们。

The credentials that rack-ntlm is asking for in settings would be YOUR credentials (or optimally, application-specific credentials in the domain that have limited query access). With that query, you would get back the details of that user from AD (group membership, email addresses, whatever). You can use that to further populate your database with user details.

rack-ntlm在设置中要求的凭据将是您的凭据(或在具有有限查询访问的领域中最优的特定于应用程序的凭据)。有了这个查询,您就可以从AD(组成员、电子邮件地址等)中获取该用户的详细信息。您可以使用它来使用用户细节进一步填充数据库。

One thing to note is that if you're using any browser OTHER than IE (and in some cases, even with IE), your users will get an HTTP authentication dialog. Depending on if your site is on the "intranet" or not, IE will passthrough the NTLM credentials automatically. This is controlled on per-browser basis so you may not have any control. In firefox, there's an "about:config" setting that will let you populate trusted sites.

需要注意的是,如果您使用的是除IE之外的任何浏览器(在某些情况下,甚至是IE),您的用户将获得一个HTTP身份验证对话框。根据您的站点是否在“内部网”上,IE将自动通过NTLM凭据。这是基于每个浏览器的控制,所以您可能没有任何控制。在firefox中,有一个“about:config”设置,可以让您填充受信任的站点。

So if we're going back to rack-ntlm, the flow would look something like this:

如果我们回到rack-ntlm,流看起来是这样的

  • browser -> sinatra app
  • 浏览器- > sinatra程序
  • (handwaving challenge/response work
    here)
  • (挥臂挑战/响应在这里工作)
  • rack-ntlm now looks up user in AD via LDAP
  • rack-ntlm现在通过LDAP查找AD中的用户
  • sinatra app now has user details from LDAP in some hash
  • sinatra应用现在有一些来自LDAP的用户细节
  • sinatra app creates a base user
  • sinatra应用程序创建一个基本用户。
  • store username (no password
    because you don't HAVE it) with some basic set of abilities in local datastore
  • 在本地数据存储中存储具有一些基本功能的用户名(因为没有密码)
  • sinatra sets cookie to "logged-in" or whatever
  • sinatra将cookie设置为“登录”或其他。

If you wanted, you could map AD groups to application roles in some capacity so that, say, domain admins automatically were added to your admin role.

如果需要,可以将广告组映射到应用程序角色,例如,将域管理员自动添加到管理角色中。

#3


2  

There are people responding to this question on authentication/security who are giving completely false and misleading information, which is potentially very dangerous. NTLM is a two-phase process. You've got the client-to-web-server negotiation, which gets details from the client such as the purported username and a token which is encrypted with a hash of the users password. A lot of people think as long as you can talk NTLM with the client, somehow authentication has happened. I have no idea why people make this assumption, maybe because the NTLM hand-shake process is relatively convoluted.

有一些人对这个问题作出了回应,他们提供了完全错误和误导性的信息,这可能是非常危险的。NTLM是一个两阶段的过程。您已经获得了从客户端到web服务器的协商,该协商从客户端获取详细信息,比如所谓的用户名和使用用户密码散列加密的令牌。许多人认为,只要能与客户端进行NTLM对话,身份验证就会以某种方式发生。我不知道为什么人们会做出这样的假设,可能是因为NTLM握手过程相对复杂。

If you stop after the first phase and don't perform the actual authentication, you're trusting the client/user, in which case you may as well not do any authentication and just put a message saying "Please don't use this web application if you're not allowed to".

如果您在第一个阶段之后停止,并没有执行实际的身份验证,那么您将信任客户端/用户,在这种情况下,您可能不会进行任何身份验证,并只会发出一条消息:“如果您不允许,请不要使用这个web应用程序”。

The second phase is the actual authentication. The web server sends the details provided by the client (the encrypted token) to the domain controller. The domain controller knows the hash of the users password used to encrypt the token, and so performs the same encryption of the password hash. If it matches the value of the client, then we know the client used the correct password hash. The web server never sees the hashed password, it only sees a token that was encrypted with the hashed password as the encryption key.

第二阶段是实际的身份验证。web服务器将客户端提供的详细信息(加密的令牌)发送到域控制器。域控制器知道用于加密令牌的用户密码的哈希,因此执行密码哈希的相同加密。如果它匹配客户端的值,那么我们知道客户端使用了正确的密码散列。web服务器永远不会看到散列密码,它只看到一个用散列密码加密的令牌作为加密密钥。

Unfortunately, there are not many LDAP libraries that support the NETLOGON capabilities required to actually authenticate an NTLM token, probably because it's non-trivial proprietary crap. Samba (well actually winbind) is one of only a small handful of libraries that can do it. There isn't currently a Ruby library capable of NTLM authentication, though there's plenty of libraries that'll get you the username reported by the client, though the client can report any username it likes.

不幸的是,并没有很多LDAP库支持NETLOGON功能,它们实际上需要对NTLM令牌进行身份验证,这可能是因为它不是微不足道的专有垃圾。Samba(实际上是winbind)是仅有的几个能够做到这一点的库之一。目前还没有能够进行NTLM身份验证的Ruby库,尽管有很多库可以让您获得客户端报告的用户名,但是客户端可以报告它喜欢的任何用户名。

As a rule of thumb, if you're NTLM library isn't asking for details of your domain controller, then there's no way it's doing any kind of authentication. A lot of the developers of these simple libraries themselves have no idea what they're doing.

根据经验,如果NTLM库没有要求域控制器的详细信息,那么它不可能进行任何类型的身份验证。这些简单库的许多开发人员自己都不知道自己在做什么。

#4


1  

I use OmniAuth to do authentication off of an ActiveDirectory LDAP interface. Documentation is pretty good and it hooks easily into Rack.

我使用OmniAuth对ActiveDirectory LDAP接口进行身份验证。文档很好,很容易挂在架子上。

#5


1  

I successfully used the Apache Kerberos module that you mentioned (http://modauthkerb.sourceforge.net/) It then presents the same API as would basic auth, while providing all the goodies of Kerberos. You'll just have to use a plain Rack::Auth::Basic, and that's it.

我成功地使用了您提到的Apache Kerberos模块(http://modauthkerb.sourceforge.net/),然后它提供与基本auth相同的API,同时提供Kerberos的所有优点。你只需要使用一个普通的架子::Auth::Basic,就这样。

For plain Rack auth, you could probably use https://github.com/djberg96/rack-auth-kerberos, but I haven't personally tried it. The code looks straight forward, though.

对于plain Rack auth,您可能可以使用https://github.com/djberg96/rack-auth-kerberos,但我还没有亲自尝试过。不过,代码看起来很直接。

Obviously in both cases you'll have to introduce your server to AD.

显然,在这两种情况下,都需要向AD介绍服务器。

#6


0  

I have this working without the rack and NTLM solutions.

我有这个工作没有机架和NTLM解决方案。

For authentication see my answer here: is there a way to read a clients windows login name using ruby on rails

要进行身份验证,请参阅我的答案:是否有一种方法可以使用ruby on rails读取客户端windows登录名

Authorisation can then be done through the net-ldap gem by checking membership of security groups.

然后,通过检查安全组的成员身份,可以通过net-ldap gem完成授权。

This runs only once when the server/service starts, only downside to this is you need to restart the service when the members in the group change. You could of course keep the authorised users in a database table.

这只在服务器/服务启动时运行一次,惟一的缺点是,当组中的成员发生更改时,您需要重新启动服务。当然,您可以将授权用户保存在数据库表中。

Here my code.

我的代码。

In the Sinatra app

辛纳屈的应用

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

and in the helper file

在帮助文件中

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://*.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