http文件鉴权支持自定义错误提示

This commit is contained in:
xiongziliang 2019-06-14 18:42:09 +08:00
parent cfbdda0698
commit c7cc082d95
6 changed files with 101 additions and 81 deletions

View File

@ -673,34 +673,23 @@ void installWebApi() {
}; };
API_REGIST(hook,on_http_access,{ API_REGIST(hook,on_http_access,{
#if 0
//能访问根目录以及根目录下所有文件10分钟
val["path"] = "/";
val["second"] = 10 * 60;
#else
//在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件 //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
if(!checkAccess(allArgs["params"])){ if(!checkAccess(allArgs["params"])){
//无访问权限 //无访问权限
val["err"] = "无访问权限";
//仅限制访问当前目录
val["path"] = ""; val["path"] = "";
//标记该客户端无权限1分钟1分钟之内它凭此cookie访问将都无权限 //标记该客户端无权限1分钟
//如果客户端不支持cookie那么可以根据url参数来追踪用户请参考kBroadcastTrackHttpClient事件
//如果服务器未处理kBroadcastTrackHttpClient事件那么ZLMediaKit会根据ip和端口追踪用户
val["second"] = 60; val["second"] = 60;
return; return;
} }
//只能访问本文件且只授权10分钟访问其他文件都要另外授权 //可以访问
if(allArgs["is_dir"].as<bool>()){ val["err"] = "";
//访问的是目录该授权cookie只对该目录有效 //只能访问当前目录
val["path"] = (string)allArgs["path"]; val["path"] = "";
}else{ //该http客户端用户被授予10分钟的访问权限该权限仅限访问当前目录
//访问的是文件,那么我们授予客户端访问所在目录的权限
string dir = allArgs["path"].substr(0,allArgs["path"].rfind("/") + 1);
val["path"] = dir;
}
//该http客户端用户被授予10分钟的访问权限该权限仅限访问特定目录
val["second"] = 10 * 60; val["second"] = 10 * 60;
#endif
}); });

View File

@ -399,13 +399,13 @@ void installWebHook(){
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
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);
return; return;
} }
if(!hook_enable || hook_http_access.empty()){ if(!hook_enable || hook_http_access.empty()){
//未开启http文件访问鉴权那么允许访问但是每次访问都要鉴权 //未开启http文件访问鉴权那么允许访问但是每次访问都要鉴权
//因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权) //因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
invoker("/",0); invoker("","",0);
return; return;
} }
@ -423,15 +423,13 @@ void installWebHook(){
do_http_hook(hook_http_access,body, [invoker](const Value &obj,const string &err){ do_http_hook(hook_http_access,body, [invoker](const Value &obj,const string &err){
if(!err.empty()){ if(!err.empty()){
//如果接口访问失败那么仅限本次没有访问http服务器的权限 //如果接口访问失败那么仅限本次没有访问http服务器的权限
invoker("",0); invoker(err,"",0);
return; return;
} }
//path参数是该客户端能访问的顶端目录该目录下的所有文件它都能访问 //err参数代表不能访问的原因空则代表可以访问
//second参数规定该cookie超时时间,超过这个时间后,用户需要重新鉴权 //path参数是该客户端能访问或被禁止的顶端目录如果path为空字符串则表述为当前目录
//如果path为空字符串则为禁止访问任何目录 //second参数规定该cookie超时时间如果second为0本次鉴权结果不缓存
//如果second为0本次鉴权结果不缓存 invoker(obj["err"].asString(),obj["path"].asString(),obj["second"].asInt());
//如果被禁止访问文件在cookie有效期内假定再次访问的url参数变了那么也能立即触发重新鉴权操作
invoker(obj["path"].asString(),obj["second"].asInt());
}); });
}); });
} }

View File

@ -76,6 +76,10 @@ bool HttpServerCookie::isExpired() {
return _ticker.elapsedTime() > _max_elapsed * 1000; return _ticker.elapsedTime() > _max_elapsed * 1000;
} }
std::shared_ptr<lock_guard<mutex> > HttpServerCookie::getLock(){
return std::make_shared<lock_guard<mutex> >(_mtx);
}
string HttpServerCookie::cookieExpireTime() const{ string HttpServerCookie::cookieExpireTime() const{
char buf[64]; char buf[64];
time_t tt = time(NULL) + _max_elapsed; time_t tt = time(NULL) + _max_elapsed;
@ -105,7 +109,7 @@ void HttpCookieManager::onManager() {
for (auto it_cookie = it_name->second.begin() ; it_cookie != it_name->second.end() ; ){ for (auto it_cookie = it_name->second.begin() ; it_cookie != it_name->second.end() ; ){
if(it_cookie->second->isExpired()){ if(it_cookie->second->isExpired()){
//cookie过期,移除记录 //cookie过期,移除记录
WarnL << it_cookie->second->getUid() << " cookie过期"; DebugL << it_cookie->second->getUid() << " cookie过期:" << it_cookie->second->getCookie();
it_cookie = it_name->second.erase(it_cookie); it_cookie = it_name->second.erase(it_cookie);
continue; continue;
} }
@ -114,7 +118,7 @@ void HttpCookieManager::onManager() {
if(it_name->second.empty()){ if(it_name->second.empty()){
//该类型下没有任何cooki记录,移除之 //该类型下没有任何cooki记录,移除之
WarnL << "该path下没有任何cooki记录:" << it_name->first; DebugL << "该path下没有任何cooki记录:" << it_name->first;
it_name = _map_cookie.erase(it_name); it_name = _map_cookie.erase(it_name);
continue; continue;
} }
@ -152,6 +156,7 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name,con
} }
if(it_cookie->second->isExpired()){ if(it_cookie->second->isExpired()){
//cookie过期 //cookie过期
DebugL << "cookie过期:" << it_cookie->second->getCookie();
it_name->second.erase(it_cookie); it_name->second.erase(it_cookie);
return nullptr; return nullptr;
} }

View File

@ -102,6 +102,12 @@ public:
* @return * @return
*/ */
bool isExpired(); bool isExpired();
/**
*
* @return
*/
std::shared_ptr<lock_guard<mutex> > getLock();
private: private:
string cookieExpireTime() const ; string cookieExpireTime() const ;
private: private:
@ -110,6 +116,7 @@ private:
string _cookie_uuid; string _cookie_uuid;
uint64_t _max_elapsed; uint64_t _max_elapsed;
Ticker _ticker; Ticker _ticker;
mutex _mtx;
std::weak_ptr<HttpCookieManager> _manager; std::weak_ptr<HttpCookieManager> _manager;
}; };

View File

@ -49,11 +49,10 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE; static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE;
static int kHlsCookieSecond = 10 * 60;
static const string kCookieName = "ZL_COOKIE"; static const string kCookieName = "ZL_COOKIE";
static const string kAccessPathKey = "kAccessPathKey"; static const string kCookiePathKey = "kCookiePathKey";
static const string kAccessDirUnauthorized = "你没有权限访问该目录"; static const string kAccessErrKey = "kAccessErrKey";
static const string kAccessFileUnauthorized = "你没有权限访问该文件";
string dateStr() { string dateStr() {
char buf[64]; char buf[64];
@ -346,13 +345,8 @@ static inline bool checkHls(BroadcastHttpAccessArgs){
} }
//访问的hls.m3u8结尾我们转换成kBroadcastMediaPlayed事件 //访问的hls.m3u8结尾我们转换成kBroadcastMediaPlayed事件
Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){ Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){
if(err.empty() ){ //cookie有效期为kHlsCookieSecond
//鉴权通过,允许播放一个小时 invoker(err,"",kHlsCookieSecond);
invoker(path.substr(0,path.rfind("/") + 1),60 * 60);
}else{
//鉴权失败10秒内不允许播放hls
invoker("",10);
}
}; };
auto args_copy = args; auto args_copy = args;
@ -360,13 +354,13 @@ static inline bool checkHls(BroadcastHttpAccessArgs){
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender); 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){ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback_in){
auto path = path_in; auto path = path_in;
replace(const_cast<string &>(path),"//","/"); replace(const_cast<string &>(path),"//","/");
auto callback = [callback_in,this](bool canAccess,const HttpServerCookie::Ptr &cookie){ auto callback = [callback_in,this](const string &errMsg,const HttpServerCookie::Ptr &cookie){
try { try {
callback_in(canAccess,cookie); callback_in(errMsg,cookie);
}catch (SockException &ex){ }catch (SockException &ex){
if(ex){ if(ex){
shutdown(ex); shutdown(ex);
@ -386,48 +380,63 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
} }
if(cookie){ if(cookie){
//找到了cookie //找到了cookie对cookie上锁先
auto accessPath = (*cookie)[kAccessPathKey]; auto lck = cookie->getLock();
if (!accessPath.empty() && path.find(accessPath) == 0) { auto accessErr = (*cookie)[kAccessErrKey];
//用户是有权限访问该目录 if (accessErr.empty() && path.find((*cookie)[kCookiePathKey]) == 0) {
callback(true, nullptr); //用户有权限访问该目录
callback("", nullptr);
return; return;
} }
//用户无权限访问,我们看看用户的url参数变了没有 //用户无权限访问,我们看看用户的url参数变了没有
//如果url参数变了那么重新鉴权 if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) {
if (cookie->getUid() == _parser.Params()) {
//url参数未变那么判断无权限访问 //url参数未变那么判断无权限访问
callback(false, nullptr); callback(accessErr.empty() ? "无权限访问该目录" : accessErr, nullptr);
return; return;
} }
//如果url参数变了那么旧cookie失效我们重新鉴权
HttpCookieManager::Instance().delCookie(cookie);
} }
//该用户从来未获取过cookie这个时候我们广播是否允许该用户访问该http目录 //该用户从来未获取过cookie这个时候我们广播是否允许该用户访问该http目录
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this()); weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
HttpAccessPathInvoker accessPathInvoker = [weakSelf, callback, uid , path] (const string &accessPath, int cookieLifeSecond) { HttpAccessPathInvoker accessPathInvoker = [weakSelf,callback,uid,path,is_dir] (const string &errMsg,const string &cookie_path_in, int cookieLifeSecond) {
string cookie_path = cookie_path_in;
if(cookie_path.empty()){
//如果未设置鉴权目录,那么我们采用当前目录
if(is_dir){
cookie_path = path;
}else{
cookie_path = path.substr(0,path.rfind("/") + 1);
}
}
HttpServerCookie::Ptr cookie ;
if(cookieLifeSecond) {
//本次鉴权设置了有效期我们把鉴权结果缓存在cookie中
cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
//对cookie上锁
auto lck = cookie->getLock();
//记录用户能访问的路径
(*cookie)[kCookiePathKey] = cookie_path;
//记录能否访问
(*cookie)[kAccessErrKey] = errMsg;
}
auto strongSelf = weakSelf.lock(); auto strongSelf = weakSelf.lock();
if (!strongSelf) { if (!strongSelf) {
//自己已经销毁 //自己已经销毁
return; return;
} }
strongSelf->async([weakSelf, callback, accessPath, cookieLifeSecond, uid , path]() { strongSelf->async([weakSelf,callback,cookie,errMsg]() {
//切换到自己线程 //切换到自己线程
auto strongSelf = weakSelf.lock(); auto strongSelf = weakSelf.lock();
if (!strongSelf) { if (!strongSelf) {
//自己已经销毁 //自己已经销毁
return; return;
} }
if(cookieLifeSecond){ callback(errMsg, cookie);
//我们给用户生成追踪cookie
auto cookie = HttpCookieManager::Instance().addCookie(kCookieName,uid,cookieLifeSecond);
//记录用户能访问的路径
(*cookie)[kAccessPathKey] = accessPath;
//判断该用户是否有权限访问该目录并且设置客户端cookie
callback(!accessPath.empty() && path.find(accessPath) == 0, cookie);
}else{
//仅限本次访问文件
callback(!accessPath.empty() && path.find(accessPath) == 0, nullptr);
}
}); });
}; };
@ -439,7 +448,7 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
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){
//此事件无人监听,我们默认都有权限访问 //此事件无人监听,我们默认都有权限访问
callback(true, nullptr); callback("", nullptr);
} }
} }
@ -497,15 +506,16 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
} }
//判断是否有权限访问该目录 //判断是否有权限访问该目录
canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](bool canAccess,const HttpServerCookie::Ptr &cookie){ auto path = _parser.Url();
if(!canAccess){ canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun,path](const string &errMsg,const HttpServerCookie::Ptr &cookie){
const_cast<string &>(strMeun) = kAccessDirUnauthorized; if(!errMsg.empty()){
const_cast<string &>(strMeun) = errMsg;
} }
auto headerOut = makeHttpHeader(bClose,strMeun.size()); auto headerOut = makeHttpHeader(bClose,strMeun.size());
if(cookie){ if(cookie){
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kAccessPathKey]); headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
} }
sendResponse(canAccess ? "200 OK" : "401 Unauthorized" , headerOut, strMeun); sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" , headerOut, strMeun);
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder"); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
}); });
return; return;
@ -534,7 +544,16 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
auto parser = _parser; auto parser = _parser;
//判断是否有权限访问该文件 //判断是否有权限访问该文件
canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](bool canAccess,const HttpServerCookie::Ptr &cookie){ canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](const string &errMsg,const HttpServerCookie::Ptr &cookie){
if(!errMsg.empty()){
auto headerOut = makeHttpHeader(bClose,errMsg.size());
if(cookie){
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
}
sendResponse("401 Unauthorized" , headerOut, errMsg);
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed");
}
//判断是不是分节下载 //判断是不是分节下载
auto &strRange = parser["Range"]; auto &strRange = parser["Range"];
int64_t iRangeStart = 0, iRangeEnd = 0; int64_t iRangeStart = 0, iRangeEnd = 0;
@ -552,8 +571,7 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
pcHttpResult = "206 Partial Content"; pcHttpResult = "206 Partial Content";
fseek(pFilePtr.get(), iRangeStart, SEEK_SET); fseek(pFilePtr.get(), iRangeStart, SEEK_SET);
} }
auto httpHeader = canAccess ? makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data())) auto httpHeader = makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()));
: makeHttpHeader(bClose, kAccessFileUnauthorized.size());
if (strRange.size() != 0) { if (strRange.size() != 0) {
//分节下载返回Content-Range头 //分节下载返回Content-Range头
httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl); httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl);
@ -564,12 +582,10 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
httpHeader["Access-Control-Allow-Credentials"] = "true"; httpHeader["Access-Control-Allow-Credentials"] = "true";
} }
if(cookie){
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kAccessPathKey]);
}
//先回复HTTP头部分 //先回复HTTP头部分
sendResponse(canAccess ? pcHttpResult : "401 Unauthorized" , httpHeader,canAccess ? "" : kAccessFileUnauthorized); sendResponse(pcHttpResult,httpHeader,"");
if (!canAccess || iRangeEnd - iRangeStart < 0) {
if (iRangeEnd - iRangeStart < 0) {
//文件是空的! //文件是空的!
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file"); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file");
} }

View File

@ -52,7 +52,12 @@ public:
const KeyValue &headerOut, const KeyValue &headerOut,
const string &contentOut)> HttpResponseInvoker; const string &contentOut)> HttpResponseInvoker;
typedef std::function<void(const string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker; /**
* @param errMsg
* @param accessPath 访
* @param cookieLifeSecond cookie有效期
**/
typedef std::function<void(const string &errMsg,const string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker;
HttpSession(const Socket::Ptr &pSock); HttpSession(const Socket::Ptr &pSock);
virtual ~HttpSession(); virtual ~HttpSession();
@ -125,7 +130,7 @@ private:
* @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(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback);
/** /**
* id * id