如何使用 Java 的 Google Directory API 更改用户密码?

Ste*_*ole 3 java google-directory-api google-oauth-java-client

我要提前道歉,因为我可能会丢失一些信息,以便让任何人知道我的设置是正确的。请让我知道我还需要指定什么。我在一所大学工作,我们通过 Google 托管学生电子邮件帐户。他们经常忘记密码并不得不重置密码。如果他们验证了足够的有关自己的信息,我们有一个页面可以设置他们的密码。Google 已弃用我们一直使用的代码,转而使用他们的 Directory API。我的任务是转换这个旧代码:

//changes a password
@Override
public void reset ( final String username, final String password ) throws ResetException
{
    try
    {
        Validate.notEmpty( username );
        Validate.notEmpty( password );

        final AppsForYourDomainClient client = ClientFactory.getClient();  //admin-user account
        final UserEntry entry = client.retrieveUser( username );
        Validate.isTrue( !entry.getLogin().getSuspended(), "Account is suspended." );
        entry.getLogin().setPassword( password );
        client.updateUser( username, entry );
    }
    catch ( final Exception e )
    {
        throw new ResetException( e );
    }
}
Run Code Online (Sandbox Code Playgroud)

使用他们的新 API 进行一些操作。我读过很多文档和几个示例,但似乎没有一个有帮助。我已通过他们的管理控制台为我们的管理员帐户启用了 Admin SDK,并注册了我的应用程序并从他们的开发人员控制台获取了密钥,但我似乎无法收到任何返回我想要的内容的请求。现在我只是想获取用户列表:

public void testList () throws Exception
{
    InputStream is = null;
    final String accessToken = getAccessToken();
    final NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
    final File p12 = new File( GoogleResetterTest.class.getClassLoader().getResource("ead05e56893a.p12").toURI() );
    GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
                                        .setJsonFactory(jsonFactory)
                                        .setServiceAccountId( SERVICE_ACCOUNT_EMAIL )
                                        .setServiceAccountScopes( PlusScopes.all() )
                                        .setServiceAccountPrivateKeyFromP12File( p12 )  //password: notasecret
                                        .setClientSecrets(CLIENT_ID, CLIENT_SECRET)
                                        .build();
    final Directory dir = new Directory.Builder( httpTransport, jsonFactory, credential)
                                                .setApplicationName( "API Project" )
                                                .build();
    final Directory.Users diruser = dir.users();
    final Directory.Users.List diruserlist = diruser.list().setDomain( EMAIL_DOMAIN );
    final HttpResponse response = diruserlist.executeUsingHead();
    is = response.getContent();
    StringWriter writer = new StringWriter();
    IOUtils.copy(is, writer, "UTF-8");
    String theString = writer.toString();
    IOUtils.closeQuietly( is );
}
Run Code Online (Sandbox Code Playgroud)

在 diruserlist.executeUsingHead() 上;我得到这样的回复:

com.google.api.client.auth.oauth2.TokenResponseException:400错误请求{“错误”:“invalid_grant”}

对我来说,这是一个非常无用的错误消息,因为似乎有 4 或 5 个部分可能出错而导致该响应。

我不禁觉得我把这整件事搞得太复杂了。我喜欢原始代码的简单性,但对新 API 的一些回应批评它更复杂。有没有人必须这样做并且可以指出我正确的路径来解决这个问题?

Ste*_*ole 5

我将回答我自己的问题并将其标记为已解决,而不是删除问题或编辑它。

这里有一些事情在起作用,有些事情我也搞错了。主要是因为有关构建器的文档很差或不存在。这是我必须做的:

在管理控制台( https://admin.google.com/ )上设置

  1. 确保我尝试使用的帐户具有该域的超级管理员访问权限(管理员角色 -> 超级管理员)
  2. 在安全选项卡中启用 API 访问。(安全 -> API 参考 -> API 访问 -> 启用 API 访问
  3. 在开发者控制台上进行设置(如下)
  4. 添加对我的应用程序的访问权限(安全 -> 高级设置 -> 身份验证 -> 管理 OAuth 客户端访问
  5. 从开发者控制台向我的客户授予这些范围(不带方括号):
    • [https]://www.googleapis.com/auth/admin.directory.group
    • [https]://www.googleapis.com/auth/admin.directory.user
    • [https]://www.googleapis.com/auth/admin.directory.user.readonly
    • [https]://www.googleapis.com/auth/admin.directory.user.security

并非所有这些范围都是必要的,但这是我启用的

在开发者控制台( https://console.developers.google.com/ )上设置

  1. 创建项目
  2. 为项目启用 Admin SDK(API 和身份验证 -> API)
  3. 创建 OAuth 客户端作为服务帐户(API 和身份验证 -> 凭据 -> 创建新的客户端 ID)
  4. 将该客户端 ID 返回到管理控制台(上面的步骤 5)
  5. 下载 p12 密钥,我的 java 应用程序可以读取它。(API 和身份验证 -> 凭据 -> 生成新的 P12 密钥)

现在我终于可以写一些java了!

import java.io.*;
import java.net.*;
import java.util.*;

import java.security.GeneralSecurityException;
import java.security.MessageDigest;

import javax.xml.bind.DatatypeConverter;

import com.google.api.services.admin.directory.*;
import com.google.api.services.admin.directory.model.*;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

public class PasswordResetter
{
    public void changePassword( final String username, final String newPassword ) throws Exception
    {
        final Directory dir = getDirectory();
        final User studentUser = updatePassword( getUser( dir, DOMAIN_NAME, username ), password );
        updateDirectory( dir, studentUser );
    }

    private Directory getDirectory() throws IOException, GeneralSecurityException, URISyntaxException
    {
        final NetHttpTransport httpTransport = new NetHttpTransport();
        final JacksonFactory jsonFactory = new JacksonFactory();
        final File p12 = new File( P12_FILENAME );
        final GoogleCredential credential = new GoogleCredential.Builder()
                                                                    .setTransport(httpTransport)
                                                                    .setJsonFactory(jsonFactory)
                                                                    .setServiceAccountUser( SUPER_USER_EMAIL )
                                                                    .setServiceAccountId( SERVICE_ACCOUNT_EMAIL_FROM_DEV_CONSOLE ) //the one that ends in "@developer.gserviceaccount.com"
                                                                    .setServiceAccountScopes( getCredentials() )
                                                                    .setServiceAccountPrivateKeyFromP12File( p12 )
                                                                    .build();
        return new Directory.Builder( httpTransport, jsonFactory, null)
                                    .setHttpRequestInitializer( credential )
                                    .setApplicationName( "API Project" )    //Not necessary, but silences a runtime warning using any not-blank string here
                                    .build();
    }

    private List<String> getCredentials()
    {
        final List<String> toReturn = new LinkedList<String>();
        toReturn.add( DirectoryScopes.ADMIN_DIRECTORY_GROUP );
        toReturn.add( DirectoryScopes.ADMIN_DIRECTORY_USER );
        toReturn.add( DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY );
        toReturn.add( DirectoryScopes.ADMIN_DIRECTORY_USER_SECURITY );
        return toReturn;
    }

    private Users getUser( final Directory dir, final String domain, final String username ) throws Exception
    {
        Directory.Users.List diruserlist = dir.users().list()
                                                        .setDomain( domain )
                                                        .setQuery( "email:" + username + "*" );
        return diruserlist.execute();
    }

    private User updatePassword( final User user, final String password ) throws Exception
    {
        final MessageDigest md = MessageDigest.getInstance( "MD5" );    //I've been warned that this is not thread-safe
        final byte[] digested = md.digest( password.getBytes( "UTF-8" ) );
        final String newHashword = DatatypeConverter.printHexBinary( digested );
        return user.setHashFunction("MD5")                              //only accepts MD5, SHA-1, or CRYPT
                    .setPassword( newHashword );
    }

    private void updateDirectory( final Directory dir, final User user ) throws IOException
    {
        final Directory.Users.Update updateRequest = dir.users().update( user.getPrimaryEmail(), user );
        updateRequest.execute();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,我的超级用户帐户有一个很好的方法,只需提供一个用户名(定义他们的电子邮件,从而进行查询)和一个新密码,它就会为他们更改它。请注意,这不是完整的类,并且不能仅通过复制粘贴进行编译。

我当然希望我没有跳过任何一步。我的控制台中有一些额外的设置,我担心它们会损坏某些东西,因此我担心它们会被删除。希望这能帮助其他人在未来。