From cfbdda0698f97d9e1861e6f73622a425999b12dd Mon Sep 17 00:00:00 2001 From: xiongziliang <771730766@qq.com> Date: Fri, 14 Jun 2019 15:19:02 +0800 Subject: [PATCH] =?UTF-8?q?url=E5=8F=82=E6=95=B0=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=E5=90=8E=E5=86=8D=E6=AC=A1=E9=89=B4=E6=9D=83=EF=BC=9Bhls?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E9=89=B4=E6=9D=83=E6=8F=90=E5=88=B0httpsessi?= =?UTF-8?q?on=E7=B1=BB=E4=B8=AD=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/WebHook.cpp | 90 +++++++--------------------------- src/Common/config.cpp | 1 - src/Common/config.h | 6 --- src/Http/HttpCookieManager.cpp | 11 +++++ src/Http/HttpCookieManager.h | 27 ++++++---- src/Http/HttpSession.cpp | 89 +++++++++++++++++++++++---------- src/Http/HttpSession.h | 14 +++++- 7 files changed, 120 insertions(+), 118 deletions(-) diff --git a/server/WebHook.cpp b/server/WebHook.cpp index f160ab16..714a5ee5 100644 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -72,7 +72,6 @@ const char kOnShellLogin[] = HOOK_FIELD"on_shell_login"; const char kOnStreamNoneReader[] = HOOK_FIELD"on_stream_none_reader"; const char kOnHttpAccess[] = HOOK_FIELD"on_http_access"; const char kAdminParams[] = HOOK_FIELD"admin_params"; -const char kAccessFileExceptHls[] = HOOK_FIELD"access_file_except_hls"; onceToken token([](){ 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()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access"; mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; - mINI::Instance()[kAccessFileExceptHls] = true; },nullptr); }//namespace Hook @@ -194,8 +192,6 @@ void installWebHook(){ GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin); GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader); GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess); - GET_CONFIG(bool,access_file_except_hls,Hook::kAccessFileExceptHls); - 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"){ @@ -385,75 +381,22 @@ void installWebHook(){ }); - //由于http是短链接,如果http客户端不支持cookie,那么http服务器就不好追踪用户; - //如果无法追踪用户,那么每次访问http服务器文件都会触发kBroadcastHttpAccess事件,这样的话会严重影响性能 - //所以在http客户端不支持cookie的情况下,目前只有两种方式来追踪用户 - //1、根据url参数,2、根据ip和端口 - //由于http短连接的特性,端口基本上是无法固定的,所以根据ip和端口来追踪用户基本不太现实,所以只剩方式1了 - //以下提供了根据url参数来追踪用户的范例 - NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastTrackHttpClient,[](BroadcastTrackHttpClientArgs){ - auto ¶ms = 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客户端访问文件鉴权事件 + /** + * kBroadcastHttpAccess事件触发机制 + * 1、根据http请求头查找cookie,找到进入步骤3 + * 2、根据http url参数(如果没有根据ip+端口号)查找cookie,如果还是未找到cookie则进入步骤5 + * 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件 + * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码 + * 5、触发kBroadcastHttpAccess事件 + */ //开发者应该通过该事件判定http客户端是否有权限访问http服务器上的特定文件 - //ZLMediaKit会记录本次鉴权的结果,并且通过设置cookie的方式追踪该http客户端, - //在该cookie的有效期内,该http客户端再次访问该文件将不再触发kBroadcastHttpAccess事件 - //如果http客户端不支持cookie,那么ZLMediaKit会通过诸如url参数的方式追踪http客户端 - //通过追踪http客户端的方式,可以减少http短连接导致的大量的鉴权事件请求 - //在kBroadcastHttpAccess事件中,开发者应该通过参数params(url参数)来判断http客户端是否具有访问权限 - //需要指出的是,假如http客户端支持cookie,并且判定客户端没有权限,那么在该cookie有效期内, - //不管该客户端是否变换url参数都将无法再次访问该文件,所以如果判定无权限的情况下,可以把cookie有效期设置短一点 + //ZLMediaKit会记录本次鉴权的结果至cookie + //如果鉴权成功,在cookie有效期内,那么下次客户端再访问授权目录时,ZLMediaKit会直接返回文件 + //如果鉴权失败,在cookie有效期内,如果http url参数不变(否则会立即再次触发鉴权事件),ZLMediaKit会直接返回错误码 + //如果用户客户端不支持cookie,那么ZLMediaKit会根据url参数查找cookie并追踪用户, + //如果没有url参数,客户端又不支持cookie,那么会根据ip和端口追踪用户 + //追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 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){ //如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 invoker("/",60 * 60); @@ -483,8 +426,11 @@ void installWebHook(){ invoker("",0); return; } - //path参数是该客户端能访问的根目录,该目录下的所有文件它都能访问 + //path参数是该客户端能访问的顶端目录,该目录下的所有文件它都能访问 //second参数规定该cookie超时时间,超过这个时间后,用户需要重新鉴权 + //如果path为空字符串,则为禁止访问任何目录 + //如果second为0,本次鉴权结果不缓存 + //如果被禁止访问文件,在cookie有效期内,假定再次访问的url参数变了,那么也能立即触发重新鉴权操作 invoker(obj["path"].asString(),obj["second"].asInt()); }); }); diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 0789c82a..0e19df23 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -58,7 +58,6 @@ const string kBroadcastMediaChanged = "kBroadcastMediaChanged"; const string kBroadcastRecordMP4 = "kBroadcastRecordMP4"; const string kBroadcastHttpRequest = "kBroadcastHttpRequest"; const string kBroadcastHttpAccess = "kBroadcastHttpAccess"; -const string kBroadcastTrackHttpClient = "kBroadcastTrackHttpClient"; const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm"; const string kBroadcastOnRtspAuth = "kBroadcastOnRtspAuth"; const string kBroadcastMediaPlayed = "kBroadcastMediaPlayed"; diff --git a/src/Common/config.h b/src/Common/config.h index 709d5380..d396b5c7 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -83,12 +83,6 @@ extern const string kBroadcastHttpRequest; 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 -//追踪用户事件,如果http客户端不支持cookie,ip端口又一直变,那么可以根据url参数来追踪用户, -//从而减少kBroadcastHttpAccess事件触发的次数。 -extern const string kBroadcastTrackHttpClient; -#define BroadcastTrackHttpClientArgs const Parser &parser,string &uid,TcpSession &sender - - //该流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 extern const string kBroadcastOnGetRtspRealm; #define BroadcastOnGetRtspRealmArgs const MediaInfo &args,const RtspSession::onGetRealm &invoker,TcpSession &sender diff --git a/src/Http/HttpCookieManager.cpp b/src/Http/HttpCookieManager.cpp index 742a4d58..2b1baa75 100644 --- a/src/Http/HttpCookieManager.cpp +++ b/src/Http/HttpCookieManager.cpp @@ -173,6 +173,17 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name,con 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) { if(!cookie){ return false; diff --git a/src/Http/HttpCookieManager.h b/src/Http/HttpCookieManager.h index f769f21e..3267216d 100644 --- a/src/Http/HttpCookieManager.h +++ b/src/Http/HttpCookieManager.h @@ -182,22 +182,20 @@ public: */ 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,用户登出时使用 * @param cookie cookie对象,可以为nullptr * @return */ 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: HttpCookieManager(); void onManager(); @@ -217,6 +215,15 @@ private: */ 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 * @param cookie_name cookie名,例如MY_SESSION diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 664a511a..308c750e 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -321,21 +321,46 @@ inline static string findIndexFile(const string &dir){ } inline string HttpSession::getClientUid(){ - //该ip端口只能有一个cookie,不能重复获取cookie, - //目的是为了防止我们让客户端设置cookie,但是客户端不支持cookie导致一直重复生成cookie - //判断是否为同一个用户还可以根据url相关字段,但是这个跟具体业务逻辑相关,在这里不便实现 - //如果一个http客户端不支持cookie并且一直变换端口号,那么可能会导致服务器无法追踪该用户,从而导致一直触发事件并且一直生成cookie - string uid = StrPrinter << get_peer_ip() << ":" << get_peer_port(); - //所以我们通过kBroadcastTrackHttpClient事件来让业务逻辑自行决定根据url参数追踪用户 - NoticeCenter::Instance().emitEventNoCopy(Broadcast::kBroadcastTrackHttpClient,_parser,uid,*this); + //如果http客户端不支持cookie,那么我们可以通过url参数来追踪用户 + //如果url参数也没有,那么只能通过ip+端口号来追踪用户 + //追踪用户的目的是为了减少http短链接情况的重复鉴权验证,通过缓存记录鉴权结果,提高性能 + string uid = _parser.Params(); + if(uid.empty()){ + uid = StrPrinter << get_peer_ip() << ":" << get_peer_port(); + } return uid; } -inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function &callback_in){ - if(NoticeCenter::Instance().listenerSize(Broadcast::kBroadcastHttpAccess) == 0){ - //该事件无人监听,那么就不做cookie查找这样费时的操作 - callback_in(true, nullptr); - return; + + +//字符串是否以xx结尾 +static inline bool end_of(const string &str, const string &substr){ + 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 &callback_in){ auto path = path_in; replace(const_cast(path),"//","/"); @@ -351,23 +376,28 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f } }; - //根据http头中的cookie字段获取cookie - auto cookie = HttpCookieManager::Instance().getCookie(kCookieName,_parser.getValues()); - if (cookie) { - //判断该用户是否有权限访问该目录,并且不再设置客户端cookie - callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr); - return; + //获取用户唯一id + auto uid = getClientUid(); + //先根据http头中的cookie字段获取cookie + HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, _parser.getValues()); + if(!cookie){ + //客户端请求中无cookie,再根据该用户的用户id获取cookie + cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid); } - //根据该用户的用户名获取cookie - string uid = getClientUid(); - auto cookie_str = HttpCookieManager::Instance().getOldestCookie(kCookieName,uid); - if(!cookie_str.empty()){ - //该用户已经登录过了,但是它(http客户端)貌似不支持cookie,所以我们只能通过它的用户名获取cookie - cookie = HttpCookieManager::Instance().getCookie(kCookieName,cookie_str); - if (cookie) { - //判断该用户是否有权限访问该目录,并且不再设置客户端cookie - callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr); + if(cookie){ + //找到了cookie + auto accessPath = (*cookie)[kAccessPathKey]; + if (!accessPath.empty() && path.find(accessPath) == 0) { + //用户是有权限访问该目录 + callback(true, nullptr); + return; + } + //用户无权限访问,我们看看用户的url参数变了没有 + //如果url参数变了,那么重新鉴权 + if (cookie->getUid() == _parser.Params()) { + //url参数未变,那么判断无权限访问 + callback(false, nullptr); 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); if(!flag){ //此事件无人监听,我们默认都有权限访问 diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 4b640ef9..ab339902 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -114,14 +114,24 @@ private: const string &contentOut); /** - * 是否有权限访问某目录或文件 + * 判断http客户端是否有权限访问文件的逻辑步骤 + * + * 1、根据http请求头查找cookie,找到进入步骤3 + * 2、根据http url参数(如果没有根据ip+端口号)查找cookie,如果还是未找到cookie则进入步骤5 + * 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件 + * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码 + * 5、触发kBroadcastHttpAccess事件 * @param path 文件或目录 * @param is_dir path是否为目录 * @param callback 有权限或无权限的回调 */ inline void canAccessPath(const string &path,bool is_dir,const function &callback); - //获取用户唯一识别id,我们默认为ip+端口号 + /** + * 获取用户唯一识别id + * 有url参数返回参数,无参数返回ip+端口号 + * @return + */ inline string getClientUid(); private: Parser _parser;