3

我要提前道歉,我可能遗漏了一些信息,让任何人都知道我的设置是正确的。请让我知道我还需要说明什么。我在一所大学工作,我们通过 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 );
    }
}

使用他们的新 API。我已经阅读了很多文档和几个示例,但似乎都没有帮助。我已经通过他们的 Admin Console 为我们的管理员帐户启用了 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 );
}

在 diruserlist.executeUsingHead(); 我得到这个回复:

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

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

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

4

1 回答 1

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 (APIs & auth -> APIs)
  3. 创建一个 OAuth 客户端作为服务帐户(APIs & auth -> Credentials -> Create new Client ID)
  4. 将该客户端 ID 带回管理控制台(上面的第 5 步)
  5. 下载我的 java 应用程序可以读取的 p12 密钥。(APIs & auth -> Credentials -> Generate new P12 key)

现在我终于可以写一些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();
    }
}

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

我当然希望我没有跳过一步。我的控制台中有一些额外的设置,我害怕摆脱它以防它破坏某些东西。希望这将有助于将来的其他人。

于 2014-09-09T20:11:00.380 回答