【问题标题】:Python requests 401 UnauthorizedPython请求401未经授权
【发布时间】:2020-06-18 00:28:16
【问题描述】:

我正在尝试登录基于 cloudflare 服务器的网站。我使用 cloudserver 绕过了登录问题,但我的下一站是当我尝试发送获取请求以访问某些登录后令牌时。

我的代码:

headers = {
          'authority': 'www.paf.es',
          'accept': 'application/json, text/plain, */*',
          'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36',
          'x-requested-with': 'XMLHttpRequest',
          'sec-fetch-site': 'same-origin',
          'sec-fetch-mode': 'cors',
          'sec-fetch-dest': 'empty',
          'referer': 'https://www.paf.es/my-paf',
          'accept-language': 'es-ES,es;q=0.9,en;q=0.8',
          'cookie': '__cfduid=d4247b3fce5d260d7c5257b5d65a572001592254859; com.paf.frontend.common.LocaleCookie=es_ES; _gcl_au=1.1.193127229.1592254863; _ga=GA1.2.484179826.1592254863; _gid=GA1.2.1016568931.1592254863; _fbp=fb.1.1592254863595.468566668; com.paf.frontend.cookiesAccepted=true; com.paf.frontend.common.device=desktop; __cf_bm=10366cb3cb936cf0d9188cdf37dfaf276961f164-1592257614-1800-AbXayDicXL3zBDecjcoUuzlv+Qb5YhjhhqZO6goD80+W/J7ahYM+mwNHdcav405NnNOcPxyErOcdvPzijcdXGhk=; BIGipServerprod01_pool=1067697930.22811.0000; _gat_UA-641842-15=1; com.paf.frontend.common.showMenu=account; _gali=loginButton; JSESSIONID=tuO580BlkBaMw5v3txOBy0v2hqQV-61ZBQCVRqdqcTEQN4-5Z6tuu0021151447412; com.paf.frontend.common.LoginTime=1592257626364; com.paf.frontend.common.LoggedIn=true; __cfruid=5fe9d18ceeda0612668c20982f65d634686cb526-1592227626; com.paf.frontend.common.LifeCycleCookie=HAS_LOGGED_IN; trackingParams={"_ga":{"value":"GA1.2.484179826.1592254863","expiration":1600033627916},"utm_nooverride":{"value":"1","expiration":1600033627916}}; com.paf.frontend.common.LocaleCookie=es_ES; JSESSIONID=m4650rp-X0aJYw_6b8-3ghQIOU-h1luYOQV4dAwZz6UGVc4RanvZ!151447412; com.paf.frontend.common.LoginTime=1592255494930; com.paf.frontend.common.LoggedIn=true; __cfruid=3ffb40834f313a4c2b4d351r24f9a946uu5f7db9-1592255495'
        }
cloudserver.get(url=url,headers=headers,data=json.dumps({}))

这会返回 401 作为答案:

The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.46) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity MAY include relevant diagnostic information.

它的标题是

{'Date': 'Wed, 17 Jun 2020 23:56:04 GMT', 'Content-Type': 'text/html; charset=UTF-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'CF-Ray': '5a50b4816fddd665-MAD', 'Set-Cookie': 'com.paf.frontend.common.LocaleCookie=es_ES; expires=Thu, 17-Jun-2021 23:56:04 GMT; path=/, com.paf.frontend.common.LoggedIn=; expires=Thu, 01-Jan-1970 01:00:00 GMT; path=/, __cf_bm=73f0a2b2216d419f8a19f3e8ff74e8eca2458229-1592438164-1800-AaPuIPRQVOJGvI9l1DBiMeXXmyczqpm7Owaf2XUHFqZ+FJ9PT44TdL4kxAU4FCOWDWQmztz9Ff1FTHrCcDQw88w=; path=/; expires=Thu, 18-Jun-20 00:26:04 GMT; domain=.paf.es; HttpOnly; Secure; SameSite=None', 'Strict-Transport-Security': 'max-age=15552000; includeSubDomains; preload', 'CF-Cache-Status': 'DYNAMIC', 'cf-request-id': '03664f24e50000d66506af8200000001', 'Expect-CT': 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"', 'X-Content-Type-Options': 'nosniff', 'Vary': 'Accept-Encoding', 'Server': 'cloudflare', 'alt-svc': 'h3-27=":443"; ma=86400'}

好的,所以我首先尝试提供身份验证。像这样

s.get(url=url,headers=headers,data=json.dumps({}),auth=HTTPBasicAuth('somemail@mail.com','password'))

我再次遇到同样的错误,即 401,但这次响应的标头确实有一个 www-authenticate 我应该挑战

{'Date': 'Wed, 17 Jun 2020 23:58:42 GMT', 'Content-Type': 'text/html; charset=UTF-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'CF-Ray': '5a50b85dd8fdd665-MAD', 'Strict-Transport-Security': 'max-age=15552000; includeSubDomains; preload', 'WWW-Authenticate': 'Basic realm="weblogic"', 'CF-Cache-Status': 'DYNAMIC', 'cf-request-id': '0366518ea80000d66506a1b200000001', 'Expect-CT': 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"', 'X-Content-Type-Options': 'nosniff', 'Set-Cookie': '__cf_bm=c153a7277cf7abbaa004cdb46da821906f04b7c6-1592438322-1800-AR+KhjkQM5NHTTqKA0scqXrUGJpKNOnG0ZASOG386zEILi28YAh28BV+pWYGXzly+7ptsr9TJSGurY2nBOTv75I=; path=/; expires=Thu, 18-Jun-20 00:28:42 GMT; domain=.paf.es; HttpOnly; Secure; SameSite=None', 'Vary': 'Accept-Encoding', 'Server': 'cloudflare', 'alt-svc': 'h3-27=":443"; ma=86400'}

根据我的阅读,它是基本的意味着我必须在我的标题中添加一个像 'Authentication': "Basic fhsejdjsjejdsj" 这样的键,它是基本之后的字符串,是 'username:password' 的 base64 编码。

但是,在我的标头中添加此内容后,我再次发送了一个请求,但我仍然停留在同一点,使用相同的 401。

我的问题是,在“WWW-Authenticate”中:“Basic realm="weblogic"” 域之后的内容是否重要?我怎样才能克服这个问题并能够完成我的请求?

【问题讨论】:

  • 为什么不使用 requests.get 呢?
  • 我不明白,而不是什么
  • 使用requests lib,可以google python requests lib使用
  • cloudserver 是一个基于请求的库。它用于绕过 cloudflare 给请求的 403。如果您对此获取查询使用常规请求,您仍然会得到 401
  • 您是否尝试过在 Postman 中运行您的 get 请求?这个问题看起来像是标题(与 Python 代码相关)。同样在你的标题中你有身份验证,但我认为正确的词是授权,例如:“授权”:“基本 abcde”

标签: python-3.x authentication python-requests http-status-code-401


【解决方案1】:

以防万一 HTTP 身份验证在这里没有被清楚地理解,我将解释它是如何工作的。

  1. 您以通常的方式请求 HTTP 资源,包含您认为需要的所有标头

  2. 如果内容或区域(即领域)受密码保护,则服务器返回 401 错误。响应包含 WWW-Authenticate 标头,它告诉您在响应服务器时必须使用哪种身份验证方法以及它适用于哪个领域。

  3. 如果您收到 401,您将向服务器重复您的请求,但您将 Authorization 标头添加到请求中。它的内容根据所使用的方法而有所不同,即服务器在第一个响应中请求的方法。对于基本身份验证方法,您的授权应该将单词 Basic 作为第一个“参数”,然后使用冒号“:”分隔用户名和密码,但此字符串必须是 Base64 编码的。你可以这样做:

    headers["Authorization"] = "Basic "+(("%s:%s" % (username, password)).encode("base64")

这是为支持浏览器和 GUI 对话框而设计的。当浏览器收到 401 时,它会弹出一个对话框,要求您输入用户名和密码。当您单击确定时,它会重复请求并添加授权标头。从这一点开始,浏览器总是发送 Authorization 标头以继续登录,直到收到另一个领域的另一个 401。使用领域,以便浏览器可以根据领域服务器请求自动更改凭据以执行会话,而无需在每次请求相同领域时再次提示您输入它们。那是因为您可能使用不同的密码保护不同区域的文件,或者同一服务器上的多个帐户等等。

现在,出于安全和标准化原因,服务器可能总是拒绝第一个请求,无论它是否已经收到 Authorization 标头。事实上,如果您在未请求 WWW-Authenticate 之前发送授权,它可能会将其视为安全漏洞。一些服务器会发送一次,并期望从此时开始存在授权,并且只有在它从客户端的进一步请求中消失时才重复 401。其他人总是会发送 401 然后期待重复的请求。有些还会完全切断您的访问权限,如果您发送不正确的凭据,您将收到 403 Forbidden,例如连续 3 次。此外,如果服务器使用基本身份验证,这通常意味着服务器使用的是 HTTPS,而不是 HTTP。由于发送的数据很容易在 HTTP 链接上被截获和读取,如果使用 HTTP,将要求使用其他一些加密的方法来保护安全性。因此,如果您通过 HTTP 而不是 HTTPS 使用基本方法发送 Authorization 标头,服务器也可能会拒绝您。

我上面描述的服务器的特性(HTTP 标准身份验证过程)是您有时进入,有时又收到 401 的原因。您处理此问题的方法是永远不要发送 Authorization 标头,除非您首先收到 401。使用 urllib 处理这个问题很容易,因为 401 Unauthorized 会引发 HTTPError()。所以你会:

def get (*args, **kwargs):
    user = kwargs.pop("user", "")
    pwd  = kwargs.pop("pwd", "")
    r = Request(*args, **kwargs) # A request with URL and headers and data
    try:
        u = urlopen(r)
    except HTTPError as e:
        if e.code==401 and "WWW-Authenticate" in e.headers:
            if not e.headers.get("WWW-Authenticate").lower().startswith("basic "):
                raise
            r.add_header("Authorization", "Basic "+(user+":"+pwd).encode("base64"))
            u = urlopen(r)
        else:
            raise
    c = u.read()
    u.close()
    return c

你需要查看服务器是每次发送401还是只发送一次。当您使用请求时,您必须使用响应属性检查响应代码,而不使用 try-except 块。或者切换到stdlib urllib/urllib2。 这就是我要做的,因为您似乎并没有使用会话等请求中的功能,而是每次手动发送 cookie。

这里真正的问题是 JSON 的身份验证和 HTTP 标头的身份验证。两者都需要或只需要其中之一。你只需要进行实验。在我看来,即使您只通过数据发送凭据,API 也会执行 HTTP 身份验证。但是……

【讨论】:

    【解决方案2】:

    我不能 100% 确定您在这里使用的是哪个库,但假设该库是基于 Requests 的,我的阅读方式是:

    您尝试向服务器发送请求,但由于 401 错误而失败。这意味着您的请求缺少必要的凭据来访问您尝试访问的内容。

    所以你发送另一个带有用户名和密码的请求。

    服务器回复另一个 401 错误,这仍然意味着同一件事:您的请求缺少必要的凭据来访问您尝试访问的内容。

    鉴于服务器提供的响应头:'WWW-Authenticate': 'Basic realm="weblogic"',您是正确的,您需要使用基本身份验证。

    weblogic 领域是服务器告诉您需要登录的区域。您的请求需要在该服务器上具有该领域的有效用户名和密码。

    再次假设你的库是基于 Requests 的,你不需要自己做 base64 任何东西,这个调用:

    s.get(url=url,headers=headers,data=json.dumps({}),auth=HTTPBasicAuth('somemail@mail.com','password'))
    

    或者这个速记版本:

    s.get(url=url,headers=headers,data=json.dumps({}),auth=('somemail@mail.com','password'))
    

    将为您处理,但您需要有效的用户名和密码组合。

    【讨论】:

    • 是的,cloudscraper 是基于请求的。就像 requests.session 一样。我已经按照你的建议做了,但 401 仍然占上风。有点奏效的是,证明s=cloudscrsper.create_scraper() 是一个请求会话,重用登录的标头而不是添加 s.get 请求。问题是这有时有效,有时仍然得到 401,我不知道为什么会这样
    【解决方案3】:

    好的,所以我尝试的一些方法似乎部分奏效了。回顾一下,我尝试了一切:

    在请求的头部添加Authorization: Basic <token>

    在请求的头部添加Authorization: Bearer <token>

    在请求中使用auth=HTTPBasicAuth(username,password)

    在请求中使用auth=HTTPDigestAuth(username,password)

    似乎没有任何效果。提醒一下,我使用 cloudcraper 而不是常规请求,因为服务器在 cloudflare 中,否则返回 403。

    因此,实际起作用的是重用登录尝试中使用的标头。所以不是这个

    s=cloudscraper.create_scraper()
    s.post(url=url,headers=headers,data=payload) #Being url the XHR for the login, headers the ones from postman, and  data a string with my actual username and password
    s.get(url=url,headers=headers) #New url requests and new headers from postman
    

    我做了这个

    s=cloudscraper.create_scraper()
    s.post(url=url,headers=headers,data=payload)
    s.get(url=url) #no headers here, just a reuse from the ones in the previous request
    

    这并不完美,因为有时它可以工作,有时它不会,无缘无故地以完全相同的顺序发布完全相同的参数。但是,这是一个进步

    【讨论】:

      猜你喜欢
      • 2016-10-24
      • 1970-01-01
      • 2012-08-17
      • 1970-01-01
      • 1970-01-01
      • 2021-04-26
      • 2017-11-13
      • 2011-11-16
      • 1970-01-01
      相关资源
      最近更新 更多