LibVLC 与 ASP.NET 授权 HLS 流的 Cookie 转发问题
结论
--http-forward-cookies 无法可靠解决这个问题。它主要用于在 HTTP 请求链中转发 Cookie,例如处理重定向,但不保证 LibVLC 3 的 HLS adaptive 模块会保存 master playlist 响应中的 Set-Cookie,并把 Cookie 带到子 playlist、初始化文件和媒体分片请求中。
从日志可以看出问题所在:
set-cookie: "stream_session=...; path=/; samesite=lax; httponly"
...
Retrieving https://localhost:7002/.../audio_64k.m3u8
...
:status: "401"
第一条日志只能证明 LibVLC 收到了 Set-Cookie 响应头,无法证明 Cookie 已保存到后续 HLS 请求使用的 Cookie 容器中。子 playlist 由 adaptive 模块单独请求。该请求没有携带 Cookie,服务器因此返回 401。
生产环境建议使用有效期较短、限制资源范围的流访问令牌,并把令牌写入每个 HLS 资源 URI。也可以通过受控代理统一添加认证信息。播放授权不应依赖 LibVLC 自动接收和转发 Cookie。
为什么当前配置不起作用
一次 HLS 播放会发起多次 HTTP 请求,可能涉及:
- master playlist;
- variant/media playlist;
EXT-X-MAP指定的初始化文件;- 音视频分片;
- 加密密钥或字幕等附加资源。
master playlist 中的相对地址,例如:
audio_64k.m3u8
会被解析为:
https://localhost:7002/api/stream/{trackId}/audio_64k.m3u8
master URL 的查询参数不会自动传给子请求。因此:
master.m3u8?session=abc
对应的子请求仍然是原始地址,并不会自动变成:
audio_64k.m3u8?session=abc
--http-forward-cookies 也不会把收到的所有 Cookie 自动附加到全部 HLS 子请求。即使同时把它配置为 LibVLC 实例参数和 media option,这一限制仍然存在。
日志里的 206 Partial Content 来自 enableRangeProcessing: true,表明 master playlist 的 Range 请求已经成功返回,并非故障。
推荐方案:让每个 HLS URI 都携带令牌
客户端先通过受认证的 API 获取短期流令牌。令牌应当:
- 使用不可预测的随机值或带签名的值;
- 绑定到指定
track_id; - 设置较短的有效期;
- 可选地绑定用户、设备或播放会话;
- 支持撤销,并限制使用范围。
客户端只需把令牌放在 master playlist URL 中:
var session = await m_StreamService.CreateSession(track);
var url =
$"https://my-server/hls/{track}/master.m3u8" +
$"?session={Uri.EscapeDataString(session.ToString())}";
using var media = new Media(m_LibVLC, url, FromType.FromLocation);
media.AddOption(":network-caching=1000");
media.AddOption(":live-caching=1000");
media.AddOption(":http-reconnect");
m_MediaPlayer.Media = media;
服务器返回 master playlist 时,把令牌追加到每个子 playlist URI:
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.2"
audio_64k.m3u8?session=8dd048ec-1906-44f3-b615-9c0c820c27ef
#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="mp4a.40.2"
audio_128k.m3u8?session=8dd048ec-1906-44f3-b615-9c0c820c27ef
media playlist 中的初始化文件、分片和密钥等 URI 也需要携带令牌:
#EXTM3U
#EXT-X-MAP:URI="init_audio.mp4?session=8dd048ec-1906-44f3-b615-9c0c820c27ef"
#EXTINF:6.000,
segment_0001.m4s?session=8dd048ec-1906-44f3-b615-9c0c820c27ef
#EXTINF:6.000,
segment_0002.m4s?session=8dd048ec-1906-44f3-b615-9c0c820c27ef
如果使用 EXT-X-KEY、EXT-X-MEDIA 或字幕 playlist,也要改写相应的 URI。
ASP.NET 端的实现方式
所有 HLS 资源可以共用一套授权检查:
private bool TryValidateStreamSession(
Guid trackId,
Guid session,
out IActionResult? error)
{
if (session == Guid.Empty)
{
error = Unauthorized();
return false;
}
if (!m_StreamingService.ValidateSession(session, trackId))
{
error = Unauthorized();
return false;
}
error = null;
return true;
}
子 playlist 和分片接口直接读取查询参数:
[HttpGet("{track_id}/{playlist}.m3u8")]
public IActionResult GetAudio(
[FromRoute] Guid track_id,
[FromRoute] string playlist,
[FromQuery] Guid session)
{
if (!TryValidateStreamSession(track_id, session, out var error))
return error!;
// 校验 playlist,读取文件并改写其中的所有资源 URI。
// 返回 Content-Type: application/vnd.apple.mpegurl
}
[HttpGet("{track_id}/{segment}.m4s")]
public IActionResult GetSegment(
[FromRoute] Guid track_id,
[FromRoute] string segment,
[FromQuery] Guid session)
{
if (!TryValidateStreamSession(track_id, session, out var error))
return error!;
// 校验路径并返回分片。
}
不要对 playlist 文本直接做全局字符串替换。以下内容需要分别处理:
- 非注释行中的资源地址;
EXT-X-MAP的URI;EXT-X-KEY的URI;EXT-X-MEDIA的URI;- 已有查询参数的 URI;
- 绝对 URI 和相对 URI。
URI 已有查询参数时使用 &session=...,没有查询参数时使用 ?session=...。令牌值还需要经过 URL 编码。
能否直接给 LibVLC 设置 Cookie
部分 LibVLC 构建或访问模块可能支持类似下面的 media option:
media.AddOption($":http-cookie=stream_session={session}");
这个选项可以用于兼容性测试,但不能默认它会传递到 LibVLC 3 adaptive 模块创建的所有 HLS 请求。不同 LibVLC 版本、平台和网络访问模块的行为可能不同,需要实际检查子 playlist、分片和 EXT-X-MAP 请求。
即使初始请求携带了 Cookie,也要逐项验证:
- 子 playlist 是否携带
Cookie; - 分片和初始化文件是否携带
Cookie; - playlist 更新、码率切换和重连后是否继续携带;
- 跳转到其他主机或 CDN 后是否仍符合预期。
未经验证的 :http-cookie 或自定义 header option 不适合作为唯一的访问控制措施。
另一种可靠方案:本地认证代理
如果不希望令牌出现在每个上游 URL 中,可以在 Avalonia 客户端内运行一个只监听回环地址的 HTTP 代理:
LibVLC
-> http://127.0.0.1:{randomPort}/stream/master.m3u8
-> Avalonia 本地代理
-> ASP.NET HLS 服务
本地代理负责:
- 使用
HttpClient请求上游资源; - 在每个请求中添加
Authorization或 Cookie; - 把 playlist 内的 URI 改写为本地代理地址;
- 将 Range、状态码、Content-Type 和响应体转发给 LibVLC。
这样可以完全控制认证头,避开 LibVLC 的 Cookie 行为差异。实现时仍要正确处理 Range 请求、并发、取消、缓存、playlist 刷新和 URI 改写。
安全注意事项
查询参数会出现在 URL 中,需要采取以下措施:
- 全程使用 HTTPS;
- 不要把完整查询字符串写入访问日志;
- 设置较短的令牌有效期;
- 将令牌绑定到
track_id,避免跨资源复用; - 播放结束后尽可能撤销会话;
- 不要用可预测的 GUID 或用户 ID 直接充当授权凭据;
- 避免令牌通过重定向、错误页面或外部资源泄露;
- 明确缓存策略,防止授权 playlist 进入共享缓存。
例如,动态 playlist 可以返回:
Response.Headers.CacheControl = "private, no-store";
媒体分片是否允许缓存,需要根据 CDN 和令牌校验方式单独设计。如果 URL 包含令牌,还要确认缓存键包含查询参数。
如果继续保留 Cookie 方案,至少应明确设置:
Response.Cookies.Append(
"stream_session",
session.ToString(),
new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Lax,
Path = $"/api/stream/{track_id}/"
});
这些属性可以限制 Cookie 的使用范围,却不能促使 LibVLC 的 HLS 模块自动共享 Cookie。
如何选择
对现有架构改动最小的做法,是继续使用当前 session 服务,同时取消子请求对 Cookie 的依赖。服务器动态改写 master playlist 和 media playlist,让每个 HLS URI 都携带短期 session。
如果访问令牌不能出现在 URL 中,可以使用本地代理,或者使用服务端/CDN 提供的签名 URL、签名 Cookie 机制。采用签名 Cookie 前,仍需确认当前 LibVLC 版本会在所有 HLS 子请求中发送 Cookie。
备注:内容仅供参考。
