转自:https://blog.csdn.net/liuchen1206/article/details/77839039
目前直播平台非常的火爆。当前有不少的流媒体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播放器测试验证。
原文地址:http://blog.csdn.net/cui918/article/details/53540397
在此表示感谢!