我在使用 Redirect Url 并使用 FastApi 从 Python 中的 QueryParams 获取数据时遇到问题。我正在使用 Azure AD 授权授予流程登录,下面是生成
RedirectResponse
的代码
@app.get("/auth/oauth/{provider_id}")
async def oauth_login(provider_id: str, request: Request):
if config.code.oauth_callback is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No oauth_callback defined",
)
provider = get_oauth_provider(provider_id)
if not provider:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Provider {provider_id} not found",
)
random = random_secret(32)
params = urllib.parse.urlencode(
{
"client_id": provider.client_id,
"redirect_uri": f"{get_user_facing_url(request.url)}/callback",
"state": random,
**provider.authorize_params,
}
)
response = RedirectResponse(
url=f"{provider.authorize_url}?{params}")
samesite = os.environ.get("CHAINLIT_COOKIE_SAMESITE", "lax") # type: Any
secure = samesite.lower() == "none"
response.set_cookie(
"oauth_state",
random,
httponly=True,
samesite=samesite,
secure=secure,
max_age=3 * 60,
)
return response
这就是我接收重定向 URL 的地方。
@app.get("/auth/oauth/{provider_id}/callback")
async def oauth_callback(
provider_id: str,
request: Request,
error: Optional[str] = None,
code: Optional[str] = None,
state: Optional[str] = None,
):
if config.code.oauth_callback is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No oauth_callback defined",
)
provider = get_oauth_provider(provider_id)
if not provider:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Provider {provider_id} not found",
)
if not code or not state:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Missing code or state",
)
response.delete_cookie("oauth_state")
return response
当 QueryParams 带有 ? 时,此重定向工作正常。但现在的问题是,来自 Azure AD 的重定向回调带有 #,因此我无法从 Url
获取
Code
和
State
QueryParams
#
http://localhost/callback#code=xxxxxx&state=yyyyyy
关于如何解决此问题的任何想法。
获取服务器端散列标记
#
之后的文本(或键值对,在您的情况下)— URL 中的 #
也称为 URI 片段(请参阅 文本片段) MDN 文档也是如此)——目前不可能。这只是因为片段从不发送到服务器(相关帖子可以在这里和这里找到)。
我建议在 URL 中使用 问号
?
,这是在 HTTP 请求中发送查询参数的正确方法。如果这是一个复杂的路径参数,您可以按照这个答案中描述的方法进行操作,这将允许您捕获整个 URL 路径,包括 /
和 %
等字符,但仍然不是文本/值#
之后。
由于“片段”仅在客户端可用/可访问,因此您可以使用 JavaScript 来获取
hash
接口的 Location
属性,即 window.location.hash
。为此,您可以有一个 /callback_init
端点,该端点最初被调用并用作授权服务器的 redirect_uri
,它将返回相关的 JavaScript 代码来读取片段并将其传递到 URL 的查询字符串中到最后的 /callback
端点。这可以通过将 URL 中的 #
替换为 ?
轻松完成,如下所示:
@app.get('/callback_init', response_class=HTMLResponse)
async def callback_init(request: Request):
html_content = """
<html>
<head>
<script>
var url = window.location.href;
newUrl = url.replace('/callback_init', '/callback').replace("#", "?");
window.location.replace(newUrl);
</script>
</head>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
但是,上述方法不会考虑 URL 中已存在查询参数的可能性(即使这在您的情况下不是问题);因此,人们可以使用以下内容。
下面的示例还考虑到您设置了 cookie,您需要稍后将其删除;因此,这也在下面得到证明。另请注意,为了替换 URL(或向
/callback
端点发送请求),使用 window.location.replace()
,正如 this answer 中所述,不会让当前页面(在导航到下一个)保存在会话历史记录中,这意味着用户将无法使用浏览器中的后退按钮导航回该位置(如果出于某种原因,您必须允许用户返回,则您可以使用 window.location.href
或 window.location.assign()
代替)。
最后,请注意,在查询字符串中发送敏感信息是不安全——请参阅此答案了解有关该主题的更多详细信息。
要触发重定向,请转到您的浏览器并拨打
http://localhost:8000/
。
from fastapi import FastAPI, Request, Response
from fastapi.responses import RedirectResponse, HTMLResponse
from typing import Optional
app = FastAPI()
@app.get("/")
async def main():
redirect_url = 'http://localhost:8000/callback_init?msg=Hello#code=1111&state=2222'
response = RedirectResponse(redirect_url)
response.set_cookie(key='some-cookie', value='some-cookie-value', httponly=True)
return response
@app.get('/callback_init', response_class=HTMLResponse)
async def callback_init(request: Request):
html_content = """
<html>
<head>
<script>
var url = window.location.href;
const fragment = window.location.hash;
const searchParams = new URLSearchParams(fragment.substring(1));
url = url.replace('/callback_init', '/callback').replace(fragment, "");
const newUrl = new URL(url);
for (const [key, value] of searchParams) {
newUrl.searchParams.append(key, value);
}
window.location.replace(newUrl);
</script>
</head>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
@app.get("/callback")
async def callback(
request: Request,
response: Response,
code: Optional[str] = None,
state: Optional[str] = None,
):
print(request.url.query)
print(request.cookies)
response.delete_cookie("some-cookie")
return {"code": code, "state": state}