管理Android和Flask-SocketIO之间的身份验证会话

时间:2018-08-22 05:19:21

标签: java android python session flask-socketio

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

对于网页,我通过HTTP身份验证标头传递用户名和密码。然后由服务器读取它,对数据库进行身份验证,最后在Flask Session中设置一些具有用户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会话?

更新

免责声明:有令人鼓舞的结果,但仍未正确进行身份验证。

在仔细研究了源代码之后,我发现了自述文件中缺少的一些内容!您可以向有权访问请求标头对象的传输注册一个事件侦听器。可以这样做:

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会话对象的值不会被保留。我对选择缓存的会话对象的内部操作不是很熟悉,所以我真的不知道自己缺少什么。

1 个答案:

答案 0 :(得分:0)

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

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