websocketpp的流媒体微服务使用安全

Posted qianbo_insist

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了websocketpp的流媒体微服务使用安全相关的知识,希望对你有一定的参考价值。

websocketpp

websocketpp不失为一个具有良好设计的websocket的好使用的lib。我们在使用媒体服务的过程中,可以使用websocketpp制作微服务,提供tcp stream流服务长链接系统,最大的两个问题1 是记录客户端数据,2 是安全问题。这里使用websocket的原因是提供流媒体服务。

鉴权安全性:

  first, 客户端需要像鉴权服务器使用用户名和密码
  second,客户端必须发送授信flag标识,flag标识为硬件标识,客户端必须首先拿到这个标识,标识为物理隔离,物理隔离概念并不是一定是隔绝,而是带有硬件相关性。

1 如何让websocket服务保证链接的客户是正确的客户,以及是常用的客户,常用的意思是一般一直是这些客户。服务端接收的用户名和密码是添加进去的,内部系统在每隔一定的事件修改密码,服务端自动化做更改当然也是可以的,只有内部人员可以知道密码更改。客户端在登录以后获取到用户名和密码的变种ID,用来传输到ws服务
2 授信flag和硬件相关,客户端在配置文件或者html里面可以直接更改,外部系统复制flag是没有用的,除非同时获取了用户名和密码,但是服务器会记录所有登录的ip和端口等等相关客户端数据。

1 如何记录客户端数据

不像javascript数据,数据结构可以随时改变,c++的数据结构需要在编译时确定,当然我们可以使用配置或者脚本语言来做一些灵活的配置,有一个问题,客户端链接上来后,我们如何标记客户端,记录一些客户端船上来的值?有三种方式,a) 自己准备一个hash,每次存和取都从hash里面进行
map<connection, data>
数据结构data 就是我们需要的,比如

struct connection_data 
	enum_flag m_flag = enum_null;
	std::string m_app;
	std::string m_id;
	std::string m_user;
	//第几个线程
	int m_nthread = -1;
	std::string m_address;
	uint32_t m_heart = 0;
;

这个方法的问题是每次都要去从hash表里找。

b)直接修改源代码,这个没有大问题,代码你掌握,直接加在源码里面,这种暴力方法是可行的。

c) 实际上,本身websocketpp就提供了这种方法,server 本身就是一个模板类,在声明的时候把connection_data 放到模板参数里面

struct custom_config : public websocketpp::config::asio 
	// pull default settings from our core config
	typedef websocketpp::config::asio core;

	typedef core::concurrency_type concurrency_type;
	typedef core::request_type request_type;
	typedef core::response_type response_type;
	typedef core::message_type message_type;
	typedef core::con_msg_manager_type con_msg_manager_type;
	typedef core::endpoint_msg_manager_type endpoint_msg_manager_type;
	typedef core::alog_type alog_type;
	typedef core::elog_type elog_type;
	typedef core::rng_type rng_type;
	typedef core::transport_type transport_type;
	typedef core::endpoint_base endpoint_base;

	// Set a custom connection_base class
	typedef connection_data connection_base;

;
typedef websocketpp::server<custom_config> c_server;

这样,我们在使用的时候,事件on_open中



void c_ws_server::on_open(connection_hdl hdl)

	std::error_code ec;
	c_server::connection_ptr connptr = m_server.get_con_from_hdl(hdl,ec);
	if (connptr == nullptr || ec)
	
		if(ec)
			std::cout << "exception: " << ec.message() << std::endl;
		return;
	
	
	std::string &url = connptr->get_uri()->str();
	if (judge_open(url, connptr) == 0)
	
	//这里可以直接使用
		connptr->m_id = url;
		connptr->m_address = connptr->get_remote_endpoint();
		//connptr->get_re
		m_proxy->node_into(connptr);
	

2、鉴权问题

服务器只能被一些用户访问,同时限制客户端个数,客户端的url必须传输这些密文。

int c_ws_server::judge_open(std::string &url, c_server::connection_ptr ptr)

	s_url surl;
	std::map<string, string> paras;
	split_url(url, surl, paras);

	if (surl.m_app == NULL || surl.m_id == NULL || surl.m_para == NULL)
	
		close(-1, ptr);
		return -1;
	

	auto iter_user = paras.find("user");
	if (iter_user == paras.end())
	
		close(-2, ptr);
		return -2;
	
	auto iter_flag = paras.find("flag");
	if (iter_flag == paras.end())
	
		close(-3, ptr);
		return -3;
	
	ptr->m_app = surl.m_app;
	ptr->m_id  = surl.m_id;
	ptr->m_user = iter_user->second;
	ptr->m_flag = iter_flag->second.compare("pull") == 0 ? enum_wpull_join : enum_wpush_join;
	//成功
	return 0;

我们在onopen事件中要求ws 客户端传入权限,这个对应的码进行加密后传入后台,后台对应解码的flag 需要解成受信服务器id 和服务器的记录id进行对应,否则就无法链接,链接直接被断开,用户的用户名和密码同时被传输到服务端,所以客户端是需要在传输url之前问鉴权服务要到用户名和密码。

安全问题和漏洞:
如果事先复制授信flag 可行否?
答:
授信flag 是和服务器端的硬件相对应的,各个服务器端的资源并不相同,流并不一样,所以保证了资源安全和授信flag的独立性。

3、websocket html端模拟

<!DOCTYPE HTML>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <div id="sse">
        <a href="javascript:WebSocketTest()">运行 WebSocket</a>
    </div>
      <div id="imgDiv"></div>
      <script type="text/javascript">
        function WebSocketTest() 
            if ("WebSocket" in window) 
                // alert("您的浏览器支持 WebSocket!");

                // 打开一个 web socket
                var ws = new WebSocket("ws://127.0.0.1:9002/live/1001?flag=s&user=qianbo");
                console.log(ws);
                ws.onopen = function (evt) 
                    // Web Socket 已连接上,使用 send() 方法发送数据
                    console.log("connected")
                    let obj = JSON.stringify(
                        type: 'login',
                        data: 
                            roomId: '1',
                            userId: '2',
                            userName: 'lrc2'
                        
                    )
                    ws.send(obj);
                    //alert("数据发送中...");
                ;

                ws.onmessage = function (evt) 
                    if (typeof (evt.data) == "string") 
                        //textHandler(JSON.parse(evt.data));  
                     else 
                        var reader = new FileReader();
                        reader.onload = function (evt) 
                            if (evt.target.readyState == FileReader.DONE) 
                                var url = evt.target.result;
                                console.log(url);
                                //img.src = url;// "bg1.jpg";
                                var img = document.getElementById("imgDiv");
                                img.innerHTML = "<img src = " + url + " />";
                            
                        
                        reader.readAsDataURL(evt.data);
                    
                ;

                ws.onclose = function () 
                    // 关闭 websocket
                    alert("连接已关闭...");
                ;
             else 
                // 浏览器不支持 WebSocket
                alert("您的浏览器不支持 WebSocket!");
            
        
    </script>
</body>

</html>

数据库记录信息

使用sqlite来记录所有客户端信息,sqlite本身为进程内服务器,不受网络攻击

头文件
#ifndef _DB_SET_SQLITE_
#define _DB_SET_SQLITE_

#include <string>
using namespace std;


typedef struct db_content

	string key;
	string app;
	string channel;
	string route;
	string content;
	string push_time;
db_content;


void *db_open(const char *db_path);
void db_close(void **db_handle);

enum  API_OP_GET, API_OP_SET, API_OP_DEL ;

void db_op(struct mg_connection *nc, const struct http_message *hm,
	const struct mg_str *key, void *db, int op);

#endif 


cpp
#include "dbstorage.h"
#include "sqlite3.h"
#include <time.h>


void time_get(char * buf)

	time_t rawtime;
	struct tm * timeinfo;
	time(&rawtime);
	timeinfo = localtime(&rawtime);
	strcpy(buf, asctime(timeinfo));



void *db_open(const char *db_path) 
	sqlite3 *db = NULL;
	if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
		SQLITE_OPEN_FULLMUTEX,
		NULL) == SQLITE_OK) 
		char *sql = "CREATE TABLE IF NOT EXISTS log([key] TEXT, [val] TEXT,[category] TEXT, [infotime] TimeStamp NOT NULL DEFAULT(datetime('now', 'localtime'))";
		sqlite3_exec(db, sql, 0, 0, 0);
	
	return db;


void db_close(void **db_handle) 
	if (db_handle != NULL && *db_handle != NULL) 
		sqlite3_close((sqlite3*)*db_handle);
		*db_handle = NULL;
	


#define str(x) x.c_str()
#define len(x) strlen(str(x))
#define start(n) int n = 0

#define sqlbindstr(name,n) n++;sqlite3_bind_text(stmt,n,str(name), len(name), SQLITE_STATIC)

static void op_set(db_content * cn, sqlite3 *db) 
	sqlite3_stmt *stmt = NULL;

	//time_get(time);

	if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO info VALUES (?, ?, ? ,? ,? ,?);", -1,
		&stmt, NULL) == SQLITE_OK) 
		start(n);
		sqlbindstr(cn->key, n);
		sqlbindstr(cn->app, n);
		sqlbindstr(cn->channel, n);
		sqlbindstr(cn->route, n);
		sqlbindstr(cn->content, n);
		sqlbindstr(cn->push_time, n);
		sqlite3_step(stmt);
		sqlite3_finalize(stmt);
	



static db_content *op_get(char * key, sqlite3 *db) 
	sqlite3_stmt *stmt = NULL;
	const char *data = NULL;
	int result;

	if (sqlite3_prepare_v2(db, "SELECT app,channel,route,content,push_time FROM info WHERE key = ?;", -1, &stmt,
		NULL) == SQLITE_OK) 
		sqlite3_bind_text(stmt, 1, key, strlen(key), SQLITE_STATIC);
		result = sqlite3_step(stmt);
		db_content *pdcn = new db_content();
		if ((result == SQLITE_OK || result == SQLITE_ROW))
		

			char *data = (char *)sqlite3_column_text(stmt, 0);
			if (data != NULL)
				pdcn->app = data;
			data = (char *)sqlite3_column_text(stmt, 1);
			if (data != NULL)
				pdcn->channel = data;

			data = (char *)sqlite3_column_text(stmt, 2);
			if (data != NULL)
				pdcn->route = data;
			data = (char *)sqlite3_column_text(stmt, 3);
			if (data != NULL)
				pdcn->content = data;
			data = (char *)sqlite3_column_text(stmt, 4);
			if (data != NULL)
				pdcn->push_time = data;

		
		else
		
			return NULL;
		
		sqlite3_finalize(stmt);
	
	else
	
		return NULL;
	


static int op_del(string key, void *db) 
	sqlite3_stmt *stmt = NULL;
	int result;


	sqlite3 *dbs = (sqlite3*)(db);
	if (sqlite3_prepare_v2(dbs, "DELETE FROM info WHERE key = ?;", -1, &stmt,
		NULL) == SQLITE_OK)
	
		sqlite3_bind_text(stmt, 1, key.c_str(), strlen(key.c_str()), SQLITE_STATIC);
		result = sqlite3_step(stmt);
		if (result == SQLITE_OK || result == SQLITE_ROW) 
		
		else 
		
		sqlite3_finalize(stmt);
		return 0;
	
	else
	
		return -1;
		//mg_printf(nc, "%s",
		//          "HTTP/1.1 500 Server Error\\r\\n"
		//          "Content-Length: 0\\r\\n\\r\\n");
	


void db_op(struct mg_connection *nc, const struct http_message *hm,
	const struct mg_str *key, void *db, int op) 
	switch (op) 
	case API_OP_GET:
		//op_get(nc, hm, key, db);
		break;
	case API_OP_SET:
		//op_set(nc, hm, key, db);
		break;
	case API_OP_DEL:
		//op_del(nc, hm, key, db);
		break;
	default:
	/*	mg_printf(nc, "%s",
			"HTTP/1.0 501 Not Implemented\\r\\n"
			"Content-Length: 0\\r\\n\\r\\n");*/
		break;
	

分析到此结束,读者有兴趣可以加入作者一起讨论

以上是关于websocketpp的流媒体微服务使用安全的主要内容,如果未能解决你的问题,请参考以下文章

5个规则,确保你的微服务优化运行

记一次不太聪明的微服务优化方案

Day916.基于JWT令牌的安全认证架构 -SpringBoot与K8s云原生微服务实践

Day915.安全认证架构演进:微服务阶段 -SpringBoot与K8s云原生微服务实践

Day914.安全认证架构演进:单块阶段 -SpringBoot与K8s云原生微服务实践

微服务实战:选择微服务部署策略