有没有办法在 PyQt4/5 中强制使用 QNetworkAccessManager 进行身份验证?

Posted

技术标签:

【中文标题】有没有办法在 PyQt4/5 中强制使用 QNetworkAccessManager 进行身份验证?【英文标题】:Is there any way to force authentication with QNetworkAccessManager in PyQt4/5? 【发布时间】:2017-09-01 15:37:32 【问题描述】:

在我的实现中,似乎QNetworkAccessManager 在成功验证后缓存了用于特定 url 的凭据。

使用不同凭据对同一网址的后续请求将成功,但这只是因为它使用缓存的凭据而不是新的凭据(authenticationRequired 信号不会发出)。

(Py)Qt4(也是 PyQt 5.6)中是否有某种方法可以清除缓存或以其他方式强制更新缓存的凭据?我应该删除经理并构建一个新经理吗?还是我做错了什么?

据我所知,Qt 5 提供了clearAccessCache() 方法,但 Qt 4 没有。

这个小例子说明了这个问题:

import sys
import json
from PyQt4 import QtNetwork, QtGui, QtCore

def show_reply_content(reply):
    content = json.loads(str(reply.readAll()))
    if 'authenticated' in content and content['authenticated']:
        print 'authentication successful for user '.format(
            content['user'])
        reply.manager().replies_authenticated += 1

    # Quit when all replies are finished
    reply.deleteLater()
    reply.manager().replies_unfinished -= 1
    if not reply.manager().replies_unfinished:
        print 'number of successful authentications: '.format(
            reply.manager().replies_authenticated)
        app.quit()


def provide_credentials(reply, authenticator):
    print 'Setting credentials for :\nusername: \n' \
          'password: \n\n'.format(reply.url(),
                                    reply.credentials['username'],
                                    reply.credentials['password'])
    authenticator.setUser(reply.credentials['username'])
    authenticator.setPassword(reply.credentials['password'])

# Some initialization
app = QtGui.QApplication(sys.argv)
manager = QtNetwork.QNetworkAccessManager()
manager.finished.connect(show_reply_content)
manager.authenticationRequired.connect(provide_credentials)
manager.replies_unfinished = 0
manager.replies_authenticated = 0

# Specify credentials
all_credentials = [dict(username='aap',
                        password='njkrfnq'),
                   dict(username='noot',
                        password='asdfber')]

# url authenticates successfully only for the specified credentials
url = 'http://httpbin.org/basic-auth//'.format(
    *all_credentials[1].values())

# Schedule requests
replies = []
for credentials in all_credentials:
    replies.append(
        manager.get(QtNetwork.QNetworkRequest(QtCore.QUrl(url))))

    # Add credentials as dynamic attribute
    replies[-1].credentials = credentials

    manager.replies_unfinished += 1

# Start event loop
app.exec_()

只要一个请求成功通过身份验证,下一个请求也会成功验证,即使它不应该是。

【问题讨论】:

您使用的是什么特定版本的 Qt4?这个错误看起来很相似:QTBUG-15566. 也相关:QNetworkAccessManager - how to enforce authentication? 不确定Qt版本,但我使用的是PyQt4.11.4。我仍然需要查看该错误报告,但第二个链接将我指向QNetworkRequest.AuthenticationReuseAttribute,这听起来很有希望。不幸的是,我使用 request.setAttribute(QNetworkRequest.AuthenticationReuseAttribute, QNetworkRequest.Manual) 的第一次尝试没有成功:现在所有请求都被 401 拒绝。我稍后会深入研究。 @ekhumoro,上面提到的错误看起来确实相似,因​​为使用了缓存的凭据。但是这种行为仍然存在,当我使用 PyQt 5.6 时也是如此。使用 WIreshark 进行的快速检查表明,一旦找到一组有效的凭据(如状态 200 所示),这些凭据将用于对同一 url 的任何后续请求,以及任何待处理的请求(只要他们收到 401,经理就会继续发送这些),并且在向不同的 url 发出请求(也需要授权)之前不会再次发出 authorizationRequired 信号。 也许this 是要走的路? 【参考方案1】:

事实证明,有几个类似的 Qt 错误报告仍然存在:QTBUG-16835、QTBUG-30433...

我想应该可以使用 QNetworkRequest.AuthenticationReuseAttribute,但我无法使用不同(有效)凭据集对同一个 url 的多个请求进行此操作。

一种可能的解决方法是忘记所有关于 authenticationRequired 信号/插槽的信息,并为每个请求添加一个原始标头 (RFC 7617),即 as described here。

在示例中,这意味着删除 authenticationRequired 信号/插槽连接并将 manager.get() 部分替换为以下行(对于带有 PyQt4 的 Python 2.7):

request = QtNetwork.QNetworkRequest(QtCore.QUrl(url))
header_data = QtCore.QByteArray(':'.format(
    credentials['username'], credentials['password'])).toBase64()
request.setRawHeader('Authorization', 'Basic '.format(header_data))
replies.append(manager.get(request))

但是,正如@ekhumoro 在上面的 cmets 中指出的那样:不确定这有多安全。

【讨论】:

以上是关于有没有办法在 PyQt4/5 中强制使用 QNetworkAccessManager 进行身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在 UcanAccess 中强制从磁盘读取?

有没有办法在锁定该文件之前强制更新 SVN 文件?

一款超级给力的弱网测试神器—Qnet(上)

有没有办法强制刷新主机?

一款超级给力的弱网测试神器—Qnet(附视频)

有没有办法使用doctrine2强制执行唯一列?