尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Shaka Player 许可证服务器认证实战:Header、URL 参数、Cookie 与异步凭证完整指南

Shaka Player 许可证服务器认证实战:Header、URL 参数、Cookie 与异步凭证完整指南 Shaka Player 许可证服务器认证实战Header、URL 参数、Cookie 与异步凭证完整指南【免费下载链接】shaka-playerJavaScript player library / DASH HLS client / MSE-EME player项目地址: https://gitcode.com/GitHub_Trending/sh/shaka-player本指南以 Shaka Player 的许可证服务器License Server认证为主题系统讲解四种常见的认证方式——请求头认证、URL 参数认证、Cookie 认证与异步凭证获取并深入剖析其背后的请求过滤器Request Filter机制、跨域凭证Credentials规则与凭证过期重试方案。读完本文你将能够为 Shaka Player 接入任何需要认证的 DRM 许可证服务器并在凭证失效时实现自动续期让付费用户稳定获得播放授权。前置条件理解 EME 与安全 URL 限制本教程使用的演示许可证服务器是一个Widevine 许可证服务器因此需要使用 Chrome 浏览器才能顺利跟进。同时由于EMEEncrypted Media Extensions要求使用安全 URL你必须在localhost或https环境下运行本教程此外如果站点本身使用https由于混合内容Mixed Content的限制Manifest 与所有分片也必须使用https。更详细的原因可参考 DRM 配置教程 开头的 EME and http URLs 一节。在开始之前请先以 基础用法教程 中的代码为基础替换为以下 Manifest 与许可证服务器地址const manifestUri https://storage.googleapis.com/shaka-demo-assets/sintel-widevine/dash.mpd; const licenseServer https://cwip-shaka-proxy.appspot.com/no_auth;同时需要在加载 Manifest 之前将许可证服务器配置进播放器。drm.servers是一个Key System ID → 服务器 URL的映射对象示例中的com.widevine.alpha即为 Widevine 的 Key System IDplayer.configure({ drm: { servers: { com.widevine.alpha: licenseServer } } }); // Try to load a manifest. try { await player.load(manifestUri); // The video should now be playing! } catch (e) { onError(e); }由于该端点路径为/no_auth无需任何认证即可正常播放。后续各节将在此基础上逐步引入认证机制。Header 认证两种添加请求头的方式将许可证服务器切换为header_auth端点const licenseServer https://cwip-shaka-proxy.appspot.com/header_auth;该端点要求请求携带一个特定请求头才会下发许可证。如果直接请求而未设置该认证头你会看到Error code 6007即LICENSE_REQUEST_FAILED许可证请求失败。此时打开 JavaScript 控制台可以看到一次失败的 HTTP 请求状态码为401 (Unauthorized)播放到流的加密部分约 10 秒处时播放会一直挂起等待。Shaka Player 提供了两种为请求添加任意请求头的方式二选一即可都必须在player.load()之前完成配置。方式一通过drm.advanced.headers静态配置直接通过player.configure()在drm.advanced下为指定 Key System 配置headers对象player.configure({ drm: { servers: { com.widevine.alpha: https://cwip-shaka-proxy.appspot.com/header_auth }, advanced: { com.widevine.alpha: { headers: { // This is the specific header name and value the server wants: CWIP-Auth-Header: VGhpc0lzQVRlc3QK, } } } } });从源码实现看lib/drm/drm_engine.js 在构造许可证请求时会读取advancedConfig.headers并将其合并merge进已有请求头而不是覆盖——源码注释明确指出Add these to the existing headers. Do not clobber them! For PlayReady, there will already be headers in the request.PlayReady 的许可证请求中可能已带有来自其 Challenge XML 的HttpHeader请求头。这一合并逻辑位于createLicenseRequest_相关方法中lib/drm/drm_engine.js。headers字段的默认空对象定义在 lib/util/player_configuration.js 的drm.advanced默认配置中。方式二通过请求过滤器Request Filter动态添加注册一个请求过滤器Request Filter在请求真正发出前拦截并修改它。过滤器回调接收(type, request, context)三个参数其中type用于区分请求类型request是一个可被就地修改的可变对象player.getNetworkingEngine().registerRequestFilter((type, request, context) { // Only add headers to license requests: if (type shaka.net.NetworkingEngine.RequestType.LICENSE) { // This is the specific header name and value the server wants: request.headers[CWIP-Auth-Header] VGhpc0lzQVRlc3QK; } });两种方式配置完成后重新加载页面许可证请求即可成功。虽然本教程为演示方便使用了固定值但你的应用完全可以在回调执行前或回调执行期间动态推导出合适的认证请求头——这正是请求过滤器相较静态配置更灵活的地方。需要说明的是registerRequestFilter/unregisterRequestFilter由 lib/net/networking_engine.js 提供内部通过requestFilters_集合维护可注册多个过滤器对应的还有registerResponseFilter/unregisterResponseFilter用于响应阶段。请求过滤器会在每次请求发出前依次执行且每次发送含重试前都会重新执行。URL 参数认证在请求过滤器中改写 URI将许可证服务器切换为param_auth端点const licenseServer https://cwip-shaka-proxy.appspot.com/param_auth;该端点要求请求 URL 携带一个特定查询参数才会下发许可证。如果不设置该参数你会遇到与 Header 认证相同的Error code 6007LICENSE_REQUEST_FAILED。由于 Shaka Player 的每个网络请求可能拥有多个 URI 用于回退fallback因此request.uris是一个数组。在请求过滤器中追加参数时需要对数组中的相应元素做字符串拼接player.getNetworkingEngine().registerRequestFilter((type, request, context) { // Only add headers to license requests: if (type shaka.net.NetworkingEngine.RequestType.LICENSE) { // This is the specific parameter name and value the server wants: // Note that all network requests can have multiple URIs (for fallback), // and therefore this is an array. But there should only be one license // server URI in this tutorial. request.uris[0] ?CWIP-Auth-ParamVGhpc0lzQVRlc3QK; } });重新加载页面后许可证请求即可成功。从源码看lib/net/networking_engine.js 在request()内部会先对request.uris做一次浅拷贝ObjectUtils.cloneObject(request.uris)确保过滤器对 URI 的修改不会污染后续请求这也意味着每次请求包括重试都会从原始 URI 重新经过过滤器保证你的参数拼接逻辑每次都生效。Cookie 认证跨站点凭证Credentials规则将许可证服务器切换为cookie_auth端点const licenseServer https://cwip-shaka-proxy.appspot.com/cookie_auth;该端点要求请求携带一个特定 Cookie 才会下发许可证。如果不携带仍会得到Error code 6007LICENSE_REQUEST_FAILED。Cookie 是由服务器设置、并由浏览器在后续请求中自动回传给该服务器的JavaScript 应用本身无法直接发送Cookie。因此要设置所需的 Cookie 值只需让浏览器访问该演示服务器的set_cookie页面即cwip-shaka-proxy.appspot.com的/set_cookie路径即可。然后在 JavaScript 控制台中检查document.cookie的值确认已获得该 Cookie——你应当看到形如CWIP-Auth-CookieVGhpc0lzQVRlc3QK的内容。此时重新加载 Shaka Player 页面你仍然会得到错误码 6007。这是为什么关键在于Cookie 被浏览器的 XmlHttpRequest API 视为凭证credentials而跨源cross-origin请求默认不会携带凭证除非同时满足两个条件应用显式请求携带凭证对应 XHR 的withCredentials true目标服务器显式允许凭证请求即响应头Access-Control-Allow-Credentials: true。本教程的cookie_auth端点在响应中返回了允许携带凭证的 CORS 响应头因此只需在请求过滤器中为许可证请求开启跨站点凭证开关player.getNetworkingEngine().registerRequestFilter((type, request, context) { if (type shaka.net.NetworkingEngine.RequestType.LICENSE) { request.allowCrossSiteCredentials true; } });重新加载页面后许可证请求即可成功。从源码实现看请求对象request.allowCrossSiteCredentials的默认值是false见 lib/net/networking_engine.js 中NetworkingEngine.makeRequest()构造的默认请求结构XHR 插件会把它直接映射为xhr.withCredentials request.allowCrossSiteCredentials见 lib/net/http_xhr_plugin.js而 fetch 插件lib/net/http_fetch_plugin.js同样读取该字段以决定是否传递credentials。因此无论底层走 XHR 还是 fetchallowCrossSiteCredentials都是控制跨源凭证发送的统一开关。为什么不默认总是发送凭证你可能会问为什么不把true设为默认值只要手头有凭证就总是发送答案是如果服务器没有显式允许跨源凭证那么即便客户端一个 Cookie 都没有只要开启了该标志请求也会直接失败。你可以亲自验证将浏览器指向该演示服务器的delete_cookie页面cwip-shaka-proxy.appspot.com的/delete_cookie路径清空 Cookie将许可证服务器切换回const licenseServer https://cwip-shaka-proxy.appspot.com/no_auth;由于allowCrossSiteCredentials仍为true而no_auth端点并未显式允许凭证请求会失败。浏览器控制台会输出类似如下的错误信息Credentials flag is true, but the Access-Control-Allow-Credentials header is . It must be true to allow credentials. Origin http://localhost is therefore not allowed access.因此 Shaka Player 将该开关默认设为false交由应用根据目标服务器是否允许凭证请求来决定是否开启这正是跨源安全模型的要求。异步凭证在许可证请求前先获取授权令牌在某些场景下你可能无法立刻获得凭证——需要先发起一次额外的网络请求拿到授权令牌auth token再把它附加到 Shaka Player 想要发出的许可证请求上。自 Shaka Player v2.1.0 起请求过滤器支持异步执行。任何返回 Promise 的过滤器都是异步过滤器。由于 NetworkingEngine 本身基于 Promise 处理请求你可以在异步过滤器中方便地发起一次额外请求。首先切换许可证服务器并新增两个变量const licenseServer https://cwip-shaka-proxy.appspot.com/header_auth; const authTokenServer https://cwip-shaka-proxy.appspot.com/get_auth_token; const authToken null;然后改写请求过滤器player.getNetworkingEngine().registerRequestFilter((type, request, context) { // Only add headers to license requests: if (type ! shaka.net.NetworkingEngine.RequestType.LICENSE) return; // If we already know the token, attach it right away: if (authToken) { console.log(Have auth token, attaching to license request.); request.headers[CWIP-Auth-Header] authToken; return; } console.log(Need auth token.); // Start an asynchronous request, and return a Promise chain based on that. const authRequest { uris: [authTokenServer], method: POST, }; const requestType shaka.net.NetworkingEngine.RequestType.APP; return player.getNetworkingEngine().request(requestType, authRequest) .promise.then((response) { // This endpoint responds with the value we should use in the header. authToken shaka.util.StringUtils.fromUTF8(response.data); console.log(Received auth token, authToken); request.headers[CWIP-Auth-Header] authToken; console.log(License request can now continue.); }); });关键点拆解提前返回当已持有令牌时同步修改请求头并直接返回不返回 Promise过滤器立即完成RequestType.APP为令牌请求指定请求类型。NetworkingEngine.RequestType是 Shaka Player 定义的一套请求类型枚举其中LICENSE表示许可证请求APP表示应用自定义请求此外还有MANIFEST、SEGMENT等。使用APP类型可以避免令牌请求被误判为其他业务请求player.getNetworkingEngine().request(...)这是 NetworkingEngine 对外暴露的通用请求入口对应源码中的request()方法返回一个包含.promise的PendingRequest对象其中.promise在响应就绪时 resolve 出response含data等字段.promise.then(...)链过滤器返回这个 Promise 链NetworkingEngine 会等待该 Promise 完成后再真正发出许可证请求从而实现了先取令牌、再发许可证请求的串行化流程。重新加载页面后你会观察到许可证请求被延迟先额外发起一次令牌请求随后许可证请求才继续。控制台应依次输出Need auth token. Received auth token VGhpc0lzQVRlc3QK License request can now continue.如果你需要同样可以按此方式编写异步响应过滤器async response filters在许可证响应返回后异步处理响应数据。错误处理凭证过期后的自动刷新重试在实际生产环境中用于认证的凭证如授权令牌可能已经过期。例如令牌有效期较短播放较长时间后令牌失效许可证请求会以401失败。自 v5.0 起请求过滤器会在每次请求尝试包括重试前都被调用因此你可以在过滤器内检查是否需要刷新凭证。结合 NetworkingEngine 的retry事件与请求对象上的request.attempt当前重试次数由 lib/net/networking_engine.js 在每次请求时初始化并递增可以实现首次失败后自动刷新凭证再重试的完整闭环let authToken await getAuthToken(); // Token we get from an external service // Flag we will use to signal do we need to update credentials let lastLicenseRequestFailed false; player.getNetworkingEngine().addEventListener(retry, (event /* shaka.net.NetworkingEngine.RetryEvent */) { const code event.error.code; const data event.error.data; if (code shaka.util.Error.Code.BAD_HTTP_STATUS) { if ( Array.isArray(data) // 401 HTTP Unauthorized data[1] 401 data[4] shaka.net.NetworkingEngine.RequestType.LICENSE ) { // If we have unauthorized error it looks like we will have to update our credentials. lastLicenseRequestFailed true; } } }); player.getNetworkingEngine().registerRequestFilter(async (type, request, context) { if (type shaka.net.NetworkingEngine.RequestType.LICENSE) { if (lastLicenseRequestFailed request.attempt 0) { // Fetch new credentials authToken await getAuthToken(); } lastLicenseRequestFailed false; request.headers[CWIP-Auth-Header] authToken; } });这段代码的完整逻辑为应用启动时先从外部服务获取初始令牌监听 NetworkingEngine 的retry事件——该事件在引擎收到可恢复错误并准备重试时触发源码中通过dispatchEvent派发一个retry类型的FakeEvent事件带error属性且可被preventDefault()取消后续重试见 lib/net/networking_engine.js 的重试逻辑在retry事件回调中判断错误是否为BAD_HTTP_STATUSHTTP 状态错误。shaka.util.Error的data数组在该错误码下包含多个字段其中data[1]是 HTTP 状态码、data[4]是请求类型当状态码为401且请求类型为LICENSE时判定为许可证请求因凭证失效而被拒置位lastLicenseRequestFailed在请求过滤器中若标志位置位且request.attempt 0说明是重试请求而非首次尝试则先await getAuthToken()异步刷新令牌再将其写入请求头随后复位标志位。这样当凭证过期触发401时NetworkingEngine 会自动重试而重试请求在过滤器阶段已拿到新令牌从而在无需用户干预的情况下完成凭证续期。小结四种认证方式的选用建议认证方式核心机制关键代码点适用场景Header 认证在请求头中携带静态或动态凭证drm.advanced.headers或过滤器中写request.headers最常见的 Bearer Token / API Key 认证URL 参数认证在请求 URL 上追加查询参数过滤器中拼接request.uris[0]服务器仅接受 URL 签名的场景Cookie 认证由浏览器携带 Cookie需开启跨源凭证request.allowCrossSiteCredentials true会话式登录、SSO 场景异步凭证先请求令牌再发许可证请求过滤器返回 Promise调用NetworkingEngine.request()令牌需临时获取或定期刷新的场景无论选择哪种方式核心都围绕 Shaka Player 的NetworkingEngine 请求/响应过滤器体系它是应用介入网络请求的统一入口所有许可证请求以及 Manifest、分片、应用请求在发出前都会经过注册的过滤器配合retry事件与request.attempt足以构建生产级、可自动续期的许可证认证方案。深入阅读 lib/net/networking_engine.js 可以进一步了解过滤器集合的管理、默认重试参数defaultRetryParameters以及请求对象各字段的语义。【免费下载链接】shaka-playerJavaScript player library / DASH HLS client / MSE-EME player项目地址: https://gitcode.com/GitHub_Trending/sh/shaka-player创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表