AI: HLS: Support query string in hls_key_url for JWT tokens. v7.0.120 (#4426)
This commit is contained in:
parent
0c26a3c309
commit
99970d6ba0
|
|
@ -1540,8 +1540,11 @@ vhost hls.srs.com {
|
||||||
hls_key_file_path ./objs/nginx/html;
|
hls_key_file_path ./objs/nginx/html;
|
||||||
# the key root URL, use this can support https.
|
# the key root URL, use this can support https.
|
||||||
# @remark It's optional.
|
# @remark It's optional.
|
||||||
|
# @remark Supports query string for authentication tokens (e.g., JWT).
|
||||||
|
# Example: http://localhost:8080/?token=abc123&sig=xyz789
|
||||||
|
# Result in m3u8: http://localhost:8080/live/livestream-0.key?token=abc123&sig=xyz789
|
||||||
# Overwrite by env SRS_VHOST_HLS_HLS_KEY_URL for all vhosts.
|
# Overwrite by env SRS_VHOST_HLS_HLS_KEY_URL for all vhosts.
|
||||||
hls_key_url https://localhost:8080;
|
hls_key_url http://localhost:8080/;
|
||||||
|
|
||||||
# Special control controls.
|
# Special control controls.
|
||||||
###########################################
|
###########################################
|
||||||
|
|
|
||||||
35
trunk/conf/hls-encrypted-query.conf
Normal file
35
trunk/conf/hls-encrypted-query.conf
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Test config for HLS encryption with query string in hls_key_url
|
||||||
|
# This tests the fix for issue #4426
|
||||||
|
|
||||||
|
listen 1935;
|
||||||
|
max_connections 1000;
|
||||||
|
daemon off;
|
||||||
|
srs_log_tank console;
|
||||||
|
|
||||||
|
http_server {
|
||||||
|
enabled on;
|
||||||
|
listen 8080;
|
||||||
|
dir ./objs/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
vhost __defaultVhost__ {
|
||||||
|
hls {
|
||||||
|
enabled on;
|
||||||
|
hls_fragment 10;
|
||||||
|
hls_window 60;
|
||||||
|
hls_path ./objs/nginx/html;
|
||||||
|
hls_m3u8_file [app]/[stream].m3u8;
|
||||||
|
hls_ts_file [app]/[stream]-[seq].ts;
|
||||||
|
|
||||||
|
# Enable AES-128 encryption
|
||||||
|
hls_keys on;
|
||||||
|
hls_fragments_per_key 5;
|
||||||
|
hls_key_file [app]/[stream]-[seq].key;
|
||||||
|
hls_key_file_path ./objs/nginx/html;
|
||||||
|
|
||||||
|
# Test with query string - this should now work correctly
|
||||||
|
# Expected result in m3u8: http://localhost:8080/live/livestream-0.key?token=abc123&sig=xyz789
|
||||||
|
hls_key_url http://localhost:8080/?token=abc123&sig=xyz789;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -7,6 +7,7 @@ The changelog for SRS.
|
||||||
<a name="v7-changes"></a>
|
<a name="v7-changes"></a>
|
||||||
|
|
||||||
## SRS 7.0 Changelog
|
## SRS 7.0 Changelog
|
||||||
|
* v7.0, 2025-11-07, AI: HLS: Support query string in hls_key_url for JWT tokens. v7.0.120 (#4426)
|
||||||
* v7.0, 2025-11-07, AI: RTC: Support keep_original_ssrc to preserve SSRC and timestamps. v7.0.119 (#3850)
|
* v7.0, 2025-11-07, AI: RTC: Support keep_original_ssrc to preserve SSRC and timestamps. v7.0.119 (#3850)
|
||||||
* v7.0, 2025-11-05, AI: WebRTC: Report video/audio codec info and frame stats in HTTP API. v7.0.118 (#4554)
|
* v7.0, 2025-11-05, AI: WebRTC: Report video/audio codec info and frame stats in HTTP API. v7.0.118 (#4554)
|
||||||
* v7.0, 2025-11-04, AI: SRT: Report video/audio codec info and frame stats in HTTP API. v7.0.117 (#4554)
|
* v7.0, 2025-11-04, AI: SRT: Report video/audio codec info and frame stats in HTTP API. v7.0.117 (#4554)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ using namespace std;
|
||||||
#include <srs_kernel_utility.hpp>
|
#include <srs_kernel_utility.hpp>
|
||||||
#include <srs_protocol_amf0.hpp>
|
#include <srs_protocol_amf0.hpp>
|
||||||
#include <srs_protocol_format.hpp>
|
#include <srs_protocol_format.hpp>
|
||||||
|
#include <srs_protocol_http_stack.hpp>
|
||||||
#include <srs_protocol_rtmp_stack.hpp>
|
#include <srs_protocol_rtmp_stack.hpp>
|
||||||
#include <srs_protocol_stream.hpp>
|
#include <srs_protocol_stream.hpp>
|
||||||
|
|
||||||
|
|
@ -45,6 +46,33 @@ using namespace std;
|
||||||
// reset the piece id when deviation overflow this.
|
// reset the piece id when deviation overflow this.
|
||||||
#define SRS_JUMP_WHEN_PIECE_DEVIATION 20
|
#define SRS_JUMP_WHEN_PIECE_DEVIATION 20
|
||||||
|
|
||||||
|
// Build the full key URL by appending key_file to hls_key_url with proper query string handling.
|
||||||
|
// If hls_key_url contains query string like "http://localhost:8080/?token=abc",
|
||||||
|
// the result will be "http://localhost:8080/live/livestream-0.key?token=abc"
|
||||||
|
// @param hls_key_url The base URL which may contain query string
|
||||||
|
// @param key_file The key file path like "live/livestream-0.key"
|
||||||
|
// @return The full key URL with query string properly appended
|
||||||
|
string srs_hls_build_key_url(const string &hls_key_url, const string &key_file)
|
||||||
|
{
|
||||||
|
if (hls_key_url.empty()) {
|
||||||
|
return key_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the query string separator
|
||||||
|
size_t pos = hls_key_url.find("?");
|
||||||
|
if (pos != string::npos) {
|
||||||
|
// URL contains query string, split and rebuild
|
||||||
|
// Example: "http://localhost:8080/?token=abc" + "live/livestream-0.key"
|
||||||
|
// Result: "http://localhost:8080/live/livestream-0.key?token=abc"
|
||||||
|
string base_url = hls_key_url.substr(0, pos);
|
||||||
|
string query_string = hls_key_url.substr(pos); // Include the '?'
|
||||||
|
return base_url + key_file + query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No query string, simple concatenation
|
||||||
|
return hls_key_url + key_file;
|
||||||
|
}
|
||||||
|
|
||||||
SrsHlsSegment::SrsHlsSegment(SrsTsContext *c, SrsAudioCodecId ac, SrsVideoCodecId vc, ISrsFileWriter *w)
|
SrsHlsSegment::SrsHlsSegment(SrsTsContext *c, SrsAudioCodecId ac, SrsVideoCodecId vc, ISrsFileWriter *w)
|
||||||
{
|
{
|
||||||
sequence_no_ = 0;
|
sequence_no_ = 0;
|
||||||
|
|
@ -1107,11 +1135,7 @@ srs_error_t SrsHlsFmp4Muxer::do_refresh_m3u8_segment(SrsHlsM4sSegment *segment,
|
||||||
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
||||||
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
||||||
|
|
||||||
string key_path = key_file;
|
string key_path = srs_hls_build_key_url(hls_key_url_, key_file);
|
||||||
// if key_url is not set,only use the file name
|
|
||||||
if (!hls_key_url_.empty()) {
|
|
||||||
key_path = hls_key_url_ + key_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
ss << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
ss << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
||||||
}
|
}
|
||||||
|
|
@ -2040,11 +2064,7 @@ srs_error_t SrsHlsMuxer::do_refresh_m3u8_segment(SrsHlsSegment *segment, std::st
|
||||||
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
string key_file = srs_path_build_stream(hls_key_file_, req_->vhost_, req_->app_, req_->stream_);
|
||||||
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
key_file = srs_strings_replace(key_file, "[seq]", srs_strconv_format_int(segment->sequence_no_));
|
||||||
|
|
||||||
string key_path = key_file;
|
string key_path = srs_hls_build_key_url(hls_key_url_, key_file);
|
||||||
// if key_url is not set,only use the file name
|
|
||||||
if (!hls_key_url_.empty()) {
|
|
||||||
key_path = hls_key_url_ + key_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
ss << "#EXT-X-KEY:METHOD=AES-128,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
ss << "#EXT-X-KEY:METHOD=AES-128,URI=" << "\"" << key_path << "\",IV=0x" << hexiv << SRS_CONSTS_LF;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@
|
||||||
|
|
||||||
#define VERSION_MAJOR 7
|
#define VERSION_MAJOR 7
|
||||||
#define VERSION_MINOR 0
|
#define VERSION_MINOR 0
|
||||||
#define VERSION_REVISION 119
|
#define VERSION_REVISION 120
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -111,6 +111,7 @@ public:
|
||||||
void reset();
|
void reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef SRS_RTSP
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class SrsRtspConsumer;
|
class SrsRtspConsumer;
|
||||||
|
|
||||||
|
|
@ -159,5 +160,6 @@ public:
|
||||||
void set_send_error(srs_error_t err);
|
void set_send_error(srs_error_t err);
|
||||||
void reset();
|
void reset();
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1549,6 +1549,7 @@ void MockStatisticForRtspPlayStream::reset()
|
||||||
srs_freep(on_client_error_);
|
srs_freep(on_client_error_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SRS_RTSP
|
||||||
// MockRtspSourceManager implementation
|
// MockRtspSourceManager implementation
|
||||||
MockRtspSourceManager::MockRtspSourceManager()
|
MockRtspSourceManager::MockRtspSourceManager()
|
||||||
{
|
{
|
||||||
|
|
@ -2750,6 +2751,7 @@ VOID TEST(RtspTcpNetworkTest, WriteRtpPacket)
|
||||||
// Verify the payload data (starts at offset 4)
|
// Verify the payload data (starts at offset 4)
|
||||||
EXPECT_EQ(0, memcmp(rtp_packet, output + 4, kRtpPacketSize));
|
EXPECT_EQ(0, memcmp(rtp_packet, output + 4, kRtpPacketSize));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// MockDvrPlan implementation
|
// MockDvrPlan implementation
|
||||||
MockDvrPlan::MockDvrPlan()
|
MockDvrPlan::MockDvrPlan()
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,7 @@ public:
|
||||||
void reset();
|
void reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef SRS_RTSP
|
||||||
// Mock ISrsRtspSourceManager for testing SrsRtspPlayStream
|
// Mock ISrsRtspSourceManager for testing SrsRtspPlayStream
|
||||||
class MockRtspSourceManager : public ISrsRtspSourceManager
|
class MockRtspSourceManager : public ISrsRtspSourceManager
|
||||||
{
|
{
|
||||||
|
|
@ -456,6 +457,7 @@ public:
|
||||||
virtual void set_all_tracks_status(bool status);
|
virtual void set_all_tracks_status(bool status);
|
||||||
void reset();
|
void reset();
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
// Mock ISrsDvrPlan for testing SrsDvrSegmenter
|
// Mock ISrsDvrPlan for testing SrsDvrSegmenter
|
||||||
class MockDvrPlan : public ISrsDvrPlan
|
class MockDvrPlan : public ISrsDvrPlan
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ using namespace std;
|
||||||
#include <srs_utest_manual_kernel.hpp>
|
#include <srs_utest_manual_kernel.hpp>
|
||||||
#include <srs_utest_manual_protocol.hpp>
|
#include <srs_utest_manual_protocol.hpp>
|
||||||
|
|
||||||
|
#ifdef SRS_GB28181
|
||||||
// Mock ISrsGbMuxer implementation
|
// Mock ISrsGbMuxer implementation
|
||||||
MockGbMuxer::MockGbMuxer()
|
MockGbMuxer::MockGbMuxer()
|
||||||
{
|
{
|
||||||
|
|
@ -2294,6 +2295,7 @@ VOID TEST(GB28181Test, GoApiGbPublishSuccess)
|
||||||
|
|
||||||
srs_freep(conf);
|
srs_freep(conf);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Mock ISrsRtcNetwork implementation
|
// Mock ISrsRtcNetwork implementation
|
||||||
MockRtcNetworkForNetworks::MockRtcNetworkForNetworks()
|
MockRtcNetworkForNetworks::MockRtcNetworkForNetworks()
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include <srs_app_rtsp_conn.hpp>
|
#include <srs_app_rtsp_conn.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SRS_GB28181
|
||||||
// Mock ISrsGbMuxer for testing SrsGbSession
|
// Mock ISrsGbMuxer for testing SrsGbSession
|
||||||
class MockGbMuxer : public ISrsGbMuxer
|
class MockGbMuxer : public ISrsGbMuxer
|
||||||
{
|
{
|
||||||
|
|
@ -541,6 +542,7 @@ public:
|
||||||
#endif
|
#endif
|
||||||
void reset();
|
void reset();
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
// Mock ISrsRtcNetwork for testing SrsRtcNetworks
|
// Mock ISrsRtcNetwork for testing SrsRtcNetworks
|
||||||
class MockRtcNetworkForNetworks : public ISrsRtcNetwork
|
class MockRtcNetworkForNetworks : public ISrsRtcNetwork
|
||||||
|
|
|
||||||
|
|
@ -517,6 +517,7 @@ public:
|
||||||
virtual int get_rtc_drop_for_pt(std::string vhost) { return rtc_drop_for_pt_; }
|
virtual int get_rtc_drop_for_pt(std::string vhost) { return rtc_drop_for_pt_; }
|
||||||
virtual bool get_rtc_twcc_enabled(std::string vhost) { return rtc_twcc_enabled_; }
|
virtual bool get_rtc_twcc_enabled(std::string vhost) { return rtc_twcc_enabled_; }
|
||||||
virtual bool get_rtc_init_rate_from_sdp(std::string vhost) { return rtc_init_rate_from_sdp_; }
|
virtual bool get_rtc_init_rate_from_sdp(std::string vhost) { return rtc_init_rate_from_sdp_; }
|
||||||
|
virtual bool get_rtc_keep_original_ssrc(std::string vhost) { return false; }
|
||||||
virtual bool get_srt_enabled() { return srt_enabled_; }
|
virtual bool get_srt_enabled() { return srt_enabled_; }
|
||||||
virtual bool get_srt_enabled(std::string vhost) { return srt_enabled_; }
|
virtual bool get_srt_enabled(std::string vhost) { return srt_enabled_; }
|
||||||
virtual std::string get_srt_default_streamid() { return "#!::r=live/livestream,m=request"; }
|
virtual std::string get_srt_default_streamid() { return "#!::r=live/livestream,m=request"; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user