nginx rtmp module新增鑑權機制
目前直播平臺非常的火爆。當前有不少的流媒體CDN,基於rtmp,http-flv和hls協議的。
也可以自己搭建私有的流媒體伺服器,目前比較常見的有:Live555,EasyDarwin,Red5,DSS,Wowza,nginx-rtmp
這裡重點講解開源的nginx-rtmp伺服器。但是流媒體伺服器最大的一個問題就是防盜鏈和鑑權問題,如何防止流媒體伺服器被第三方應用免費使用。
所以需要給nginx-rtmp新增鑑權機制,大致的方案如下:
啟動一個鑑權服務。提供 get_user_token 和 auth 兩個api。
給每個主播或者觀看直播的人員分配user id 和 token,當填寫推流和拉流url需要填寫鑑權引數,比如:
推流地址:
格式:推流名稱?使用者ID&使用者Token
拉流地址:
rtmp://192.168.5.238:1982/mytv/abc123?1&1481169468_586597
先下載nginx和nginx-rtmp-module原始碼
https://www.nginx.com/
https://github.com/arut/nginx-rtmp-module
在nginx-rtmp-module中的ngx_rtmp_cmd_module.c新增鑑權方法:
int auth(const char *auth_info) { int ret = 0; int fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family=AF_INET; address.sin_addr.s_addr=inet_addr("127.0.0.1"); address.sin_port=htons(8888); //設定讀寫操作超時時間 struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0; setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); ret = connect(fd, (struct sockaddr *)&address, sizeof(address)); if(ret != 0) { ngx_log_stderr(0, "connect to auth svr failed, fd:%d, ret:%d, errno:%d, errmsg:%s\n", fd, ret, errno, strerror(errno)); close(fd); return ret; } ngx_log_stderr(0, "connect to auth svr success!\n"); char *pos = NULL; pos = strchr(auth_info, '&'); if(pos == NULL) { ngx_log_stderr(0, "auth_info is invalid.\n"); close(fd); return -1; } char user_id[100] = {0}; char token[100] = {0}; strncpy(user_id, auth_info, (unsigned int)(pos-auth_info)); strncpy(token, pos+1, (unsigned int)(auth_info+strlen(auth_info)-pos-1)); char req[1024] = {0}; snprintf(req, 1024, "{\"head\":{\"cmd\":\"auth\", \"time\":%llu}, \"body\":{\"user_id\":%s, \"token\":\"%s\"}}\n", (unsigned long long)time(NULL), user_id, token); ret = send(fd, req, strlen(req), 0); if(ret > 0) { ngx_log_stderr(0, "send auth req success!\n"); } else { ngx_log_stderr(0, "send auth req failed!, ret:%d\n", ret); close(fd); return -1; } char rsp[1024] = {0}; ret = recv(fd, rsp, 1023, 0); if(ret > 0 ) { ngx_log_stderr(0, "rsp from auth svr:%s\n", rsp); char *pos = NULL; pos = strstr(rsp, "err"); if(pos == NULL) { ngx_log_stderr(0, "rsq is invalid.\n"); close(fd); return -1; } char err = *(pos+5); if(err == '0') { ngx_log_stderr(0, "auth success!\n"); close(fd); return 0; } else { ngx_log_stderr(0, "auth failed!\n"); close(fd); return -1; } } else if(ret == 0) { ngx_log_stderr(0, "socket is colse!\n"); close(fd); return -1; } else { ngx_log_stderr(0, "recv failed, ret:%d\n", ret); } close(fd); return ret; }
將鑑權方法在nginx-rtmp-module中的ngx_rtmp_cmd_module.c的推流初始化方法(如下)
static ngx_int_t ngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in)
呼叫鑑權方法,具體程式碼如下:
static ngx_int_t ngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { static ngx_rtmp_publish_t v; static ngx_rtmp_amf_elt_t in_elts[] = { /* transaction is always 0 */ { NGX_RTMP_AMF_NUMBER, ngx_null_string, NULL, 0 }, { NGX_RTMP_AMF_NULL, ngx_null_string, NULL, 0 }, { NGX_RTMP_AMF_STRING, ngx_null_string, &v.name, sizeof(v.name) }, { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_STRING, ngx_null_string, &v.type, sizeof(v.type) }, }; ngx_memzero(&v, sizeof(v)); if (ngx_rtmp_receive_amf(s, in, in_elts, sizeof(in_elts) / sizeof(in_elts[0]))) { return NGX_ERROR; } ngx_rtmp_cmd_fill_args(v.name, v.args); ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "publish: name='%s' args='%s' type=%s silent=%d", v.name, v.args, v.type, v.silent); //新增呼叫鑑權函式 int ret = auth((const char*)v.args); if (ret != 0) { ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "auth failed!"); return NGX_ERROR; } return ngx_rtmp_publish(s, &v); }
在播放回調函式中也要呼叫鑑權方法,具體程式碼如下:
static ngx_int_t
ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
static ngx_rtmp_play_t v;
static ngx_rtmp_amf_elt_t in_elts[] = {
/* transaction is always 0 */
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
&v.name, sizeof(v.name) },
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&v.start, 0 },
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&v.duration, 0 },
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN,
ngx_null_string,
&v.reset, 0 }
};
ngx_memzero(&v, sizeof(v));
if (ngx_rtmp_receive_amf(s, in, in_elts,
sizeof(in_elts) / sizeof(in_elts[0])))
{
return NGX_ERROR;
}
ngx_rtmp_cmd_fill_args(v.name, v.args);
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
"play: name='%s' args='%s' start=%i duration=%i "
"reset=%i silent=%i",
v.name, v.args, (ngx_int_t) v.start,
(ngx_int_t) v.duration, (ngx_int_t) v.reset,
(ngx_int_t) v.silent);
//呼叫鑑權操作
int ret = auth((const char*)v.args);
if (ret != 0)
{
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "auth failed!");
return NGX_ERROR;
}
return ngx_rtmp_play(s, &v);
}
重新編譯nginx原始碼
#./configure --prefix=/home/xucuiping/tool/src/nginx_self/sdk --add-module=/home/xucuiping/tool/src/nginx_self/src/nginx-rtmp-module-master --with-debug
#make
#make install
啟動nginx
#./nginx -c /home/xucuiping/tool/src/nginx_self/sdk/conf/nginx_rtmp.conf
測試:
通過OBS推流和RTMP播放器測試驗證。
在此表示感謝!