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

View File

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

View File

@ -76,6 +76,10 @@ bool HttpServerCookie::isExpired() {
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{
char buf[64];
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() ; ){
if(it_cookie->second->isExpired()){
//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);
continue;
}
@ -114,7 +118,7 @@ void HttpCookieManager::onManager() {
if(it_name->second.empty()){
//该类型下没有任何cooki记录,移除之
WarnL << "该path下没有任何cooki记录:" << it_name->first;
DebugL << "该path下没有任何cooki记录:" << it_name->first;
it_name = _map_cookie.erase(it_name);
continue;
}
@ -152,6 +156,7 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name,con
}
if(it_cookie->second->isExpired()){
//cookie过期
DebugL << "cookie过期:" << it_cookie->second->getCookie();
it_name->second.erase(it_cookie);
return nullptr;
}

View File

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

View File

@ -49,11 +49,10 @@ using namespace toolkit;
namespace mediakit {
static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE;
static int kHlsCookieSecond = 10 * 60;
static const string kCookieName = "ZL_COOKIE";
static const string kAccessPathKey = "kAccessPathKey";
static const string kAccessDirUnauthorized = "你没有权限访问该目录";
static const string kAccessFileUnauthorized = "你没有权限访问该文件";
static const string kCookiePathKey = "kCookiePathKey";
static const string kAccessErrKey = "kAccessErrKey";
string dateStr() {
char buf[64];
@ -346,13 +345,8 @@ static inline bool checkHls(BroadcastHttpAccessArgs){
}
//访问的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);
}
//cookie有效期为kHlsCookieSecond
invoker(err,"",kHlsCookieSecond);
};
auto args_copy = args;
@ -360,13 +354,13 @@ static inline bool checkHls(BroadcastHttpAccessArgs){
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;
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 {
callback_in(canAccess,cookie);
callback_in(errMsg,cookie);
}catch (SockException &ex){
if(ex){
shutdown(ex);
@ -386,48 +380,63 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
}
if(cookie){
//找到了cookie
auto accessPath = (*cookie)[kAccessPathKey];
if (!accessPath.empty() && path.find(accessPath) == 0) {
//用户是有权限访问该目录
callback(true, nullptr);
//找到了cookie对cookie上锁先
auto lck = cookie->getLock();
auto accessErr = (*cookie)[kAccessErrKey];
if (accessErr.empty() && path.find((*cookie)[kCookiePathKey]) == 0) {
//用户有权限访问该目录
callback("", nullptr);
return;
}
//用户无权限访问,我们看看用户的url参数变了没有
//如果url参数变了那么重新鉴权
if (cookie->getUid() == _parser.Params()) {
if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) {
//url参数未变那么判断无权限访问
callback(false, nullptr);
callback(accessErr.empty() ? "无权限访问该目录" : accessErr, nullptr);
return;
}
//如果url参数变了那么旧cookie失效我们重新鉴权
HttpCookieManager::Instance().delCookie(cookie);
}
//该用户从来未获取过cookie这个时候我们广播是否允许该用户访问该http目录
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();
if (!strongSelf) {
//自己已经销毁
return;
}
strongSelf->async([weakSelf, callback, accessPath, cookieLifeSecond, uid , path]() {
strongSelf->async([weakSelf,callback,cookie,errMsg]() {
//切换到自己线程
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
//自己已经销毁
return;
}
if(cookieLifeSecond){
//我们给用户生成追踪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);
}
callback(errMsg, cookie);
});
};
@ -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);
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){
if(!canAccess){
const_cast<string &>(strMeun) = kAccessDirUnauthorized;
auto path = _parser.Url();
canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun,path](const string &errMsg,const HttpServerCookie::Ptr &cookie){
if(!errMsg.empty()){
const_cast<string &>(strMeun) = errMsg;
}
auto headerOut = makeHttpHeader(bClose,strMeun.size());
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");
});
return;
@ -534,7 +544,16 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
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"];
int64_t iRangeStart = 0, iRangeEnd = 0;
@ -552,8 +571,7 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
pcHttpResult = "206 Partial Content";
fseek(pFilePtr.get(), iRangeStart, SEEK_SET);
}
auto httpHeader = canAccess ? makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()))
: makeHttpHeader(bClose, kAccessFileUnauthorized.size());
auto httpHeader = makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()));
if (strRange.size() != 0) {
//分节下载返回Content-Range头
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";
}
if(cookie){
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kAccessPathKey]);
}
//先回复HTTP头部分
sendResponse(canAccess ? pcHttpResult : "401 Unauthorized" , httpHeader,canAccess ? "" : kAccessFileUnauthorized);
if (!canAccess || iRangeEnd - iRangeStart < 0) {
sendResponse(pcHttpResult,httpHeader,"");
if (iRangeEnd - iRangeStart < 0) {
//文件是空的!
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file");
}

View File

@ -52,7 +52,12 @@ public:
const KeyValue &headerOut,
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);
virtual ~HttpSession();
@ -125,7 +130,7 @@ private:
* @param is_dir path是否为目录
* @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