线程 "main" com.google.api.client.auth.oauth2.TokenResponseException 中的异常:尝试访问 Google 教室时 401 未经授权 API
Exception in thread "main" com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized when trying to access Google classroom API
我已经阅读了所有与该问题相关的堆栈溢出问题。我对此有一些想法,但它无法帮助我解决这个问题。
我已使用服务帐户进行授权。因为我不想为我的用户显示授权屏幕。因此,我在 GoogleAPI 控制台中创建了服务帐户并启用了全域授权。
我需要以编程方式在用户的 google 教室中创建课程。我已经按照 java Quickstart 使用 OAuth 客户端 ID 和 OAuth 客户端密码。完成后授权令牌已经存放在项目目录下。使用此令牌,我可以在授权用户的 google 教室中创建课程。因为我需要在 google 教室中为我域中的许多用户创建课程。所以我使用了服务帐户。当我尝试实现它时,出现了上述错误。
我已经在网络应用程序中实现了它。为了更好地理解和代码可读性,我创建了一个 quickstart maven 项目并粘贴了代码。该代码在 System.out.println("Entering to list course method: "); 行之前工作正常。
1) 如何解决这个问题?
2) 会要求管理员授权吗?因为为了访问用户数据,我们需要访问令牌。如何使用服务帐户获取访问令牌?
public class ClassroomServiceAccount {
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final String SERVICE_ACCOUNT_EMAIL = "xxxxxxx@projectName.iam.gserviceaccount.com";
private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH = "/MyProject.p12";
private static final List<String> SCOPES = Arrays.asList(ClassroomScopes.CLASSROOM_COURSES, ClassroomScopes.CLASSROOM_TOPICS, ClassroomScopes.CLASSROOM_ANNOUNCEMENTS);
public static void main(String... args) throws IOException, GeneralSecurityException {
HttpTransport httpTransport = new NetHttpTransport();
InputStream in = ClassroomServiceAccount.class.getResourceAsStream(SERVICE_ACCOUNT_PKCS12_FILE_PATH);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(in, "notasecret".toCharArray());
String alias = "key1";
Key key = keystore.getKey(alias, "notasecret".toCharArray());
// Get certificate of public key
Certificate cert = keystore.getCertificate(alias);
// Get public key
PublicKey publicKey = cert.getPublicKey();
System.out.println("This is public key : "+ publicKey);
// Return a key pair
KeyPair k = new KeyPair(publicKey, (PrivateKey) key);
System.out.println("This is k : "+ k.getPrivate());
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountPrivateKey(k.getPrivate())
.setServiceAccountScopes(SCOPES)
.setServiceAccountUser("email@yourdomain.com")
.build();
System.out.println("credentials : " + credential.getServiceAccountPrivateKey());
Classroom service = new Classroom.Builder(httpTransport, JSON_FACTORY, null)
.setApplicationName(APPLICATION_NAME)
.setHttpRequestInitializer(credential)
.build();
// Create courses in users google classroom
Course course = new Course();
course.setOwnerId("user@yourdomain.com");
course.setName("Science");
service.courses().create(course).execute();
}
}
如评论中所述,您必须将 Service Account. In this example I modified the credential's Classroom Quickstart 示例中的用户凭据替换为服务帐户中的用户凭据。如您所见,我完全删除了 getCredentials
函数。但是,无论如何您都可以将凭据生成器放在那里:
public class ClassroomQuickstart {
private static final String APPLICATION_NAME = "Google Classroom API Java Quickstart";
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final String TOKENS_DIRECTORY_PATH = "tokens";
private static final String KEY_FILE_LOCATION = "full path of your service account creds json";
/**
* Global instance of the scopes required by this quickstart.
* If modifying these scopes, delete your previously saved tokens/ folder.
*/
private static final List<String> SCOPES = Collections.singletonList(ClassroomScopes.CLASSROOM_COURSES_READONLY);
/**
* Creates an authorized Credential object.
* @param HTTP_TRANSPORT The network HTTP Transport.
* @return An authorized Credential object.
* @throws IOException If the credentials.json file cannot be found.
*/
public static void main(String... args) throws IOException, GeneralSecurityException {
GoogleCredential credential = GoogleCredential
.fromStream(new FileInputStream(KEY_FILE_LOCATION))
.createScoped(ClassroomScopes.all());
// Build a new authorized API client service.
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
Classroom service = new Classroom.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
// List the first 10 courses that the user has access to.
ListCoursesResponse response = service.courses().list()
.setPageSize(10)
.execute();
List<Course> courses = response.getCourses();
if (courses == null || courses.size() == 0) {
System.out.println("No courses found.");
} else {
System.out.println("Courses:");
for (Course course : courses) {
System.out.printf("%s\n", course.getName());
}
}
}
}
更新:域名授权
要使用服务帐户在您的 Google 帐户中创建课程,您需要模拟它。为此,您必须执行所谓的 Domain-Wide Delegation.
由于您已经创建了服务帐户,我将跳转到全域委托授权:
- 转到您的 GCP 和 select 项目的服务帐户。
- 点击左边的三个竖点然后编辑
- 在服务帐户详细信息中,点击显示全域委派,然后确保勾选启用G Suite全域委派复选框检查。
- 单击“保存”更新服务帐户,return到table个服务帐户。可以看到一个新的列,Domain-wide delegation。单击查看 客户端 ID,获取并记下客户端 ID。
现在您必须授权您的用户,以便您的服务帐户可以模拟它。我没有写这些步骤,因为它太长了,但是你可以看到它们 here.
回到代码,我们必须选择凭据的设置方式。为了让事情更简单,我现在使用 P12 密钥文件而不是 JSON 凭据:
GoogleCredential credential2 = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("example@project.iam.gserviceaccount.com")
.setServiceAccountScopes(SCOPES)
.setServiceAccountUser("my_email@example.com")
.setServiceAccountPrivateKeyFromP12File(new File(KEY_FILE_LOCATION)).build();
域范围快速入门的其余部分here .
您的课堂服务构建器和新课程代码是正确的,应该可以使用新凭据。
我已经通过更改范围解决了这个异常。即,在我的代码中,我使用了三个范围。这三个范围应该与 Gsuite 的范围相匹配。 delegating domain-wide authority to the service account,我们添加了作用域。这两个范围都应该匹配以避免 401 unauthorized。