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等于没有给请求附加身份凭证。
可落地的修复步骤
- 配置OAuth2授权凭据
- 打开Google Cloud控制台,进入「API和服务」-「OAuth同意屏幕」,个人使用选择「外部」用户类型,填写必要的应用名称、联系方式信息,在「测试用户」列表里添加你要上传视频的目标Google账号邮箱。
- 进入「凭据」页面,创建「OAuth 2.0 客户端ID」,应用类型选择「桌面应用」,创建完成后下载对应的JSON凭据文件,存到项目资源目录下。
- 修正依赖配置
删掉你之前引入的旧版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>
- 修正代码逻辑
- 把代码里的JacksonFactory导入替换为
com.google.api.client.json.jackson2.JacksonFactory,实例化方式改回JacksonFactory.getDefaultInstance(),不要手动new实例。 - 补全OAuth2授权逻辑,加载本地的客户端凭据完成账号授权,构建YouTube服务时传入授权得到的Credential对象,不要仅设置API密钥。
核心修正代码参考:
- 把代码里的JacksonFactory导入替换为
// 补充缺失的导入 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); } }
- 运行验证
第一次运行代码时会自动弹出浏览器,跳转到Google登录页面,你登录之前添加到测试用户列表的账号,给应用授予YouTube上传权限即可,授权完成后令牌会存在本地配置目录,后续运行不需要重复授权。
补充说明
- 如果需要无人工交互的服务端自动上传场景,普通个人YouTube频道不支持直接用服务账号授权,需要提前获取账号的长期refresh_token,每次调用时用refresh_token生成有效的access_token再发起请求。
- 上传大文件时可以使用接口的断点续传能力,避免网络波动导致整个上传任务失败。
内容的提问来源于stack exchange,提问作者Phi Tiet
相关产品推荐
相关产品推荐

