url参数变更后再次鉴权;hls播放鉴权提到httpsession类中实现

This commit is contained in:
xiongziliang 2019-06-14 15:19:02 +08:00
parent e365824be2
commit cfbdda0698
7 changed files with 120 additions and 118 deletions

View File

@ -72,7 +72,6 @@ const char kOnShellLogin[] = HOOK_FIELD"on_shell_login";
const char kOnStreamNoneReader[] = HOOK_FIELD"on_stream_none_reader"; const char kOnStreamNoneReader[] = HOOK_FIELD"on_stream_none_reader";
const char kOnHttpAccess[] = HOOK_FIELD"on_http_access"; const char kOnHttpAccess[] = HOOK_FIELD"on_http_access";
const char kAdminParams[] = HOOK_FIELD"admin_params"; const char kAdminParams[] = HOOK_FIELD"admin_params";
const char kAccessFileExceptHls[] = HOOK_FIELD"access_file_except_hls";
onceToken token([](){ onceToken token([](){
mINI::Instance()[kEnable] = true; mINI::Instance()[kEnable] = true;
@ -89,7 +88,6 @@ onceToken token([](){
mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader"; mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader";
mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access"; mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access";
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
mINI::Instance()[kAccessFileExceptHls] = true;
},nullptr); },nullptr);
}//namespace Hook }//namespace Hook
@ -194,8 +192,6 @@ void installWebHook(){
GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin); GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin);
GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader); GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader);
GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess); GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess);
GET_CONFIG(bool,access_file_except_hls,Hook::kAccessFileExceptHls);
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){ NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){ if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){
@ -385,75 +381,22 @@ void installWebHook(){
}); });
//由于http是短链接如果http客户端不支持cookie那么http服务器就不好追踪用户 /**
//如果无法追踪用户那么每次访问http服务器文件都会触发kBroadcastHttpAccess事件这样的话会严重影响性能 * kBroadcastHttpAccess事件触发机制
//所以在http客户端不支持cookie的情况下目前只有两种方式来追踪用户 * 1http请求头查找cookie3
//1、根据url参数,2、根据ip和端口 * 2http url参数(ip+)cookiecookie则进入步骤5
//由于http短连接的特性端口基本上是无法固定的所以根据ip和端口来追踪用户基本不太现实所以只剩方式1了 * 3cookie标记是否有权限访问文件
//以下提供了根据url参数来追踪用户的范例 * 4cookie中记录的url参数是否跟本次url参数一致
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastTrackHttpClient,[](BroadcastTrackHttpClientArgs){ * 5kBroadcastHttpAccess事件
auto &params = parser.getUrlArgs(); */
if(!params["token"].empty()){
//根据token追踪用户
uid = params["token"];
return;
}
if(!parser.Params().empty()){
//根据url参数来追踪用户
uid = parser.Params();
}
});
//字符串是否以xx结尾
static auto end_of = [](const string &str, const string &substr){
auto pos = str.rfind(substr);
return pos != string::npos && pos == str.size() - substr.size();
};
//拦截hls的播放请求
static auto checkHls = [](BroadcastHttpAccessArgs){
if(!end_of(args._streamid,("/hls.m3u8"))) {
//不是hls
return false;
}
//访问的.m3u8结尾我们转换成kBroadcastMediaPlayed事件
Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){
if(err.empty() ){
//鉴权通过,允许播放一个小时
invoker(path.substr(0,path.rfind("/") + 1),60 * 60);
}else{
//鉴权失败10秒内不允许播放hls
invoker("",10);
}
};
auto args_copy = args;
replace(args_copy._streamid,"/hls.m3u8","");
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender);
};
//http客户端访问文件鉴权事件
//开发者应该通过该事件判定http客户端是否有权限访问http服务器上的特定文件 //开发者应该通过该事件判定http客户端是否有权限访问http服务器上的特定文件
//ZLMediaKit会记录本次鉴权的结果并且通过设置cookie的方式追踪该http客户端 //ZLMediaKit会记录本次鉴权的结果至cookie
//在该cookie的有效期内该http客户端再次访问该文件将不再触发kBroadcastHttpAccess事件 //如果鉴权成功在cookie有效期内那么下次客户端再访问授权目录时ZLMediaKit会直接返回文件
//如果http客户端不支持cookie那么ZLMediaKit会通过诸如url参数的方式追踪http客户端 //如果鉴权失败在cookie有效期内如果http url参数不变(否则会立即再次触发鉴权事件)ZLMediaKit会直接返回错误码
//通过追踪http客户端的方式可以减少http短连接导致的大量的鉴权事件请求 //如果用户客户端不支持cookie那么ZLMediaKit会根据url参数查找cookie并追踪用户
//在kBroadcastHttpAccess事件中开发者应该通过参数paramsurl参数来判断http客户端是否具有访问权限 //如果没有url参数客户端又不支持cookie那么会根据ip和端口追踪用户
//需要指出的是假如http客户端支持cookie并且判定客户端没有权限那么在该cookie有效期内 //追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能
//不管该客户端是否变换url参数都将无法再次访问该文件所以如果判定无权限的情况下可以把cookie有效期设置短一点
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
if(checkHls(parser,args,path,is_dir,invoker,sender)){
//是hls的播放鉴权,拦截之
return;
}
if(!access_file_except_hls){
//不允许访问hls之外的文件
invoker("",60 * 60);
return;
}
if(sender.get_peer_ip() == "127.0.0.1" && args._param_strs == hook_adminparams){ if(sender.get_peer_ip() == "127.0.0.1" && args._param_strs == hook_adminparams){
//如果是本机或超级管理员访问那么不做访问鉴权权限有效期1个小时 //如果是本机或超级管理员访问那么不做访问鉴权权限有效期1个小时
invoker("/",60 * 60); invoker("/",60 * 60);
@ -483,8 +426,11 @@ void installWebHook(){
invoker("",0); invoker("",0);
return; return;
} }
//path参数是该客户端能访问的目录,该目录下的所有文件它都能访问 //path参数是该客户端能访问的顶端目录,该目录下的所有文件它都能访问
//second参数规定该cookie超时时间,超过这个时间后,用户需要重新鉴权 //second参数规定该cookie超时时间,超过这个时间后,用户需要重新鉴权
//如果path为空字符串则为禁止访问任何目录
//如果second为0本次鉴权结果不缓存
//如果被禁止访问文件在cookie有效期内假定再次访问的url参数变了那么也能立即触发重新鉴权操作
invoker(obj["path"].asString(),obj["second"].asInt()); invoker(obj["path"].asString(),obj["second"].asInt());
}); });
}); });

View File

@ -58,7 +58,6 @@ const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4"; const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
const string kBroadcastHttpRequest = "kBroadcastHttpRequest"; const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
const string kBroadcastHttpAccess = "kBroadcastHttpAccess"; const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
const string kBroadcastTrackHttpClient = "kBroadcastTrackHttpClient";
const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm"; const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm";
const string kBroadcastOnRtspAuth = "kBroadcastOnRtspAuth"; const string kBroadcastOnRtspAuth = "kBroadcastOnRtspAuth";
const string kBroadcastMediaPlayed = "kBroadcastMediaPlayed"; const string kBroadcastMediaPlayed = "kBroadcastMediaPlayed";

View File

@ -83,12 +83,6 @@ extern const string kBroadcastHttpRequest;
extern const string kBroadcastHttpAccess; extern const string kBroadcastHttpAccess;
#define BroadcastHttpAccessArgs const Parser &parser,const MediaInfo &args,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender #define BroadcastHttpAccessArgs const Parser &parser,const MediaInfo &args,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender
//追踪用户事件如果http客户端不支持cookieip端口又一直变那么可以根据url参数来追踪用户
//从而减少kBroadcastHttpAccess事件触发的次数。
extern const string kBroadcastTrackHttpClient;
#define BroadcastTrackHttpClientArgs const Parser &parser,string &uid,TcpSession &sender
//该流是否需要认证是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 //该流是否需要认证是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证
extern const string kBroadcastOnGetRtspRealm; extern const string kBroadcastOnGetRtspRealm;
#define BroadcastOnGetRtspRealmArgs const MediaInfo &args,const RtspSession::onGetRealm &invoker,TcpSession &sender #define BroadcastOnGetRtspRealmArgs const MediaInfo &args,const RtspSession::onGetRealm &invoker,TcpSession &sender

View File

@ -173,6 +173,17 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name,con
return HttpCookieManager::Instance().getCookie(cookie_name , cookie); return HttpCookieManager::Instance().getCookie(cookie_name , cookie);
} }
HttpServerCookie::Ptr HttpCookieManager::getCookieByUid(const string &cookie_name,const string &uid){
if(cookie_name.empty() || uid.empty()){
return nullptr;
}
auto cookie = getOldestCookie(cookie_name,uid);
if(cookie.empty()){
return nullptr;
}
return getCookie(cookie_name,cookie);
}
bool HttpCookieManager::delCookie(const HttpServerCookie::Ptr &cookie) { bool HttpCookieManager::delCookie(const HttpServerCookie::Ptr &cookie) {
if(!cookie){ if(!cookie){
return false; return false;

View File

@ -182,22 +182,20 @@ public:
*/ */
HttpServerCookie::Ptr getCookie(const string &cookie_name,const StrCaseMap &http_header); HttpServerCookie::Ptr getCookie(const string &cookie_name,const StrCaseMap &http_header);
/**
* uid获取cookie
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @return cookie对象
*/
HttpServerCookie::Ptr getCookieByUid(const string &cookie_name,const string &uid);
/** /**
* cookie使 * cookie使
* @param cookie cookie对象nullptr * @param cookie cookie对象nullptr
* @return * @return
*/ */
bool delCookie(const HttpServerCookie::Ptr &cookie); bool delCookie(const HttpServerCookie::Ptr &cookie);
/**
* cookie
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @param max_client
* @return cookie随机字符串
*/
string getOldestCookie(const string &cookie_name,const string &uid, int max_client = 1);
private: private:
HttpCookieManager(); HttpCookieManager();
void onManager(); void onManager();
@ -217,6 +215,15 @@ private:
*/ */
void onDelCookie(const string &cookie_name,const string &uid,const string &cookie); void onDelCookie(const string &cookie_name,const string &uid,const string &cookie);
/**
* cookie
* @param cookie_name cookie名MY_SESSION
* @param uid id
* @param max_client
* @return cookie随机字符串
*/
string getOldestCookie(const string &cookie_name,const string &uid, int max_client = 1);
/** /**
* cookie * cookie
* @param cookie_name cookie名MY_SESSION * @param cookie_name cookie名MY_SESSION

View File

@ -321,21 +321,46 @@ inline static string findIndexFile(const string &dir){
} }
inline string HttpSession::getClientUid(){ inline string HttpSession::getClientUid(){
//该ip端口只能有一个cookie不能重复获取cookie //如果http客户端不支持cookie那么我们可以通过url参数来追踪用户
//目的是为了防止我们让客户端设置cookie但是客户端不支持cookie导致一直重复生成cookie //如果url参数也没有那么只能通过ip+端口号来追踪用户
//判断是否为同一个用户还可以根据url相关字段但是这个跟具体业务逻辑相关在这里不便实现 //追踪用户的目的是为了减少http短链接情况的重复鉴权验证通过缓存记录鉴权结果提高性能
//如果一个http客户端不支持cookie并且一直变换端口号那么可能会导致服务器无法追踪该用户从而导致一直触发事件并且一直生成cookie string uid = _parser.Params();
string uid = StrPrinter << get_peer_ip() << ":" << get_peer_port(); if(uid.empty()){
//所以我们通过kBroadcastTrackHttpClient事件来让业务逻辑自行决定根据url参数追踪用户 uid = StrPrinter << get_peer_ip() << ":" << get_peer_port();
NoticeCenter::Instance().emitEventNoCopy(Broadcast::kBroadcastTrackHttpClient,_parser,uid,*this); }
return uid; return uid;
} }
inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(bool canAccess,const HttpServerCookie::Ptr &cookie)> &callback_in){
if(NoticeCenter::Instance().listenerSize(Broadcast::kBroadcastHttpAccess) == 0){
//该事件无人监听那么就不做cookie查找这样费时的操作 //字符串是否以xx结尾
callback_in(true, nullptr); static inline bool end_of(const string &str, const string &substr){
return; auto pos = str.rfind(substr);
return pos != string::npos && pos == str.size() - substr.size();
};
//拦截hls的播放请求
static inline bool checkHls(BroadcastHttpAccessArgs){
if(!end_of(args._streamid,("/hls.m3u8"))) {
//不是hls
return false;
} }
//访问的hls.m3u8结尾我们转换成kBroadcastMediaPlayed事件
Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){
if(err.empty() ){
//鉴权通过,允许播放一个小时
invoker(path.substr(0,path.rfind("/") + 1),60 * 60);
}else{
//鉴权失败10秒内不允许播放hls
invoker("",10);
}
};
auto args_copy = args;
replace(args_copy._streamid,"/hls.m3u8","");
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender);
}
inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(bool canAccess,const HttpServerCookie::Ptr &cookie)> &callback_in){
auto path = path_in; auto path = path_in;
replace(const_cast<string &>(path),"//","/"); replace(const_cast<string &>(path),"//","/");
@ -351,23 +376,28 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
} }
}; };
//根据http头中的cookie字段获取cookie //获取用户唯一id
auto cookie = HttpCookieManager::Instance().getCookie(kCookieName,_parser.getValues()); auto uid = getClientUid();
if (cookie) { //先根据http头中的cookie字段获取cookie
//判断该用户是否有权限访问该目录并且不再设置客户端cookie HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, _parser.getValues());
callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr); if(!cookie){
return; //客户端请求中无cookie,再根据该用户的用户id获取cookie
cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid);
} }
//根据该用户的用户名获取cookie if(cookie){
string uid = getClientUid(); //找到了cookie
auto cookie_str = HttpCookieManager::Instance().getOldestCookie(kCookieName,uid); auto accessPath = (*cookie)[kAccessPathKey];
if(!cookie_str.empty()){ if (!accessPath.empty() && path.find(accessPath) == 0) {
//该用户已经登录过了,但是它(http客户端)貌似不支持cookie所以我们只能通过它的用户名获取cookie //用户是有权限访问该目录
cookie = HttpCookieManager::Instance().getCookie(kCookieName,cookie_str); callback(true, nullptr);
if (cookie) { return;
//判断该用户是否有权限访问该目录并且不再设置客户端cookie }
callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr); //用户无权限访问,我们看看用户的url参数变了没有
//如果url参数变了那么重新鉴权
if (cookie->getUid() == _parser.Params()) {
//url参数未变那么判断无权限访问
callback(false, nullptr);
return; return;
} }
} }
@ -401,6 +431,11 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
}); });
}; };
if(checkHls(_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this)){
//是hls的播放鉴权,拦截之
return;
}
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this); bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this);
if(!flag){ if(!flag){
//此事件无人监听,我们默认都有权限访问 //此事件无人监听,我们默认都有权限访问

View File

@ -114,14 +114,24 @@ private:
const string &contentOut); const string &contentOut);
/** /**
* 访 * http客户端是否有权限访问文件的逻辑步骤
*
* 1http请求头查找cookie3
* 2http url参数(ip+)cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
* @param path * @param path
* @param is_dir path是否为目录 * @param is_dir path是否为目录
* @param callback * @param callback
*/ */
inline void canAccessPath(const string &path,bool is_dir,const function<void(bool canAccess,const HttpServerCookie::Ptr &cookie)> &callback); inline void canAccessPath(const string &path,bool is_dir,const function<void(bool canAccess,const HttpServerCookie::Ptr &cookie)> &callback);
//获取用户唯一识别id我们默认为ip+端口号 /**
* id
* url参数返回参数ip+
* @return
*/
inline string getClientUid(); inline string getClientUid();
private: private:
Parser _parser; Parser _parser;