MismatchingStateError: mismatching_state: CSRF 警告!请求和响应中的状态不相等

ada*_*ton 2 python flask docker authlib

这让我非常疯狂,并阻止我进行本地开发/测试。

我有一个使用 authlib(仅限客户端功能)的烧瓶应用程序。当用户点击我的主页时,我的 Flask 后端会将他们重定向到 /login,然后又重定向到 Google Auth。Google Auth 然后将它们发回我的应用程序的 /auth 端点。

几个月来,我一直在遇到 authlib.integrations.base_client.errors.MismatchingStateError: mismatching_state: CSRF 警告的临时问题!状态在请求和响应中不相等。感觉就像一个 cookie 问题,大多数时候,我只是打开一个新的浏览器窗口或隐身或尝试清除缓存,最终,它有点工作。

但是,我现在在 docker 容器内运行完全相同的应用程序,并且在一个阶段这是有效的。我不知道我做了什么改变,但是每当我浏览到 localhost/ 或 127.0.0.1/ 并通过身份验证过程(每次清除 cookie 以确保我没有自动登录)时,我经常被重定向回 l​​ocalhost /auth?state=blah blah blah 我遇到了这个问题:authlib.integrations.base_client.errors.MismatchingStateError: mismatching_state: CSRF 警告!状态在请求和响应中不相等。

我认为我的代码的相关部分是:

@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def catch_all(path: str) -> Union[flask.Response, werkzeug.Response]:
    if flask.session.get("user"):
        return app.send_static_file("index.html")
    return flask.redirect("/login")


@app.route("/auth")
def auth() -> Union[Tuple[str, int], werkzeug.Response]:
    token = oauth.google.authorize_access_token()
    user = oauth.google.parse_id_token(token)
    flask.session["user"] = user
    return flask.redirect("/")


@app.route("/login")
def login() -> werkzeug.Response:
    return oauth.google.authorize_redirect(flask.url_for("auth", _external=True))
Run Code Online (Sandbox Code Playgroud)

我将不胜感激任何帮助。

当我在本地运行时,我从:

export FLASK_APP=foo && flask run
Run Code Online (Sandbox Code Playgroud)

当我在 docker 容器中运行时,我从:

.venv/bin/gunicorn -b :8080 --workers 16 foo
Run Code Online (Sandbox Code Playgroud)

ada*_*ton 6

问题是 SECRET_KEY 是使用 os.random 填充的,这为不同的工作人员产生了不同的值,因此无法访问会话 cookie。

  • 将代码从“传统”部署 (flask+uwsgi+nginx) 移动到 docker(gunicorn) 后,我遇到了“CSRF 令牌无效”问题。这是唯一有效的方法。 (2认同)

Has*_*eeb 5

我如何解决我的问题

安装旧版本的 authlib 它可以与 fastapi 和 Flask 一起正常工作

Authlib==0.14.3
Run Code Online (Sandbox Code Playgroud)

对于 Fastapi

uvicorn==0.11.8
starlette==0.13.6
Authlib==0.14.3
fastapi==0.61.1
Run Code Online (Sandbox Code Playgroud)

重要的是,如果使用本地主机进行 Google 身份验证,请确保获得 https 证书

安装 Chocolatey 并设置 https 查看本教程

https://dev.to/rajshirolkar/fastapi-over-https-for-development-on-windows-2p7d

ssl_keyfile="./localhost+2-key.pem" ,
 ssl_certfile= "./localhost+2.pem"
Run Code Online (Sandbox Code Playgroud)

--- 我的代码 ---

from typing import Optional
from fastapi import FastAPI, Depends, HTTPException
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi

from starlette.config import Config
from starlette.requests import Request
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse

from authlib.integrations.starlette_client import OAuth

# Initialize FastAPI
app = FastAPI(docs_url=None, redoc_url=None)
app.add_middleware(SessionMiddleware, secret_key='!secret')




@app.get('/')
async def home(request: Request):
    # Try to get the user
    user = request.session.get('user')
    if user is not None:
        email = user['email']
        html = (
            f'<pre>Email: {email}</pre><br>'
            '<a href="/docs">documentation</a><br>'
            '<a href="/logout">logout</a>'
        )
        return HTMLResponse(html)

    # Show the login link
    return HTMLResponse('<a href="/login">login</a>')


# --- Google OAuth ---


# Initialize our OAuth instance from the client ID and client secret specified in our .env file
config = Config('.env')
oauth = OAuth(config)

CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
oauth.register(
    name='google',
    server_metadata_url=CONF_URL,
    client_kwargs={
        'scope': 'openid email profile'
    }
)


@app.get('/login', tags=['authentication'])  # Tag it as "authentication" for our docs
async def login(request: Request):
    # Redirect Google OAuth back to our application
    redirect_uri = request.url_for('auth')
    print(redirect_uri)

    return await oauth.google.authorize_redirect(request, redirect_uri)


@app.route('/auth/google')
async def auth(request: Request):
    # Perform Google OAuth
    token = await oauth.google.authorize_access_token(request)
    user = await oauth.google.parse_id_token(request, token)

    # Save the user
    request.session['user'] = dict(user)

    return RedirectResponse(url='/')


@app.get('/logout', tags=['authentication'])  # Tag it as "authentication" for our docs
async def logout(request: Request):
    # Remove the user
    request.session.pop('user', None)

    return RedirectResponse(url='/')


# --- Dependencies ---


# Try to get the logged in user
async def get_user(request: Request) -> Optional[dict]:
    user = request.session.get('user')
    if user is not None:
        return user
    else:
        raise HTTPException(status_code=403, detail='Could not validate credentials.')

    return None


# --- Documentation ---


@app.route('/openapi.json')
async def get_open_api_endpoint(request: Request, user: Optional[dict] = Depends(get_user)):  # This dependency protects our endpoint!
    response = JSONResponse(get_openapi(title='FastAPI', version=1, routes=app.routes))
    return response


@app.get('/docs', tags=['documentation'])  # Tag it as "documentation" for our docs
async def get_documentation(request: Request, user: Optional[dict] = Depends(get_user)):  # This dependency protects our endpoint!
    response = get_swagger_ui_html(openapi_url='/openapi.json', title='Documentation')
    return response


if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app,    port=8000,
                log_level='debug',
                ssl_keyfile="./localhost+2-key.pem" ,
                ssl_certfile= "./localhost+2.pem"
                )
Run Code Online (Sandbox Code Playgroud)

.env 文件

GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
Run Code Online (Sandbox Code Playgroud)

谷歌控制台设置

在此输入图像描述 在此输入图像描述


lep*_*ure 3

@adamcunnington 这是调试它的方法:

@app.route("/auth")
def auth() -> Union[Tuple[str, int], werkzeug.Response]:
    # Check these two values
    print(flask.request.args.get('state'), flask.session.get('_google_authlib_state_'))

    token = oauth.google.authorize_access_token()
    user = oauth.google.parse_id_token(token)
    flask.session["user"] = user
    return flask.redirect("/")
Run Code Online (Sandbox Code Playgroud)

检查输入的值request.argssession查看发生了什么情况。

也许是因为Flask 会话在 Heroku 上使用 Gunicorn 的 Flask 应用程序中的请求之间不持久