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

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

这让我非常抓狂,并阻止我进行本地开发/测试。
我有一个使用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警告!请求和响应不相等。
我认为我的代码的相关部分是:

  1. @app.route("/", defaults={"path": ""})
  2. @app.route("/<path:path>")
  3. def catch_all(path: str) -> Union[flask.Response, werkzeug.Response]:
  4. if flask.session.get("user"):
  5. return app.send_static_file("index.html")
  6. return flask.redirect("/login")
  7. @app.route("/auth")
  8. def auth() -> Union[Tuple[str, int], werkzeug.Response]:
  9. token = oauth.google.authorize_access_token()
  10. user = oauth.google.parse_id_token(token)
  11. flask.session["user"] = user
  12. return flask.redirect("/")
  13. @app.route("/login")
  14. def login() -> werkzeug.Response:
  15. return oauth.google.authorize_redirect(flask.url_for("auth", _external=True))

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

  1. export FLASK_APP=foo && flask run

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

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

z8dt9xmd1#

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

xwbd5t1u

xwbd5t1u2#

@adamcunnington这里是你如何调试它:

  1. @app.route("/auth")
  2. def auth() -> Union[Tuple[str, int], werkzeug.Response]:
  3. # Check these two values
  4. print(flask.request.args.get('state'), flask.session.get('_google_authlib_state_'))
  5. token = oauth.google.authorize_access_token()
  6. user = oauth.google.parse_id_token(token)
  7. flask.session["user"] = user
  8. return flask.redirect("/")

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

eyh26e7m

eyh26e7m3#

如何修复问题

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

  1. Authlib==0.14.3

对于Fastapi

  1. uvicorn==0.11.8
  2. starlette==0.13.6
  3. Authlib==0.14.3
  4. fastapi==0.61.1

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

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

  1. https://dev.to/rajshirolkar/fastapi-over-https-for-development-on-windows-2p7d
  2. ssl_keyfile="./localhost+2-key.pem" ,
  3. ssl_certfile= "./localhost+2.pem"

-我的代码-

  1. from typing import Optional
  2. from fastapi import FastAPI, Depends, HTTPException
  3. from fastapi.openapi.docs import get_swagger_ui_html
  4. from fastapi.openapi.utils import get_openapi
  5. from starlette.config import Config
  6. from starlette.requests import Request
  7. from starlette.middleware.sessions import SessionMiddleware
  8. from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse
  9. from authlib.integrations.starlette_client import OAuth
  10. # Initialize FastAPI
  11. app = FastAPI(docs_url=None, redoc_url=None)
  12. app.add_middleware(SessionMiddleware, secret_key='!secret')
  13. @app.get('/')
  14. async def home(request: Request):
  15. # Try to get the user
  16. user = request.session.get('user')
  17. if user is not None:
  18. email = user['email']
  19. html = (
  20. f'<pre>Email: {email}</pre><br>'
  21. '<a href="/docs">documentation</a><br>'
  22. '<a href="/logout">logout</a>'
  23. )
  24. return HTMLResponse(html)
  25. # Show the login link
  26. return HTMLResponse('<a href="/login">login</a>')
  27. # --- Google OAuth ---
  28. # Initialize our OAuth instance from the client ID and client secret specified in our .env file
  29. config = Config('.env')
  30. oauth = OAuth(config)
  31. CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
  32. oauth.register(
  33. name='google',
  34. server_metadata_url=CONF_URL,
  35. client_kwargs={
  36. 'scope': 'openid email profile'
  37. }
  38. )
  39. @app.get('/login', tags=['authentication']) # Tag it as "authentication" for our docs
  40. async def login(request: Request):
  41. # Redirect Google OAuth back to our application
  42. redirect_uri = request.url_for('auth')
  43. print(redirect_uri)
  44. return await oauth.google.authorize_redirect(request, redirect_uri)
  45. @app.route('/auth/google')
  46. async def auth(request: Request):
  47. # Perform Google OAuth
  48. token = await oauth.google.authorize_access_token(request)
  49. user = await oauth.google.parse_id_token(request, token)
  50. # Save the user
  51. request.session['user'] = dict(user)
  52. return RedirectResponse(url='/')
  53. @app.get('/logout', tags=['authentication']) # Tag it as "authentication" for our docs
  54. async def logout(request: Request):
  55. # Remove the user
  56. request.session.pop('user', None)
  57. return RedirectResponse(url='/')
  58. # --- Dependencies ---
  59. # Try to get the logged in user
  60. async def get_user(request: Request) -> Optional[dict]:
  61. user = request.session.get('user')
  62. if user is not None:
  63. return user
  64. else:
  65. raise HTTPException(status_code=403, detail='Could not validate credentials.')
  66. return None
  67. # --- Documentation ---
  68. @app.route('/openapi.json')
  69. async def get_open_api_endpoint(request: Request, user: Optional[dict] = Depends(get_user)): # This dependency protects our endpoint!
  70. response = JSONResponse(get_openapi(title='FastAPI', version=1, routes=app.routes))
  71. return response
  72. @app.get('/docs', tags=['documentation']) # Tag it as "documentation" for our docs
  73. async def get_documentation(request: Request, user: Optional[dict] = Depends(get_user)): # This dependency protects our endpoint!
  74. response = get_swagger_ui_html(openapi_url='/openapi.json', title='Documentation')
  75. return response
  76. if __name__ == '__main__':
  77. import uvicorn
  78. uvicorn.run(app, port=8000,
  79. log_level='debug',
  80. ssl_keyfile="./localhost+2-key.pem" ,
  81. ssl_certfile= "./localhost+2.pem"
  82. )

.env文件

  1. GOOGLE_CLIENT_ID=""
  2. GOOGLE_CLIENT_SECRET=""

Google控制台设置

展开查看全部
nfeuvbwi

nfeuvbwi4#

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

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

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

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

ljsrvy3e5#

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

相关问题