我目前正在构建一个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会话对象的值不会被保留。我对选择缓存的会话对象的内部操作不是很熟悉,所以我真的不知道自己缺少什么。
答案 0 :(得分:0)
要使用户会话可被您的Android客户端访问,您需要做的就是确保会话cookie与Socket.IO连接请求一起发送。遗憾的是,此客户端似乎没有选择发送额外的标头或cookie(在issue中引用了此限制)。
我不确定您在这里是否有很多选择。您可以修改此客户端以允许传递额外的标头,否则您可以使用其他身份验证机制。此客户端支持自定义参数的唯一方法是通过查询字符串,因此您可以以这种方式传递令牌。