【问题标题】:Managing authentication session between Android and Flask-SocketIO管理 Android 和 Flask-SocketIO 之间的身份验证会话
【发布时间】:2019-01-28 07:51:12
【问题描述】:

我目前正在构建一个 Flask Web 应用程序,它混合使用 HTTP 请求(用于基本身份验证和网页)和 Flask-SocketIO(用于实时数据传输和请求)。我使用 HTTP 基本身份验证来跟踪用户登录会话,因为它已经很成熟并且内置了身份验证框架。

对于网页,我通过 HTTP 身份验证标头传递用户名和密码。然后由服务器读取,针对数据库进行身份验证,最后在 Flask 会话中设置一些具有数据库用户 ID 的元变量。我正在使用 Flask-Session 模块来管理基于文件系统的会话,以便 HTTP 请求和 Flask-SocketIO 都可以访问它。以下是该过程的一些相关代码:

app.py

app = Flask(__name__)
app.config['SESSION_TYPE'] = 'filesystem'
Session(app)
socketio = SocketIO(app, manage_session=False)

登录功能

def authenticate(username, password):
    uid = users.getUIDByUsername(username) //'users' references the database object
    valid = users.checkUser(uid, password)
    if valid:
        permission = users.getPermissionLevel(uid)
        session["uid"] = uid //'session' references the Flask session
        session["permission"] = permission
    return valid

HTTP 请求的包装注释

def requires_auth(f):
    @wraps(f)
    def auth_check(*args, **kwargs):
        if users.getAdminCount() == 0:
            return redirect(url_for('auth.adminRegister'))
        if not 'uid' in session or not session['uid']:
            return redirect(url_for('auth.login'))
        return f(*args, **kwargs)
    return auth_check

SocketIO 事件的包装注解

def permissionSocket(p=users.MEMBER):
    def requires_permission(f):
        @wraps(f)
        def permission_check(*args, **kwargs):
            if 'uid' in session and session['uid']:
                permission = users.getPermissionLevel(session['uid'])
                if permission >= p:
                    return f(*args, **kwargs)
                log.info("Permission denied to user " + str(session['uid']) + " over websockets.")
                return
            log.info("Permission denied to unidentified user.")
            return
        return permission_check
    return requires_permission

在处理 Web 浏览器时,这对于 HTTP 请求和 SocketIO 事件都非常有效。但是,此应用程序也有一个适用于 Android 的兄弟。我还需要能够对与 Android 客户端的会话进行身份验证,以维护 Flask 服务器端点的安全性。但是,我不确定如何在 Android 上进行 HTTP 身份验证,同时还要为后续的 SocketIO 连接维护会话。关于 Android 和 Flask-SocketIO 的文档非常少,因为 Google 的结果被 Node.js 资源所覆盖。

到目前为止,我已经尝试从 HTTP 身份验证请求返回的“Set-Cookie”cookie 中读取会话 ID,如下所示:

HTTPURLConnection auth = (HttpURLConnection) new URL(endpoint).openConnection();
auth.setDoOutput(true);
auth.setDoInput(true);
auth.setUseCaches(false);

auth.setRequestProperty("Authorization", "Basic " +
        Base64.encodeToString("admin:beehive".getBytes(), 
        Base64.URL_SAFE|Base64.NO_WRAP));
auth.connect();
sessionCookie = auth.getHeaderField("Set-Cookie");
System.out.println("Station Cookie: " + sessionCookie);

这取得了一些成功;我能够获得看似有效的会话 ID (c53d8f23-e960-4ff5-acf0-89238206325d)。但是,我不知道如何将它应用到我的 SocketIO 连接并且仍然获得权限被拒绝的连接。

这是我在 Android 上处理 SocketIO 连接的方式:

//Imports
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Manager;
import com.github.nkzawa.socketio.client.Socket;

//Connection code
IO.Options opts = new IO.Options();
opts.query = "session=" + sessionCookie + ";";
Manager manager = new Manager(new URI("http://" + ip), opts);
socket = manager.socket("/socket");

对我来说,解决这个问题的最佳方法是什么?我应该为 Android 客户端创建一个单独的令牌类型系统,还是有办法让 Android SocketIO 客户端识别 HTTP/Flask 会话?

更新

免责声明:取得了可喜的结果,但仍无法正确验证。

在对源代码进行了一番挖掘之后,我发现了一些我在 README 中遗漏的东西!您可以使用有权访问请求标头对象的传输注册事件侦听器。可以这样做:

sessionCookie = decodeCookie(auth.getHeaderField("Set-Cookie"));
System.out.println("##############################" + sessionCookie);

Manager manager = new Manager(new URI("http://" + url));
socket = manager.socket("/socket");
socket.io().on(Manager.EVENT_TRANSPORT, (Object... transportArgs) -> {
    Transport transport = (Transport) transportArgs[0];

    transport.on(Transport.EVENT_REQUEST_HEADERS, (Object... headerArgs) -> {
        if (sessionCookie != null) {
            List<String> vars = new ArrayList<>();
            for (Map.Entry<String, String> entry : sessionCookie.entrySet()) {
                vars.add(entry.getKey() + "=" + entry.getValue());
                if (entry.getKey().equalsIgnoreCase("session")) {
                    vars.add("io=" + entry.getValue());
                }
            }
            @SuppressWarnings("unchecked")
            Map<String, List<String>> headers = (Map<String, List<String>>) headerArgs[0];
            headers.put("Cookie", vars);
        }
    });
});

从我的源代码研究中,我只发现了在 EngineIO Java 实现中发出此事件的一个地方。当为新的 Transport 对象调用 doOpen 方法时,会发出此事件并带有一个空的标头对象。该对象在被任何事件处理程序加载后,然后被传递给 OkHTTP3 的请求构建器,这是该 EngineIO 实现使用的请求库。

这是我的一些调试,

会话 Cookie 打印(Android 端):

{Path=/, session=ade41462-8fd3-4a65-9e23-3c0b540ca8da, Expires=Fri, 28-Sep-2018 05:42:04 GMT, HttpOnly=}

我通过在 permissionSocket 注释处理程序中添加 request.headers 的打印来确认标头正在返回到 Flask-SocketIO 服务器。上面的监听器执行后 request.headers 就等于这个了:

Socket Cookie: Cookie: Path=/; session=ade41462-8fd3-4a65-9e23-3c0b540ca8da; io=ade41462-8fd3-4a65-9e23-3c0b540ca8da; Expires=Fri, 28-Sep-2018 05:42:04 GMT; HttpOnly=

User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.0.0; SM-G950U Build/R16NW)

Host: 192.168.2.12

Connection: Keep-Alive

Accept-Encoding: gzip

所以,从那看来,这种方法似乎是成功的。会话 ID 已通过 cookie 成功保存。但是,我仍然从 permissionSocket 注释处理程序获得权限被拒绝,这意味着 Flask-Session 会话对象的值没有被持久化。我不太熟悉选择缓存会话对象的内部工作原理,所以我真的不知道我错过了什么。

【问题讨论】:

    标签: java android python session flask-socketio


    【解决方案1】:

    要使您的 Android 客户端可以访问用户会话,您需要做的就是确保会话 cookie 与 Socket.IO 连接请求一起发送。遗憾的是,此客户端似乎没有发送额外标头或 cookie 的选项(此限制在 issue 中引用)。

    我不确定您是否有很多选择。您可以修改此客户端以允许传递额外的标头,或者您可以使用不同的身份验证机制。此客户端支持自定义参数的唯一方法是通过查询字符串,因此您可以通过这种方式传递令牌。

    【讨论】:

    • 我害怕那个。您是否碰巧知道任何支持会话标头的 Java socketio 客户端?如果没有,我会看一下协议,看看我是否可以自己扩展它。否则,令牌似乎是一个很好的解决方案,但它并不理想,因为我已经有一个不同的系统。
    • 是的,并且在 URL 的查询字符串中传递令牌不太安全,因为 URL 会被 Web 服务器缓存。不幸的是,我不知道任何其他 Java 客户端。您需要做的是查看query 选项是如何实现的,并为标题添加一个类似的选项。
    • 好的,我会用我认为成功的任何内容更新问题。感谢您的帮助!
    • 我在原始问题中添加了一个新的更新部分,其中包含我的一些发现。不幸的是,我仍然没有让它工作,但是有一些有希望的结果可能会激发你的解决方案。我将非常感谢您提供的任何智慧。
    • @EvanMcCoy 根据您的代码,您似乎将session cookie 发送回服务器,cookie 名称为io?你需要把原名session发回去,然后我想服务器就可以恢复它并且可以做auth了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    • 2013-04-03
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多