访问使用Kerberos验证的Web服务

本文将举例介绍如何访问使用Kerberos验证的Web服务。首先,我们将配置一个最简单的Web服务,并给该服务加上Kerberos验证。然后,我们将通过多种方式来访问该Web服务。

环境准备

本文的环境是Ubuntu 16。请参考文章 Linux虚拟机环境 来搭建虚拟机环境, 然后参考 在Ubuntu Linux上了解MIT Kerberos 来搭建基本的Kerberos环境。

实验环境清单

  1. Kerberos服务器 auth.027yunwei.com
  2. Web服务 apps.027yunwei.com
  3. 客户端环境 ubuntu.027yunwei.com

搭建Web服务

在apps.027yunwei.com上安装apache2 (httpd)服务器,并创建一个测试文件krb5_test.txt。

sudo apt install -y apache2
sudo echo 'Hello Kerberos' > /var/www/html/krb5_test.txt

测试一下是否能访问, 在ubuntu.027yunwei.com上使用浏览器来打开页面 http://apps.027yunwei.com/krb5_test.txt。 对于我们喜欢命令行的人,可以用curl命令来访问该页面

sudo apt install -y curl
curl http://apps.027yunwei.com/krb5_test.txt

接下来,我们将使用 mod_auth_gssapi 来给上述网址加上验证功能。

sudo apt install -y libapache2-mod-auth-gssapi

编辑配置文件 /etc/apache2/sites-enabled/000-default.conf, 找到 DocumentRoot, 在其下一行加入如下内容,

<Location />
    AuthType GSSAPI
    AuthName "GSSAPI SSO Login"
    GssapiCredStore keytab:/etc/apache2/http.keytab
    Require valid-user
</Location>

上述文件中的GssapiCredStore所指向的http.keytab文件中放的是HTTP服务的密钥信息。 我们需要在Kerberos数据库中为该服务添加Principal,并将其导出到http.keytab中:

# on auth.027yunwei.com
sudo kadmin.local "add_principal -randkey HTTP/apps.027yunwei.com"
sudo kadmin.local "ktadd -f http.keytab HTTP/apps.027yunwei.com"

将上面生成的 http.keytab文件传到apps.027yunwei.com上,并放在/etc/apache2目录下。 为了使apache2的worker进程能读取该文件,我们需要将该文件的owner改为www-data:

# on apps.027yunwei.com
sudo chown www-data /etc/apache2/http.keytab

由于我们修改了apache2的配置文件,需要重新加载才能生效:

sudo service apache2 reload

使用curl --negotiate 访问

我们再试试上面的curl命令,会得到401 Unauthorized 的错误消息

# on ubuntu.027yunwei.com
curl -i http://apps.027yunwei.com/krb5_test.txt
HTTP/1.1 401 Unauthorized
Date: Wed, 30 Nov 2016 11:44:26 GMT
Server: Apache/2.4.18 (Ubuntu)
WWW-Authenticate: Negotiate
Content-Length: 465
Content-Type: text/html; charset=iso-8859-1

<省略>

我们加上 --negotiate的支持后,仍然会得到同样的401错误

# on ubuntu.027yunwei.com
curl -i --negotiate -u: http://apps.027yunwei.com/krb5_test.txt
HTTP/1.1 401 Unauthorized
Date: Wed, 30 Nov 2016 11:47:36 GMT
Server: Apache/2.4.18 (Ubuntu)
WWW-Authenticate: Negotiate
Content-Length: 465
Content-Type: text/html; charset=iso-8859-1

<省略>

原因是curl不会提示让输入Kerberos用户和密码,而是使用了当前用户的凭证缓存,因此我们需要先生成一个凭证,

kinit -p user1
Password for user1@027YUNWEI.COM:<输入密码>
# 查看凭证
klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: user1@027YUNWEI.COM

Valid starting       Expires              Service principal
2016-11-30T19:50:47  2016-12-01T05:50:47  krbtgt/027YUNWEI.COM@027YUNWEI.COM
    renew until 2016-12-01T19:50:44

再来试试 curl --negotiate 就会发现可以正常访问了

curl -i --negotiate -u: http://apps.027yunwei.com/krb5_test.txt
HTTP/1.1 401 Unauthorized
Date: Wed, 30 Nov 2016 11:52:49 GMT
Server: Apache/2.4.18 (Ubuntu)
WWW-Authenticate: Negotiate
Content-Length: 465
Content-Type: text/html; charset=iso-8859-1

HTTP/1.1 200 OK
Date: Wed, 30 Nov 2016 11:52:50 GMT
Server: Apache/2.4.18 (Ubuntu)
WWW-Authenticate: Negotiate oYG3MIG0oAMKAQChCwYJKoZIhvcSAQICooGfBIGcYIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKicQRv9lz5z9qhJSvtKsaGi48ZnWE24RDHdDAE5QQqzudA+S+6apW12Rhn34zOQT8dqvt9TE+8cF2wlvnMUWTDHUmdROPZdqXCOGDRJDzaBMrQn2180ddpeFrU0WZlcsuW21tHtlMFSGr8Imh6IXty6AYq
Last-Modified: Wed, 30 Nov 2016 08:44:57 GMT
ETag: "16-54280b8367283"
Accept-Ranges: bytes
Content-Length: 22
Content-Type: text/plain

Hello Kerberos

从curl命令的返回信息,我们发现有两次返回,第一次仍然是401 Unauthorized 和 WWW-Authenticate: Negotiate, 第二次返回了服务端提供的Token。

在apache2的access log中,能看到对应的访问日志:

# host: apps.027yunwei.com
# file: /var/log/apache2/access.log
192.168.137.147 - - [30/Nov/2016:03:54:30 -0800] "GET /krb5_test.txt HTTP/1.1" 401 658 "-" "curl/7.47.0"
192.168.137.147 - user1@027YUNWEI.COM [30/Nov/2016:03:54:30 -0800] "GET /krb5_test.txt HTTP/1.1" 200 528 "-" "curl/7.47.0"

我们可以看到,第二次访问中用户的名字user1@027YUNWEI.COM被传了进来,在实际应用中,我们将可以在apahce2或应用模块中根据该用户名称进一步执行授权校验。

为了从curl端看得更清楚,我们可以加上-v再试一次

curl -iv --negotiate -u: http://apps.027yunwei.com/krb5_test.txt
*   Trying 192.168.137.132...
* Connected to apps.027yunwei.com (192.168.137.132) port 80 (#0)
> GET /krb5_test.txt HTTP/1.1
> Host: apps.027yunwei.com
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
HTTP/1.1 401 Unauthorized
< Date: Wed, 30 Nov 2016 11:54:30 GMT
Date: Wed, 30 Nov 2016 11:54:30 GMT
< Server: Apache/2.4.18 (Ubuntu)
Server: Apache/2.4.18 (Ubuntu)
< WWW-Authenticate: Negotiate
WWW-Authenticate: Negotiate
< Content-Length: 465
Content-Length: 465
< Content-Type: text/html; charset=iso-8859-1
Content-Type: text/html; charset=iso-8859-1

<
* Ignoring the response-body
* Connection #0 to host apps.027yunwei.com left intact
* Issue another request to this URL: 'http://apps.027yunwei.com/krb5_test.txt'
* Found bundle for host apps.027yunwei.com: 0x55c118efc0c0 [can pipeline]
* Re-using existing connection! (#0) with host apps.027yunwei.com
* Connected to apps.027yunwei.com (192.168.137.132) port 80 (#0)
* Server auth using Negotiate with user ''
> GET /krb5_test.txt HTTP/1.1
> Host: apps.027yunwei.com
> Authorization: Negotiate YIICtgYGKwYBBQUCoIICqjCCAqagJzAlBgkqhkiG9xIBAgIGBSsFAQUCBgkqhkiC9xIBAgIGBisGAQUCBaKCAnkEggJ1YIICcQYJKoZIhvcSAQICAQBuggJgMIICXKADAgEFoQMCAQ6iBwMFACAAAACjggFyYYIBbjCCAWqgAwIBBaEPGw0wMjdZVU5XRUkuQ09NoiUwI6ADAgEDoRwwGhsESFRUUBsSYXBwcy4wMjd5dW53ZWkuY29to4IBKTCCASWgAwIBEqEDAgEDooIBFwSCARPa1bPYEI4nUfGnt0sD1YJFRyWuxCd5rAbrxVy0B5bTRoxSQ1fUVJFc2w3CSuQtfN7G1S03MAT+wOpimUUm8/bRZjQqpc9GLSivJlhncJLYJK18Iiro4yEJk/ElVRr/J2IckrjYSjUlqAmF967qgttANtqsKsDIbCVEHqfszWHr9Bpr0vdMkSZALZmPd3j5yXgALGysMSXQKs+TtKWedrUpGF7MhVMt0OroNV320+Xv+eyzPAM/dtonoexzbVBoiwTRb4ZkSKLNpvHUfS4PxdEcMSpOslYZ/E5kZ4gF2ejxWD15600zo+CkNKUoMu12WVGZFsJ7oRQ5YmDDPHAohFMW3V4HRCAKq6eqYNwmyWl2pQ8mHqSB0DCBzaADAgESooHFBIHCYnz/gZ7JJHu2j3vpdVJ16RIkGHUT9G1jcOnXrmj1JgYx1+HsxX1j+JIcTbYWGUJul0vXfsYSGSazrWwjCLXNciiujvowEgd6A0PQjIWbLWH/pS+aqsIY7qvB0WAQypB85gkjflCNHv25jOrX6USEse0WngrP5xn2a1LmyQn7cfeseuZjb/tE1RWxzS0ywBnYKHarWX1QJ/FBFTL1FYgW1jwyaBSMzNzcRiYZUE7Q9gA18v5E5BWVp/eJTMkuZyCnOps=
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Date: Wed, 30 Nov 2016 11:54:30 GMT
Date: Wed, 30 Nov 2016 11:54:30 GMT
< Server: Apache/2.4.18 (Ubuntu)
Server: Apache/2.4.18 (Ubuntu)
< WWW-Authenticate: Negotiate oYG3MIG0oAMKAQChCwYJKoZIhvcSAQICooGfBIGcYIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKicQRvxKuwn1/yUD6r9JfmfKFRhpVYii4p/qzmEGbegnj2ZcMgE2ksGlJe6tfxOX7iqEpBJFfnoKxfX0vhy2/tnEOhJQRnMY9PhPLMFmhUVXwGxGkAtHJDOtMqjkRnt1mukeBRUUZlp+mTAnsTsY3Pvx/Q
WWW-Authenticate: Negotiate oYG3MIG0oAMKAQChCwYJKoZIhvcSAQICooGfBIGcYIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKicQRvxKuwn1/yUD6r9JfmfKFRhpVYii4p/qzmEGbegnj2ZcMgE2ksGlJe6tfxOX7iqEpBJFfnoKxfX0vhy2/tnEOhJQRnMY9PhPLMFmhUVXwGxGkAtHJDOtMqjkRnt1mukeBRUUZlp+mTAnsTsY3Pvx/Q
< Last-Modified: Wed, 30 Nov 2016 08:44:57 GMT
Last-Modified: Wed, 30 Nov 2016 08:44:57 GMT
< ETag: "16-54280b8367283"
ETag: "16-54280b8367283"
< Accept-Ranges: bytes
Accept-Ranges: bytes
< Content-Length: 22
Content-Length: 22
< Content-Type: text/plain
Content-Type: text/plain

<
Hello Kerberos
* Closing connection 0

从上面的输出消息可以看到,curl在得到了第一次的401返回后,第二次请求时在header中加上了 Authorization: Negotiate [token], 该token是curl根据用户凭据缓存的凭证生成的。

使用Python requests来访问

Python的python-requests库是非常流行的http client库,其 Kerberos auth插件能支持SPNEGO方式来访问web服务。

首先,安装 python-requests-kerberos:

sudo apt install python-requests-kerberos

然后,将下面的代码保存为一个python文件 http_krb_sample.py

import requests
from requests_kerberos import HTTPKerberosAuth

url = 'http://apps.027yunwei.com'
res = requests.get(url, auth=HTTPKerberosAuth())

for r in res.history:
    print( 'response : {status_code} {reason} '.format(\
            status_code=r.status_code, reason=r.reason))

    print('headers[WWW-Authenticate]:{auth}'.format(\
            auth=r.headers['WWW-Authenticate']))

print( 'response : {status_code} {reason} '.format(\
            status_code=res.status_code, reason=res.reason))

print('headers[WWW-Authenticate]:{auth}'.format(\
        auth=res.headers['WWW-Authenticate']))

执行该文件,并查看输出

python http_krb5_sample.py
response : 401 Unauthorized
headers[WWW-Authenticate]:Negotiate
response : 200 OK
headers[WWW-Authenticate]:Negotiate YIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKicQRv52B8/M2jNr39kxmvm3r87+cTB8xOqpaDUZ5bxnYFrjzIVyl4j7VutTBYN/owp0iTxR12tcim7lTW4xU4CvTbnJ7GDjtJh0WWoSCfM9E4BJde7FY/xkvBjYmYrhP3DTGyGt4mh6wDNYxNjYIw0rz2

对应的apache2 access log输出

192.168.137.147 - - [30/Nov/2016:06:30:44 -0800] "GET /krb5_test.txt HTTP/1.1" 401 714 "-" "python-requests/2.9.1"
192.168.137.147 - user1@027YUNWEI.COM [30/Nov/2016:06:30:44 -0800] "GET /krb5_test.txt HTTP/1.1" 200 543 "-" "python-requests/2.9.1"

使用web浏览器的spnego支持来访问

那么使用浏览器访问页面有什么要求吗?不同浏览器支持该特性的实现方式不同,请参考 https://ping.force.com/Support/PingFederate/Integrations/How-to-configure-supported-browsers-for-Kerberos-NTLM 来进行设置。在ubuntu desktop中,我们可以使用chromium, 其配置方式是在命令行上加参数 --auth-server-whitelist,

chromium-browser --auth-server-whitelist='apps.027yunwei.com'

启动后,在地址栏输入 http://027yunwei.com/krb5_test.txt, 就可以正常访问了。后台相应的access log输出:

192.168.137.147 - - [30/Nov/2016:05:56:32 -0800] "GET /krb5_test.txt HTTP/1.1" 401 714 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"
192.168.137.147 - user1@027YUNWEI.COM [30/Nov/2016:05:56:32 -0800] "GET /krb5_test.txt HTTP/1.1" 200 367 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36"

思考

GSSAPI对SPNEGO机制的支持,使得Kerberos也能用于HTTP Web的访问验证,这使得集成HTTP Web服务与Kerberos变得可行。 但是,我们可以看到,浏览器的支持程度不够标准化,而且很多的Web服务是位于企业之外的。对于跨企业边界的SSO,在企业Web应用集成中,SAML的部署很广。而在互联网世界里,OAuth则是事实上的标准。

因此,GSSAPI SPNEGO是企业内部kerberos方案用于支持集成Web服务的,而Kerberos在操作系统、数据库系统,大数据平台,分布式集群等内部基础设施方面的应用更加广泛。

参考资料

下面的资料对于理解本文中的例子很有帮助

  1. http://www.microhowto.info/howto/configure_apache_to_use_kerberos_authentication.html 该文介绍了如何配置apache HTTPD 来使用kerberos
  2. http://modauthkerb.sourceforge.net/ libapache2-mod-auth-kerb的项目站点
  3. https://github.com/modauthgssapi/mod_auth_gssapi libapache2-mod-auth-gssapi的项目站点
  4. https://ping.force.com/Support/PingFederate/Integrations/How-to-configure-supported-browsers-for-Kerberos-NTLM 该文介绍了多种浏览器上如何启动Kerberos认证
  5. http://thekspace.com/home/component/content/article/54-kerberos-and-spnego.html
  6. https://curl.haxx.se/docs/manpage.html curl手册。其中介绍了 --negotiate 的用法。
  7. https://tools.ietf.org/html/rfc7235 HTTP/1.1 Authentication
  8. https://tools.ietf.org/html/rfc4178 GSS-API with SPNEGO
  9. https://tools.ietf.org/html/rfc4559 SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows