cd ..
2025-12-1612 min39 views

[实战] 从零打造防盗链视频平台:Spring Boot + Flutter + 阿里云OSS 全链路安全方案

#Aliyun OSS#Video Security#Hls#Spring Boot#Flutter

在构建付费课程或私有视频平台时,开发者最头疼的问题往往不是“怎么播放”,而是**“怎么防止被白嫖”**。

如果不做防护,只需一个简单的抓包工具(如 Charles),甚至直接查看浏览器 Network,攻击者就能拿到 .mp4 地址,脚本一跑,全站资源瞬间被爬空。

本文将基于 Spring Boot (后端) + Flutter (移动端) + 阿里云 OSS (存储) 的技术栈,分享一套工业级的视频防盗、防抓包、防录屏方案。


核心思路:把视频“切碎”并“上锁”

传统的 .mp4 直链播放就像把钱直接放在桌子上。我们需要改变存储和分发方式:

  1. 存储层:抛弃 MP4,采用 HLS (m3u8) 切片技术。
  2. 加密层:使用 AES-128 对每一个视频切片进行加密。
  3. 分发层:阿里云 OSS 开启私有权限,配合 STS 临时签名。
  4. 鉴权层:解密密钥(Key)由后端动态分发,验证用户身份(Token)。
  5. 客户端:Flutter 端硬件级防录屏 + 动态盲水印。

架构全览


第一步:内容生产(转码与加密)

视频上传后,不能直接存储,必须经过 FFmpeg 处理。

我们需要准备一个 key_info 文件,告诉 FFmpeg 加密用的密钥在哪里,以及未来播放器该去哪里找这个密钥。

key_info.txt 内容示例:

http://api.your-domain.com/video/key?id=VIDEO_ID  <-- 播放器会向这个地址请求解密Key
/path/to/enc.key                                  <-- 实际加密用的二进制文件路径

FFmpeg 转码指令:

ffmpeg -y -i input.mp4 \
  -c:v libx264 -c:a aac \
  -hls_time 10 \
  -hls_key_info_file key_info.txt \
  -hls_playlist_type vod \
  -hls_segment_filename "output_%03d.ts" \
  playlist.m3u8

关键点:

  • 生成后的 .m3u8 文件内部会包含一行 #EXT-X-KEY:METHOD=AES-128,URI="..."
  • 这个 URI 必须指向你的 Spring Boot 后端,而不是 OSS。这是防抓包的核心——没有业务 Token,谁也拿不到解密钥匙。

第二步:云存储配置(阿里云 OSS)

即便视频被加密了,我们也不希望闲杂人等随意下载切片浪费流量。

  1. Bucket 权限:设置为 私有(Private)
  2. STS 临时授权
    Flutter 客户端不应该持有 OSS 的永久 AccessKey。后端应集成阿里云 STS SDK。
    当用户请求播放时,后端签发一个有效期仅 1 小时的 URL 给前端。即使这个 URL 被抓包,过一会儿也就失效了。

第三步:后端门神(Spring Boot 密钥分发)

这是整个安全体系的咽喉。当播放器解析到 .m3u8 中的 Key URI 时,会发起请求。我们需要在这里拦截。

Controller 实现逻辑:

@RestController
@RequestMapping("/video")
public class VideoKeyController {

    @GetMapping("/key")
    public void getKey(@RequestParam("id") String videoId, 
                       @RequestHeader(value = "Authorization", required = false) String token,
                       HttpServletResponse response) throws IOException {
        
        // 1. 严格鉴权
        if (!authService.isValidUser(token) || !authService.hasPermission(token, videoId)) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return;
        }

        // 2. (可选) 一次性令牌校验
        // 为了防止 Token 泄露,可以在 m3u8 url 中拼接入一次性 ticket,用完即焚

        // 3. 获取密钥 (通常存储在数据库或 Redis,或者 OSS 的受保护目录)
        byte[] keyBytes = encryptionService.getSecretKey(videoId);

        // 4. 返回二进制流
        response.setContentType("application/octet-stream");
        response.getOutputStream().write(keyBytes);
    }
}

第四步:客户端实现(Flutter)

Flutter 端面临两个挑战:Header 注入防录屏

1. 播放器选型与 Header 注入

原生 video_player 比较简陋,难以定制 Header。推荐使用 Better PlayerFijkPlayer (基于 ijkplayer)。

我们需要在请求 m3u8 时,把用户的 Token 塞入 Header,以便后端在请求 Key 时能获取到。

// 使用 Better Player 示例
var dataSource = BetterPlayerDataSource(
  BetterPlayerDataSourceType.network,
  "https://oss.your-domain.com/video/playlist.m3u8?Signature=...", // 阿里云 STS 签名后的 URL
  headers: {
    "Authorization": "Bearer $userToken", // 关键:注入 Token
    "User-Agent": "MySecureApp/1.0"
  },
);

var controller = BetterPlayerController(configuration);
controller.setupDataSource(dataSource);

2. 禁止录屏与截屏

这是防止“物理盗版”的第一道防线。Android 和 iOS 都提供了系统级 API 来阻止录屏(录制结果为黑屏)。

使用 flutter_windowmanager 插件:

import 'package:flutter_windowmanager/flutter_windowmanager.dart';

class VideoPage extends StatefulWidget {
  @override
  _VideoPageState createState() => _VideoPageState();
}

class _VideoPageState extends State<VideoPage> {
  @override
  void initState() {
    super.initState();
    // 开启“安全模式”
    secureScreen();
  }

  Future<void> secureScreen() async {
    await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
  }

  @override
  void dispose() {
    // 页面销毁时清除 flag (可选)
    FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
    super.dispose();
  }
}

第五步:终极手段(动态盲水印)

如果用户用另一台手机对着屏幕拍,技术手段是无法阻断的。但我们可以增加他的犯罪成本

在视频层之上,覆盖一层半透明的、肉眼几乎不可见的水印,内容是当前用户的 ID。一旦视频泄露,可以通过技术手段提取水印,封禁该账号。

Flutter 实现思路:

Stack(
  children: [
    VideoPlayerWidget(), // 视频层
    IgnorePointer(     // 穿透层,确保不影响点击视频控制栏
      child: Container(
        color: Colors.transparent,
        child: GridView.count(
          crossAxisCount: 3,
          children: List.generate(20, (index) {
            return Center(
              child: Transform.rotate(
                angle: -0.5,
                child: Text(
                  "UID: 8848",
                  style: TextStyle(
                    color: Colors.white.withOpacity(0.05), // 极低透明度
                    fontSize: 16,
                  ),
                ),
              ),
            );
          }),
        ),
      ),
    ),
  ],
)

总结

通过这一套组合拳,我们构建了多重防御体系:

  1. 文件层面:TS 切片 + AES 加密,下载下来也看不了。
  2. 网络层面:OSS 私有化 + STS 签名,链接过期即失效。
  3. 鉴权层面:解密 Key 需 Token 换取,后端严格控制权限。
  4. 物理层面:App 禁止录屏,录制即黑屏。
  5. 溯源层面:动态水印,泄露必被抓。

虽然“绝对的安全”不存在,但这一套方案足以将 99% 的脚本小子和普通盗录者拒之门外,是目前性价比最高的商业级视频保护方案。

/** Comments(0)*/

Loading comments...
[实战] 从零打造防盗链视频平台:Spring Boot + Flutter + 阿里云OSS 全链路安全方案 | UbanillxのDevLog