我正在尝试从Jekins服务器获取URL。直到最近,我仍然能够使用此页面上描述的模式(如何使用urllib2获取Internet资源)创建一个密码管理器,该密码管理器使用用户名和密码正确响应BasicAuth挑战。在Jenkins团队更改其安全模型之前,一切都还不错,并且该代码不再起作用。
# DOES NOT WORK! import urllib2 password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() top_level_url = "http://localhost:8080" password_mgr.add_password(None, top_level_url, 'sal', 'foobar') handler = urllib2.HTTPBasicAuthHandler(password_mgr) opener = urllib2.build_opener(handler) a_url = 'http://localhost:8080/job/foo/4/api/python' print opener.open(a_url).read()
堆栈跟踪:
Traceback (most recent call last): File "/home/sal/workspace/jenkinsapi/src/examples/password.py", line 11, in <module> print opener.open(a_url).read() File "/usr/lib/python2.7/urllib2.py", line 410, in open response = meth(req, response) File "/usr/lib/python2.7/urllib2.py", line 523, in http_response 'http', request, response, code, msg, hdrs) File "/usr/lib/python2.7/urllib2.py", line 448, in error return self._call_chain(*args) File "/usr/lib/python2.7/urllib2.py", line 382, in _call_chain result = func(*args) File "/usr/lib/python2.7/urllib2.py", line 531, in http_error_default raise HTTPError(req.get_full_url(), code, msg, hdrs, fp) urllib2.HTTPError: HTTP Error 403: Forbidden [Finished in 0.0s with exit code 1]
问题似乎是Jenkins返回的不是预期的401代码,而是urllib2解释为对话结束的403。它从不实际发送密码。在github上浏览一番之后,找到了另一个可以正常工作的开发人员解决方案…
# WORKS... SORTA def auth_headers(username, password): return 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1] auth = auth_headers('sal', 'foobar') top_level_url = "http://localhost:8080" a_url = 'http://localhost:8080/job/foo/4/api/python' req = urllib2.Request(a_url) req.add_header('Authorization', auth) print urllib2.urlopen(req).read()
但是,这似乎并不令人满意。不必费心检查该域是否与用户名和密码相关…只是发送我的登录详细信息而已!
有人可以建议一种使原始脚本正常工作的方法吗?我想以一种可以登录Jenkins的方式使用urllib2密码管理器。
也请参阅以下要点:https : //gist.github.com/dnozay/194d816aa6517dc67ca1
401 - retry当您需要访问需要认证的页面时,Jenkins不会返回HTTP错误代码。相反,它返回403 - forbidden。在维基,https://wiki.jenkins- ci.org/display/JENKINS/Authenticating+scripted+clients,它表明使用命令行工具wget,你需要使用wget--auth-no-challenge它是行为正是因为。
401 - retry
403 - forbidden
wget
wget--auth-no-challenge
假设您定义了:
jenkins_url = "https://jenkins.example.com" username = "johndoe@example.com" api_token = "my-api-token"
您可以将a子类化urllib2.HTTPBasicAuthHandler以处理403HTTP响应。
urllib2.HTTPBasicAuthHandler
403
import urllib2 class HTTPBasic403AuthHandler(urllib2.HTTPBasicAuthHandler): # retry with basic auth when facing a 403 forbidden def http_error_403(self, req, fp, code, msg, headers): host = req.get_host() realm = None return self.retry_http_basic_auth(host, req, realm)
然后就可以使用该处理程序了,例如,您可以安装该处理程序,使其适用于所有urllib2.urlopen调用:
urllib2.urlopen
def install_auth_opener(): '''install the authentication handler. This handles non-standard behavior where the server responds with 403 forbidden, instead of 401 retry. Which means it does not give you the chance to provide your credentials.''' auth_handler = HTTPBasic403AuthHandler() auth_handler.add_password( realm=None, uri=jenkins_url, user=username, passwd=api_token) opener = urllib2.build_opener(auth_handler) # install it for all urllib2.urlopen calls urllib2.install_opener(opener)
这是一个简单的测试,看它是否还可以。
if __name__ == "__main__": # test install_auth_opener() page = "%s/me/api/python" % jenkins_url try: result = urllib2.urlopen(page) assert result.code == 200 print "ok" except urllib2.HTTPError, err: assert err.code != 401, 'BAD CREDENTIALS!' raise err
这个答案有一个很好的例子:。当您找到url时,403forbidden将发送Authorization标头,而不是重试。
403forbidden
Authorization
class PreemptiveBasicAuthHandler(urllib2.HTTPBasicAuthHandler): '''Preemptive basic auth. Instead of waiting for a 403 to then retry with the credentials, send the credentials if the url is handled by the password manager. Note: please use realm=None when calling add_password.''' def http_request(self, req): url = req.get_full_url() realm = None # this is very similar to the code from retry_http_basic_auth() # but returns a request object. user, pw = self.passwd.find_user_password(realm, url) if pw: raw = "%s:%s" % (user, pw) auth = 'Basic %s' % base64.b64encode(raw).strip() req.add_unredirected_header(self.auth_header, auth) return req https_request = http_request