You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java调用YouTube Data API v3 Video.insert上传视频报401 Unauthorized

问题核心原因

你遇到401 Unauthorized错误的根本原因是鉴权方式完全选错了。
YouTube Data API v3对接口鉴权有明确的权限划分:

  • 公开只读类接口(比如搜索公开视频、查询频道公开信息、获取公开视频评论)可以使用API密钥鉴权
  • 所有涉及用户私有数据访问、内容写入的接口(包括你调用的videos.insert视频上传接口、修改视频信息、删除内容、管理评论等),必须使用OAuth 2.0流程获取对应频道账号的授权令牌,仅传API密钥会被直接判定为无权限,固定返回401。
    你当前的实现里只传了API密钥,请求头里没有携带合法的用户授权Bearer令牌,从你贴的请求URL也能看出来,只有key=xxx参数,没有任何用户身份凭证,必然鉴权失败。
现有代码的其他问题

除了核心鉴权方式错误,你的代码还有几个会引发后续报错的问题:

  • 依赖配置错误:你替换JacksonFactory的方案不对,当前最新版的google-http-client-jackson2依赖正常可用,你引入的1.29.2版本旧jackson依赖和1.3x版本的Google API客户端存在版本兼容问题,容易引发请求构造、序列化异常。
  • part参数传值错误:List.of("snippet,status")写法不符合接口要求,该接口要求每个part作为独立的列表元素传入,正确写法是List.of("snippet", "status"),就算鉴权通过,当前写法也会触发参数错误。
  • 服务初始化逻辑缺失:你构建YouTube服务实例时第三个参数传了null,这个参数是HTTP请求初始化器,OAuth模式下需要通过它给每个请求注入授权头,传null等于没有给请求附加身份凭证。
可落地的修复步骤
  1. 配置OAuth2授权凭据
    • 打开Google Cloud控制台,进入「API和服务」-「OAuth同意屏幕」,个人使用选择「外部」用户类型,填写必要的应用名称、联系方式信息,在「测试用户」列表里添加你要上传视频的目标Google账号邮箱。
    • 进入「凭据」页面,创建「OAuth 2.0 客户端ID」,应用类型选择「桌面应用」,创建完成后下载对应的JSON凭据文件,存到项目资源目录下。
  2. 修正依赖配置
    删掉你之前引入的旧版jackson依赖:
<!-- 移除这段错误依赖 -->
<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client-jackson</artifactId>
    <version>1.29.2</version>
</dependency>

替换为官方当前维护的jackson2依赖:

<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client-jackson2</artifactId>
    <version>1.42.3</version>
</dependency>
  1. 修正代码逻辑
    • 把代码里的JacksonFactory导入替换为com.google.api.client.json.jackson2.JacksonFactory,实例化方式改回JacksonFactory.getDefaultInstance(),不要手动new实例。
    • 补全OAuth2授权逻辑,加载本地的客户端凭据完成账号授权,构建YouTube服务时传入授权得到的Credential对象,不要仅设置API密钥。
      核心修正代码参考:
// 补充缺失的导入
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.util.store.FileDataStoreFactory;
import java.io.InputStreamReader;
import java.util.Collections;

public class YoutubeApiServiceImpl implements YoutubeApiService {
    private static final String APPLICATION_NAME = "YSG";
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    // 上传视频需要的权限范围
    private static final List<String> SCOPES = Collections.singletonList("https://www.googleapis.com/auth/youtube.upload");
    // 你下载的OAuth凭据文件在资源目录下的路径
    private static final String CREDENTIALS_FILE_PATH = "/client_secret.json";
    // 本地存储授权令牌的目录,授权一次后后续不需要重复登录
    private static final File DATA_STORE_DIR = new File(System.getProperty("user.home"), ".credentials/youtube-upload");

    // 加载凭据完成账号授权
    public static Credential getCredentials(final NetHttpTransport httpTransport) throws IOException {
        InputStream in = YoutubeApiServiceImpl.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
                .setDataStoreFactory(new FileDataStoreFactory(DATA_STORE_DIR))
                .setAccessType("offline")
                .build();
        LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
        return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    }

    public static YouTube getService() throws GeneralSecurityException, IOException {
        final NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        Credential credential = getCredentials(httpTransport);
        // 传入授权得到的credential,不要传null
        return new YouTube.Builder(httpTransport, JSON_FACTORY, credential)
                .setApplicationName(APPLICATION_NAME)
                .build();
    }

    public static void main(String[] args) throws GeneralSecurityException, IOException, GoogleJsonResponseException {
        YouTube youtubeService = getService();
        
        Video video = new Video();
        VideoSnippet snippet = new VideoSnippet();
        snippet.setCategoryId("22");
        snippet.setDescription("Description of uploaded video.");
        snippet.setTitle("Test video upload.");
        video.setSnippet(snippet);

        VideoStatus status = new VideoStatus();
        status.setPrivacyStatus("private");
        video.setStatus(status);

        File mediaFile = new ClassPathResource("test/test.mp4").getFile();
        // 媒体类型改为video/*,不要用application/octet-stream
        InputStreamContent mediaContent = new InputStreamContent("video/*", new BufferedInputStream(new FileInputStream(mediaFile)));
        mediaContent.setLength(mediaFile.length());

        // 修正part参数,每个part单独作为列表元素,删掉setKey调用
        YouTube.Videos.Insert request = youtubeService.videos().insert(List.of("snippet", "status"), video, mediaContent);
        Video response = request.execute();
        System.out.println(response);
    }
}
  1. 运行验证
    第一次运行代码时会自动弹出浏览器,跳转到Google登录页面,你登录之前添加到测试用户列表的账号,给应用授予YouTube上传权限即可,授权完成后令牌会存在本地配置目录,后续运行不需要重复授权。
补充说明
  • 如果需要无人工交互的服务端自动上传场景,普通个人YouTube频道不支持直接用服务账号授权,需要提前获取账号的长期refresh_token,每次调用时用refresh_token生成有效的access_token再发起请求。
  • 上传大文件时可以使用接口的断点续传能力,避免网络波动导致整个上传任务失败。

内容的提问来源于stack exchange,提问作者Phi Tiet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 13:54:04