Nginx鉴权功能实现
背景
鉴权的功能是防止盗链,别人一直访问你的连接,把你的服务器打爆,当鉴权失败时(md5值计算错误、时间戳过期),nginx直接返回403。
鉴权主要分为四种:
- 时间戳鉴权
- 远程鉴权
- Referer黑白名单
- IP黑白名单
在使用上,一般推荐时间戳鉴权和远程鉴权,或者两者同时使用,剩下两项规避鉴权非常容易。
时间戳鉴权介绍(LSS的方式)
为确保视资源被非法获取,提供token认证和有效期限相结合的播放地址。开启时间戳鉴权后,通过”播放地址+timestamp+secret”的方式获取完整的加密播放地址。
地址如下:
https://domain.com/live/123.m3u8?timestamp=1680709499&secret=af921173a6c64469f31fbba2fa7e7110
算secret,方式为:md5(密钥+播放地址+timestamp)
。
-
密钥:在播放认证开关可以获取到播放密钥,按照步骤一已获取。
-
播放地址:
/<play.your-domain.com>/{app-name}/{stream-name}
。 -
timestamp:用户指定播放的超时时间,格式需要转换为十进制 Unix 时间戳,推荐一个在线转换网址。
-
将(密钥+播放地址+timestamp)拼接完成后,加密为32位小写的md5码。
假设key=111, domain = play.domain.com, app=live, stream=123, timestamp=1680709499,md5加密后的secret为:md5(111/domain.com/live/1680709499)=秘钥
中心鉴权介绍
中心鉴权是指鉴权由用户测规定,用户请求到nginx后,nginx会发送一个请求到鉴权服务器,由鉴权服务器返回的状态码来判断这个请求鉴权是否通过。
中心鉴权没有规范可言,基本都是定制化。
功能实现
通过lua实现的逻辑如下:
- 修改nginx.conf 主配置文件,增加rewrite_by_lua_file ,指定改写脚本
http {
access_by_lua_file lua/access/auth.lua;
}
- 编写处理逻辑
local cjson = require "cjson"
local ssl = require "ngx.ssl"
local http = require('resty.http')
local globalconfiguration = require "configuration"
local tool = require "tool"
local dy_conf = ngx.shared.conf
local function isintable(value,tb)
for k,v in pairs(tb) do
if v == value then
return true
end
end
return false --重点:全部跑完以后,如果非true,则返回false
end
--远程鉴权使用函数,不使用
local function center_auth()
--获取鉴权地址
local auth_server = globalconfiguration.chinaunicom_center_auth --从配置文件中读取远程鉴权接口
--获取客户端IP
local remore_addr = ngx.var.remote_addr
-- 获取uri
local uri = ngx.var.uri
-- 获取域名
local headers = ngx.req.get_headers()
local host = headers["Host"]
--获取对应的id
local auth_field = "uid"
local args = ngx.req.get_uri_args()
local uid = args[auth_field]
if not uid then
ngx.log(ngx.ERR, "client not take cneter auth field: ".. auth_field)
ngx.exit(403)
end
ngx.log(ngx.INFO, "[debug]auth_serverL: " .. auth_server, "; remore_addr: "..remore_addr .. "; uri:".. uri, ";Host: " .. host)
local httpc = http.new()
local url = "http://" .. auth_server.. "/kuanshijie/center_auth?uid=".. uid .."&client_ip="..remore_addr.."&client_uri=" .. uri .. "&client_host=" .. host
ngx.log(ngx.ERR, "uri: ".. url)
httpc:set_timeout(500)
local res, err = httpc:request_uri(url, {
keepalive_timeout = 500 -- 毫秒
})
ngx.log(ngx.INFO, "center auth status : ".. res.status)
if not res then --超时放行
return
end
if res.status == 403 then
ngx.log(ngx.ERR, "center auth response 403")
ngx.exit(403)
end
end
--请求接口
local function lss_auth(host,time_key)
--获取参数
local args = ngx.req.get_uri_args()
local client_host = args["client_host"]
--local client_uri = ngx.var.uri
local uri = ngx.var.request_uri
local client_uri = tool.split(uri,"?")[1]
local timestamp = args["timestamp"]
if timestamp == nil then
ngx.log(ngx.ERR, "ERROR timestamp is nil")
ngx.exit(403)
end
local md5 = args["secret"]
local timestamp_key = time_key
local now = ngx.time()
ngx.log(ngx.ERR, "[debug]now: ".. now .."; timestamp: ".. timestamp)
if now > tonumber(timestamp) then
ngx.log(ngx.ERR, "ERROR req is expired! host: ", host, " uri: ", uri)
ngx.exit(403)
end
local idx = string.find(client_uri, ".m3u8")
local uri_tmp = string.sub(client_uri, 1, idx - 1)
local str = string.format("%s/%s%s%s", timestamp_key, host, uri_tmp, timestamp)
local val = ngx.md5(str)
if val ~= md5 then
ngx.log(ngx.ERR, "ERROR req md5 is error! secret: ", md5, " val: ", val, " str: ", str)
ngx.exit(403)
end
ngx.log(ngx.INFO, "DEBUG timestamp auth success! val: ", val, " timestamp: ", timestamp)
end
local function process()
local host = ssl.server_name()
local str = dy_conf:get(host) --从共享内存中读取域名配置
local conf = cjson.decode(str)
local auth = conf["conf"]["m3u8_auth_list"] --判断域名配置项
if auth then
-- 判断是否存在kuanshijie_center_auth
if isintable("center_auth",auth) then
--center_auth()
end
if isintable("lss_auth", auth) then
local time_key = conf["conf"]["cucc_anti_md5"] --读取key
ngx.log(ngx.ERR, "[debug] is match lss_auth")
lss_auth(host,time_key)
end
end
end
process()
以上代码块有编写时间戳鉴权和中心鉴权两个功能,案例只使用了时间戳鉴权。