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

kjthegm6  于 2023-10-16  发布在  Docker
关注(0)|答案(5)|浏览(122)

这让我非常抓狂,并阻止我进行本地开发/测试。
我有一个使用authlib的flask应用程序(仅限客户端功能)。当用户访问我的主页时,我的flask后端会将他们重定向到/login,然后再重定向到Google Auth。然后Google Auth将它们发布回我的应用的/auth端点。
几个月来,我一直遇到authlib.integrations.base_client.errors.MismatchingStateError的特殊问题:mismatching_state:CSRF警告!请求和响应不相等。这感觉就像一个cookie的问题,大多数时候,我只是打开一个新的浏览器窗口或隐身或尝试清除缓存,最终,它的工作排序。
然而,我现在在Docker容器中运行完全相同的应用程序,并且在某个阶段这是有效的。我不知道我已经改变了什么,但每当我浏览到localhost/或127.0.0.1/并通过auth过程(每次清除cookie以确保我没有自动登录),我不断重定向回localhost/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))

我将非常感谢任何帮助。
当我在本地运行时,我开始:

export FLASK_APP=foo && flask run

当我在Docker容器中运行时,我开始:

.venv/bin/gunicorn -b :8080 --workers 16 foo
z8dt9xmd

z8dt9xmd1#

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

xwbd5t1u

xwbd5t1u2#

@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("/")

检查request.argssession中的值以了解发生了什么。
可能是因为Flask session not persistent across requests in Flask app with Gunicorn on Heroku

eyh26e7m

eyh26e7m3#

如何修复问题

安装旧版本authlib,它可以很好地与fastapi和flask配合使用

Authlib==0.14.3

对于Fastapi

uvicorn==0.11.8
starlette==0.13.6
Authlib==0.14.3
fastapi==0.61.1

* 如果使用本地主机进行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"

-我的代码-

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"
                )

.env文件

GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""

Google控制台设置

nfeuvbwi

nfeuvbwi4#

我在FastAPI中遇到了同样的问题。对我有用的是在-sessionMiddlewareoauth.register-位置设置相同的密钥:
In respective python module:

# Set up OAuth
config_data = {'GOOGLE_CLIENT_ID': GOOGLE_CLIENT_ID, 'GOOGLE_CLIENT_SECRET': GOOGLE_CLIENT_SECRET}
starlette_config = Config(environ=config_data)
oauth = OAuth(starlette_config)
oauth.register(
    name='google',
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid email profile'},
    authorize_state='Oe_Ef1Y38o1KSWM2R-s-Kg',### this string should be similar to the one we put while add sessions middleware
)

In main.py (or wherever you declare app = FastAPI()):

app.add_middleware(SessionMiddleware, secret_key="Oe_Ef1Y38o1KSWM2R-s-Kg")
ljsrvy3e

ljsrvy3e5#

我通过在浏览器中硬刷新应用程序解决了这个问题。看起来我已经在代码中更改了一些客户端ID并重新启动了应用程序,但我仍然在浏览器中从我自己的应用程序的过时版本中单击登录按钮。

相关问题