PyQt4/5 的隐式 OAuth2 授权
Implicit OAuth2 grant with PyQt4/5
我一直在开发一个使用 OAuth2 来识别用户的 python 应用程序。我似乎已经成功地实现了 OAuth2 隐式授权的工作流程(通常用于已安装和用户代理应用程序),但在接收令牌的最后一步,似乎出了点问题。
每当用户需要进行身份验证时,都会生成一个 PyQt QWebView window(基于 webkit)来显示登录页面。在用户登录并允许我的应用程序的范围权限后,OAuth2 服务器重定向到预先指定的 redirect_uri。
问题是,当使用 QWebView 浏览器时,通常出现在 # 之后的令牌字符串似乎已从 URL 中删除:URL QWebView returns 只是基础 redirect_uri.
如果我复制粘贴 OAuth 授权 URL 并按照相同的步骤在 Chrome 或 Firefox 等普通网络浏览器中登录和授权,我会看到 redirect_uri包括token字符串,所以问题不在OAuth2流程上,一定是我这边实现的地方出了问题。
这种行为是 QWebView 或 webkit 的实现所固有的吗?我读错了 QUrl?
为了完整起见,这是我的代码:
osf.py 为开放科学框架生成 OAuth2 URL 的模块。
# Import basics
import sys
import os
# Module for easy OAuth2 usage, based on the requests library,
# which is the easiest way to perform HTTP requests.
# OAuth2Session object
from requests_oauthlib import OAuth2Session
# Mobile application client that does not need a client_secret
from oauthlib.oauth2 import MobileApplicationClient
#%%----------- Main configuration settings ----------------
client_id = "cbc4c47b711a4feab974223b255c81c1"
# TESTED, just redirecting to Google works in normal browsers
# the token string appears in the url of the address bar
redirect_uri = "https://google.nl"
# Generate correct URLs
base_url = "https://test-accounts.osf.io/oauth2/"
auth_url = base_url + "authorize"
token_url = base_url + "token"
#%%--------------------------------------------------------
mobile_app_client = MobileApplicationClient(client_id)
# Create an OAuth2 session for the OSF
osf_auth = OAuth2Session(
client_id,
mobile_app_client,
scope="osf.full_write",
redirect_uri=redirect_uri,
)
def get_authorization_url():
""" Generate the URL with which one can authenticate at the OSF and allow
OpenSesame access to his or her account."""
return osf_auth.authorization_url(auth_url)
def parse_token_from_url(url):
token = osf_auth.token_from_fragment(url)
if token:
return token
else:
return osf_auth.fetch_token(url)
打开带有登录屏幕的 QWebView 浏览器window 的主程序
# Oauth2 connection to OSF
import off
import sys
from PyQt4 import QtGui, QtCore, QtWebKit
class LoginWindow(QtWebKit.QWebView):
""" A Login window for the OSF """
def __init__(self):
super(LoginWindow, self).__init__()
self.state = None
self.urlChanged.connect(self.check_URL)
def set_state(self,state):
self.state = state
def check_URL(self, url):
#url is a QUrl object, covert it to string for easier usage
url_string = url.toEncoded()
print(url_string)
if url.hasFragment():
print("URL CHANGED: On token page: {}".format(url))
self.token = osf.parse_token_from_url(url_string)
print(self.token)
elif not osf.base_url in url_string:
print("URL CHANGED: Unexpected url")
if __name__ == "__main__":
""" Test if user can connect to OSF. Opens up a browser window in the form
of a QWebView window to do so."""
# Import QT libraries
app = QtGui.QApplication(sys.argv)
browser = LoginWindow()
auth_url, state = osf.get_authorization_url()
print("Generated authorization url: {}".format(auth_url))
browser_url = QtCore.QUrl.fromEncoded(auth_url)
browser.load(browser_url)
browser.set_state(state)
browser.show()
exitcode = app.exec_()
print("App exiting with code {}".format(exitcode))
sys.exit(exitcode)
基本上,无论我使用什么,QWebView 的 url_changed 事件提供给 check_URL 函数的 url 从 OAuth 服务器返回时从不包含 OAuth 令牌片段对于 redirect_uri(在这个例子中,为了简单起见,我只是重定向到 google)。
有人可以帮我解决这个问题吗?我已经用尽了在何处寻找此问题的解决方案的选择。
这似乎是 Webkit/Safari 中的一个已知错误:
https://bugs.webkit.org/show_bug.cgi?id=24175
https://phabricator.wikimedia.org/T110976#1594914
基本上它不是固定的,因为人们不同意根据 HTTP 规范所需的行为应该是什么。 How do I preserve uri fragment in safari upon redirect? 中描述了一个可能的修复方法,但我无法对此进行测试。
编辑
我设法找到了一个(不太优雅的)解决方法来解决这个问题。我没有使用 QWebView 的 urlChanged 事件(它没有显示 OAuth 服务器完成的 301 重定向),而是使用了 QNetworkAccessManager 的 finished() 事件。这在 any http 请求完成后被触发(对于页面的所有链接内容,如图像、样式表等,因此您必须进行大量过滤)。
所以现在我的代码如下所示:
class LoginWindow(QtWebKit.QWebView):
""" A Login window for the OSF """
# Login event is emitted after successfull login
logged_in = QtCore.pyqtSignal(['QString'])
def __init__(self):
super(LoginWindow, self).__init__()
# Create Network Access Manager to listen to all outgoing
# HTTP requests. Necessary to work around the WebKit 'bug' which
# causes it drop url fragments, and thus the access_token that the
# OSF Oauth system returns
self.nam = self.page().networkAccessManager()
# Connect event that is fired if a HTTP request is completed.
self.nam.finished.connect(self.checkResponse)
def checkResponse(self,reply):
request = reply.request()
# Get the HTTP statuscode for this response
statuscode = reply.attribute(request.HttpStatusCodeAttribute)
# The accesstoken is given with a 302 statuscode to redirect
if statuscode == 302:
redirectUrl = reply.attribute(request.RedirectionTargetAttribute)
if redirectUrl.hasFragment():
r_url = redirectUrl.toString()
if osf.redirect_uri in r_url:
print("Token URL: {}".format(r_url))
self.token = osf.parse_token_from_url(r_url)
if self.token:
self.logged_in.emit("login")
self.close()
我一直在开发一个使用 OAuth2 来识别用户的 python 应用程序。我似乎已经成功地实现了 OAuth2 隐式授权的工作流程(通常用于已安装和用户代理应用程序),但在接收令牌的最后一步,似乎出了点问题。
每当用户需要进行身份验证时,都会生成一个 PyQt QWebView window(基于 webkit)来显示登录页面。在用户登录并允许我的应用程序的范围权限后,OAuth2 服务器重定向到预先指定的 redirect_uri。
问题是,当使用 QWebView 浏览器时,通常出现在 # 之后的令牌字符串似乎已从 URL 中删除:URL QWebView returns 只是基础 redirect_uri.
如果我复制粘贴 OAuth 授权 URL 并按照相同的步骤在 Chrome 或 Firefox 等普通网络浏览器中登录和授权,我会看到 redirect_uri包括token字符串,所以问题不在OAuth2流程上,一定是我这边实现的地方出了问题。
这种行为是 QWebView 或 webkit 的实现所固有的吗?我读错了 QUrl?
为了完整起见,这是我的代码:
osf.py 为开放科学框架生成 OAuth2 URL 的模块。
# Import basics
import sys
import os
# Module for easy OAuth2 usage, based on the requests library,
# which is the easiest way to perform HTTP requests.
# OAuth2Session object
from requests_oauthlib import OAuth2Session
# Mobile application client that does not need a client_secret
from oauthlib.oauth2 import MobileApplicationClient
#%%----------- Main configuration settings ----------------
client_id = "cbc4c47b711a4feab974223b255c81c1"
# TESTED, just redirecting to Google works in normal browsers
# the token string appears in the url of the address bar
redirect_uri = "https://google.nl"
# Generate correct URLs
base_url = "https://test-accounts.osf.io/oauth2/"
auth_url = base_url + "authorize"
token_url = base_url + "token"
#%%--------------------------------------------------------
mobile_app_client = MobileApplicationClient(client_id)
# Create an OAuth2 session for the OSF
osf_auth = OAuth2Session(
client_id,
mobile_app_client,
scope="osf.full_write",
redirect_uri=redirect_uri,
)
def get_authorization_url():
""" Generate the URL with which one can authenticate at the OSF and allow
OpenSesame access to his or her account."""
return osf_auth.authorization_url(auth_url)
def parse_token_from_url(url):
token = osf_auth.token_from_fragment(url)
if token:
return token
else:
return osf_auth.fetch_token(url)
打开带有登录屏幕的 QWebView 浏览器window 的主程序
# Oauth2 connection to OSF
import off
import sys
from PyQt4 import QtGui, QtCore, QtWebKit
class LoginWindow(QtWebKit.QWebView):
""" A Login window for the OSF """
def __init__(self):
super(LoginWindow, self).__init__()
self.state = None
self.urlChanged.connect(self.check_URL)
def set_state(self,state):
self.state = state
def check_URL(self, url):
#url is a QUrl object, covert it to string for easier usage
url_string = url.toEncoded()
print(url_string)
if url.hasFragment():
print("URL CHANGED: On token page: {}".format(url))
self.token = osf.parse_token_from_url(url_string)
print(self.token)
elif not osf.base_url in url_string:
print("URL CHANGED: Unexpected url")
if __name__ == "__main__":
""" Test if user can connect to OSF. Opens up a browser window in the form
of a QWebView window to do so."""
# Import QT libraries
app = QtGui.QApplication(sys.argv)
browser = LoginWindow()
auth_url, state = osf.get_authorization_url()
print("Generated authorization url: {}".format(auth_url))
browser_url = QtCore.QUrl.fromEncoded(auth_url)
browser.load(browser_url)
browser.set_state(state)
browser.show()
exitcode = app.exec_()
print("App exiting with code {}".format(exitcode))
sys.exit(exitcode)
基本上,无论我使用什么,QWebView 的 url_changed 事件提供给 check_URL 函数的 url 从 OAuth 服务器返回时从不包含 OAuth 令牌片段对于 redirect_uri(在这个例子中,为了简单起见,我只是重定向到 google)。
有人可以帮我解决这个问题吗?我已经用尽了在何处寻找此问题的解决方案的选择。
这似乎是 Webkit/Safari 中的一个已知错误:
https://bugs.webkit.org/show_bug.cgi?id=24175 https://phabricator.wikimedia.org/T110976#1594914
基本上它不是固定的,因为人们不同意根据 HTTP 规范所需的行为应该是什么。 How do I preserve uri fragment in safari upon redirect? 中描述了一个可能的修复方法,但我无法对此进行测试。
编辑
我设法找到了一个(不太优雅的)解决方法来解决这个问题。我没有使用 QWebView 的 urlChanged 事件(它没有显示 OAuth 服务器完成的 301 重定向),而是使用了 QNetworkAccessManager 的 finished() 事件。这在 any http 请求完成后被触发(对于页面的所有链接内容,如图像、样式表等,因此您必须进行大量过滤)。
所以现在我的代码如下所示:
class LoginWindow(QtWebKit.QWebView):
""" A Login window for the OSF """
# Login event is emitted after successfull login
logged_in = QtCore.pyqtSignal(['QString'])
def __init__(self):
super(LoginWindow, self).__init__()
# Create Network Access Manager to listen to all outgoing
# HTTP requests. Necessary to work around the WebKit 'bug' which
# causes it drop url fragments, and thus the access_token that the
# OSF Oauth system returns
self.nam = self.page().networkAccessManager()
# Connect event that is fired if a HTTP request is completed.
self.nam.finished.connect(self.checkResponse)
def checkResponse(self,reply):
request = reply.request()
# Get the HTTP statuscode for this response
statuscode = reply.attribute(request.HttpStatusCodeAttribute)
# The accesstoken is given with a 302 statuscode to redirect
if statuscode == 302:
redirectUrl = reply.attribute(request.RedirectionTargetAttribute)
if redirectUrl.hasFragment():
r_url = redirectUrl.toString()
if osf.redirect_uri in r_url:
print("Token URL: {}".format(r_url))
self.token = osf.parse_token_from_url(r_url)
if self.token:
self.logged_in.emit("login")
self.close()