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

时间:2016-08-12 00:11:26

标签: python authentication nginx flask

设置:

系统应如下:

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

用户的浏览器应该缓存他们提交的凭据,因此每个新请求都应该能够立即看到该用户属于flask.session并避免进行新的身份验证尝试。

问题:

看似随机,在刷新或导航受保护资源时(成功验证后),有时会出现身份验证弹出窗口,并且需要用户再次进行身份验证。他们可以提交,并在再次提示重新进行身份验证之前加载单个资源。

实施例

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

守则

我不确定我需要在此处链接多少内容才能解决问题,因此我将从负责制作auth_request的nginx文件开始subrequest和后续路由,以及负责发出auth请求和处理会话的两个python文件。

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 or not check_auth(auth.username, auth.password):
            return authenticate()
        return f(*args, **kwargs)
    return decorated

0 个答案:

没有答案