如何防止使用nginx的auth_request指令和烧瓶应用程序看似随机的重新authentication提示?

设置:

系统应该如下:

  • 有两个AWS负载均衡器,每个负载均运行Docker容器。 一个是私人负载均衡器,它包含一个任意的服务,应该保护它免受未经授权的访问; 另一个是面向公众的负载均衡器,它包含authentication门户。
    • Docker容器内部(在公共负载平衡器上)是一个监听80端口的Nginx服务器,以及8000端口上的Flask应用。
    • Nginx使用auth_request指令向Flask应用程序发出一个子请求(在请求中传递凭证作为标题),期待200或401响应。
      • Flask应用程序从请求中提取证书并检查该用户名是否已经是flask.session一部分。 如果是这样,它立即返回200响应; 否则,它会尝试对外部真相来源进行authentication。 如果成功,则将该用户添加到flask.session并且该应用程序返回200响应; 否则,它将返回一个401响应。
      • 还有一个检查到期; 例如,如果用户login超过一个小时,应用程序应该将其从flask.session删除,并返回401响应。 用户然后可以进行新的login请求。
    • 如果响应为200,则将stream量路由到专用负载均衡器。

用户的浏览器应该caching他们提交的证书,所以每个新的请求应该能够立即看到用户是flask.session一部分,并避免进行新的身份validation尝试。

问题:

在随机刷新或导航受保护资源时(成功validation后),有时会出现身份validationpopup窗口,用户需要再次进行validation。 他们可以提交,单个资源将被加载,然后再次提示重新进行身份validation。

例:

受保护资源是由索引页,CSS文件和三个图像组成的静态网站。 初始authentication后,用户多次刷新页面。 其中一个时间,身份validation提示将被触发。 他们将再次input他们的凭据,索引页面将加载。 他们会再次提示CSS文件,然后再为每个图像。

代码

我不知道有多less事情需要链接到这里来解决这个问题,所以我将从负责创buildauth_request子请求和后续路由的nginx文件开始,并且负责创build两个python文件进行身份validation请求和处理会话。

nginx.default

 server { listen 80; server_name _; location / { auth_request /auth; proxy_pass {{resource_endpoint}}; } location /auth { proxy_pass {{auth_backend}}; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; } location /logout { proxy_pass {{auth_backend}}; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; } } 

app.py

 import flask import auth0 import os app = flask.Flask(__name__) app.secret_key = os.getenv("SECRET_KEY", 'sooper seekrit') @app.route('/auth', methods=['GET']) @auth0.requires_auth def login(): print("logging in") resp_text = ( "Authentication successful." ) return flask.Response( response=resp_text, status=200, ) @app.route('/logout', methods=['GET']) def logout(): # To successfully invalidate a user, we must both clear the Flask session # as well as direct them to a '401: Unauthorized' route. # http://stackoverflow.com/questions/233507/how-to-log-out-user-from-web-site-using-basic-authentication print("logging out") flask.session.clear() return flask.Response( response='Logout', status=401, ) if __name__ == "__main__": app.debug = True from gevent.wsgi import WSGIServer http_server = WSGIServer(('', 8000), app) http_server.serve_forever() 

auth0.py

 import json import requests import flask import datetime import os from functools import wraps def check_auth(username, password): if 'username' in flask.session: import pdb; pdb.set_trace() if 'logged_in' in flask.session: now = datetime.datetime.now() expiry_window = datetime.timedelta( minutes=int(os.getenv('AUTH0_EXPIRY')) ) if flask.session['logged_in'] >= now - expiry_window: return True else: flask.session.pop('username', None) data = { 'client_id': os.getenv("AUTH0_CLIENT_ID"), 'username': username, 'password': password, 'id_token': '', 'connection': os.getenv("AUTH0_CONNECTION"), 'grant_type': 'password', 'scope': 'openid', 'device': '' } resp = requests.post( url="https://" + os.getenv('AUTH0_DOMAIN') + "/oauth/ro", data=json.dumps(data), headers={"Content-type": "application/json"} ) if 'error' in json.loads(resp.text): return False else: flask.session['username'] = username flask.session['logged_in'] = datetime.datetime.now() return True def authenticate(): return flask.Response( 'Could not verify your access level for that URL.\n' 'You have to login with proper credentials', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}, ) def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = flask.request.authorization if not auth: if not auth or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) return decorated