【问题标题】:Exception in thread "main" com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized when trying to access Google classroom API线程“主”com.google.api.client.auth.oauth2.TokenResponseException 中的异常:尝试访问 Google 课堂 API 时出现 401 未授权
【发布时间】:2020-03-29 04:07:33
【问题描述】:

我已阅读与该问题相关的所有堆栈溢出问题。我对此有所了解,但它无法帮助我解决这个问题。

我已使用服务帐户进行授权。因为我不想为我的用户显示授权屏幕。因此,我在 GoogleAPI 控制台中创建了服务帐户并启用了域范围的授权。

我需要以编程方式在用户的 Google 课堂中创建课程。我已经按照 java Quickstart 使用了 OAuth 客户端 ID 和 OAuth 客户端密码。完成后授权令牌已经存储在项目目录中。使用这个令牌,我可以在授权用户的谷歌教室中创建课程。因为我需要为我域中的许多用户在谷歌课堂上创建课程。所以我使用了服务帐户。当我尝试实现它时,我得到了上述错误。

我已经在 Web 应用程序中实现了它。为了更好地理解和代码可读性,我创建了一个快速入门 ma​​ven 项目并粘贴了代码。代码运行良好,直到 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();
        }
    }

【问题讨论】:

  • 您是否从服务帐户创建了key?令牌在那里。
  • 是的。我已经下载了 .p12 密钥。你可以在我的代码中看到它。即私有静态最终字符串SERVICE_ACCOUNT_PKCS12_FILE_PATH = "/MyProject.p12";
  • @Jescanellas 我从 .p12 文件中获取了私钥并将其传递到凭据中(setServiceAccountPrivateKey)。我已经提到了这个答案stackoverflow.com/questions/18621508/…
  • 我明白了。不要使用该帖子,因为它是 2013 年的,并且已经过时了。尝试从同一 Quickstart 复制第 3 步授权。
  • @Jescanellas 我可以使用该快速入门创建课程。他们使用 OAuth 客户端 ID。但我的问题不同。我想使用服务帐户,因为它可以使用机器人帐户启用服务器到服务器、应用级别的身份验证。

标签: java google-api google-oauth google-admin-sdk google-classroom


【解决方案1】:

正如 cmets 中所述,您必须将用户凭据替换为您的 Service Account 中的凭据。在此示例中,我使用服务帐户中的凭据修改了凭据的 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。

由于您已经创建了服务帐户,我将跳转到域范围的委派授权:

  1. 转到您的GCP 并为您的项目选择服务帐户。
  2. 点击左边三个垂直点然后编辑
  3. 在服务帐户详细信息中,点击显示域范围委派,然后确保选中启用 G Suite 域范围委派复选框。
  4. 单击保存以更新服务帐户,并返回服务帐户表。可以看到一个新列,域范围的委派。点击查看客户 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 .

您的课堂服务生成器和新课程代码正确无误,应该可以使用新凭据。

【讨论】:

  • 感谢您的回答。你能解开我的两个疑惑吗? 1)我需要在我用户的谷歌教室中创建课程/班级。我可以在哪里指定他们的电子邮件 ID/所有者 ID? 2)当我执行此代码时,我得到“系统无法精细指定路径”。我已经为路径(projectname/src/main/resources)提供了限定名称。如何解决?
  • 1) 您是从 API 还是从 Web 界面创建课程? 2) 关于路径位置,您必须使用完整路径,因为它使用的是 FileInputStream。例如:/usr/local/bin/home/userName/myProjects/java/IntellijProjects/src/main/resources/service_creds.json
  • 1) 是的,我将使用网络界面创建课程。
  • 2) 感谢您的大力帮助。现在它显示“未找到课程”。您能告诉我如何在指定用户的 google 课堂帐户中创建课程吗?
  • 当然!看看这个guide 显示的步骤。
【解决方案2】:

我已经通过更改范围解决了这个异常。即,在我的代码中,我使用了三个范围。这三个范围应该与 Gsuite 的范围相匹配。在delegating domain-wide authority to the service account 时,我们添加了范围。这两个范围都应该匹配,以避免 401 未授权。

【讨论】:

    猜你喜欢
    • 2016-12-14
    • 1970-01-01
    • 2017-03-16
    • 1970-01-01
    • 2017-10-30
    • 2017-04-14
    • 1970-01-01
    • 2011-09-15
    • 2020-11-16
    相关资源
    最近更新 更多