two-legged oauth2:如何在没有特定 API 库的情况下调用 google drive rest API
two-legged oauth2 : how to call google drive rest API without specific API library
我在 Google 开发人员控制台中创建了一个应用程序,然后创建了 OAuth2 凭据。我有一个 client_id 和 client_secret。现在,我想使用这些来获取 two-legged 调用 Google 驱动器 API 的访问令牌。我在 java:
中使用 Google 的 oauth2 客户端
import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.TokenResponse;
...
public void oauth2Test() {
String clientId = "...";
String clientSecret = "...";
ClientCredentialsTokenRequest request = new ClientCredentialsTokenRequest(
new NetHttpTransport(),
new JacksonFactory(),
new GenericUrl("https://accounts.google.com/o/oauth2/auth"));
request.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret));
TokenResponse response;
try {
response = request.execute();
} catch (Exception ex) {
ex.printStackTrace();
}
}
但是,我收到消息“400 错误请求”
"Required parameter is missing: response_type".
在two-legged请求模型中获取访问令牌的正确方法是什么?注意:我只有 client_id 和 client_secret,我没有完整的 API 令牌.
编辑:我最初的问题不准确。虽然我更喜欢只从 client_id 和 client_secret 开始,但这不是必需的。可以使用google-specific APIs获取访问令牌,使用GoogleCredential也可以。 必要的 是我能够使用从通用 REST 调用中的授权过程中获得的任何访问令牌。换句话说,给定 google 应用凭据,可以是 {client_id,client_secret},或 JSON 或 P12 格式的 google 服务帐户密钥,我如何获取访问令牌以及它们如何在 REST API 调用中使用——我是设置授权 header 还是其他?
第一个回答指出不支持client_credential,我已经验证过了。但是我仍然需要一个路径来获取不记名令牌,这样我就可以在没有特定 Google 客户端 API 库的 REST 调用中使用它。所以我从有效的代码开始,但使用了 Google 库。它需要一个 JSON 凭证文件。
InputStream is = getClass().getResourceAsStream("JSONCredFile");
GoogleCredential credential = GoogleCredential.fromStream(is).createScoped(scopes);
Drive service = new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName("My app")
.build();
FileList result = service.files().list().setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
通过将 SSLSocket 代理连接到凭据(详细信息已省略),我能够跟踪出站通信:
POST /token HTTP/1.1
Accept-Encoding: gzip
User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: oauth2.googleapis.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 771
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=<lots of encoded stuff>
回复是 gzip-encoded 不记名令牌,用于 API 调用:
GET /drive/v3/files?fields=nextPageToken,%20files(id,%20name)&pageSize=10 HTTP/1.1
Accept-Encoding: gzip
Authorization: Bearer ya29.c.Eln_BSgrx0afa85mdMstW5jzEvM5dotWpctSXl-DE1jeO2mmu1h0FErr_EZO05YnC-B1yz30IBwOyFXoWr_wwKxlZk08R6eZldNU-EAfrQ1yNftymn_Qqc_pfg
很明显这是JWT profile of oauth2。但现在呢?我需要以某种方式获取不记名令牌,而无需通过特定库实际进行 API 调用。 Google OAuth2 库似乎不支持这种请求类型,至少我没有看到 TokenRequest 的“JWT”风格。我可以直接编写 OAuth2 调用,还是创建一个支持 JWT 的 TokenRequest 子类?
有更好的主意吗?
Google 不支持 grant_type=client_credentials 这就是您使用 OAuth 客户端 ID 和密码执行 2LO 的方式。
您可以使用服务帐户进行 2LO:https://developers.google.com/identity/protocols/OAuth2ServiceAccount
好的,我终于弄明白了如何制作 JWT、发送 OAuth2 请求并提取访问令牌。为了更容易与 google OAuth2 客户端集成,我将 TokenRequest 子类化:
import com.google.api.client.auth.oauth2.TokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collection;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
/**
* @author jlilley
*/
public class JWTTokenRequest extends TokenRequest {
private String serviceKeyJson;
private boolean doRsa = true;
public JWTTokenRequest(HttpTransport transport, JsonFactory jsonFactory, GenericUrl tokenServerUrl) {
super(transport, jsonFactory, tokenServerUrl, "urn:ietf:params:oauth:grant-type:jwt-bearer");
}
@Override
public JWTTokenRequest setTokenServerUrl(GenericUrl tokenServerUrl) {
return (JWTTokenRequest)super.setTokenServerUrl(tokenServerUrl);
}
public JWTTokenRequest setServiceKey(String json) throws Exception {
this.serviceKeyJson = json;
return this;
}
public JWTTokenRequest setServiceKey(InputStream is) throws Exception {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(is))) {
return setServiceKey(buffer.lines().collect(Collectors.joining("\n")));
}
}
@Override
public JWTTokenRequest setScopes(Collection<String> scopes) {
return (JWTTokenRequest) super.setScopes(scopes);
}
@Override
public JWTTokenRequest set(String fieldName, Object value) {
return (JWTTokenRequest) super.set(fieldName, value);
}
/**
* Create a JWT for the given project id, signed with the given RSA key.
*/
private String signJwtRsa(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Create a JWT for the given project id, signed with the given ES key.
*/
private String signJwtEs(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
return jwtBuilder.signWith(SignatureAlgorithm.ES256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Finalize the JWT and set it in the assertion property of the web service call
* @throws java.io.IOException
*/
void makeAssertion() {
JsonParser parser = new JsonParser();
JsonObject jsonDoc = (JsonObject) parser.parse(serviceKeyJson);
String pkStr = jsonDoc.get("private_key").getAsString()
.replace("\n", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
byte[] pkBytes = Base64.getDecoder().decode(pkStr);
DateTime now = new DateTime();
JwtBuilder jwtBuilder = Jwts.builder()
.setIssuedAt(now.toDate())
.setExpiration(now.plusMinutes(60).toDate())
.setAudience(getTokenServerUrl().toString())
.setIssuer(jsonDoc.get("client_email").getAsString());
if (getScopes() != null) {
jwtBuilder = jwtBuilder.claim("scope", getScopes());
}
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkBytes);
String pkId = jsonDoc.get("private_key_id").getAsString();
jwtBuilder.setHeaderParam("kid", pkId);
jwtBuilder.setHeaderParam("typ", "JWT");
set("assertion", doRsa ? signJwtRsa(jwtBuilder, spec) : signJwtEs(jwtBuilder, spec));
}
/**
* Finalize the JWT, set it in the assertion property of the web service call, and make the token request.
*/
@Override
public TokenResponse execute() throws IOException {
makeAssertion();
return super.execute();
}
}
给这个,我可以从服务帐户 JSON 密钥文件设置令牌请求,执行()它,并获取生成的访问令牌。请注意,令牌续订责任取决于调用方。
我在 Google 开发人员控制台中创建了一个应用程序,然后创建了 OAuth2 凭据。我有一个 client_id 和 client_secret。现在,我想使用这些来获取 two-legged 调用 Google 驱动器 API 的访问令牌。我在 java:
中使用 Google 的 oauth2 客户端import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.TokenResponse;
...
public void oauth2Test() {
String clientId = "...";
String clientSecret = "...";
ClientCredentialsTokenRequest request = new ClientCredentialsTokenRequest(
new NetHttpTransport(),
new JacksonFactory(),
new GenericUrl("https://accounts.google.com/o/oauth2/auth"));
request.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret));
TokenResponse response;
try {
response = request.execute();
} catch (Exception ex) {
ex.printStackTrace();
}
}
但是,我收到消息“400 错误请求”
"Required parameter is missing: response_type".
在two-legged请求模型中获取访问令牌的正确方法是什么?注意:我只有 client_id 和 client_secret,我没有完整的 API 令牌.
编辑:我最初的问题不准确。虽然我更喜欢只从 client_id 和 client_secret 开始,但这不是必需的。可以使用google-specific APIs获取访问令牌,使用GoogleCredential也可以。 必要的 是我能够使用从通用 REST 调用中的授权过程中获得的任何访问令牌。换句话说,给定 google 应用凭据,可以是 {client_id,client_secret},或 JSON 或 P12 格式的 google 服务帐户密钥,我如何获取访问令牌以及它们如何在 REST API 调用中使用——我是设置授权 header 还是其他?
第一个回答指出不支持client_credential,我已经验证过了。但是我仍然需要一个路径来获取不记名令牌,这样我就可以在没有特定 Google 客户端 API 库的 REST 调用中使用它。所以我从有效的代码开始,但使用了 Google 库。它需要一个 JSON 凭证文件。
InputStream is = getClass().getResourceAsStream("JSONCredFile");
GoogleCredential credential = GoogleCredential.fromStream(is).createScoped(scopes);
Drive service = new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName("My app")
.build();
FileList result = service.files().list().setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
通过将 SSLSocket 代理连接到凭据(详细信息已省略),我能够跟踪出站通信:
POST /token HTTP/1.1
Accept-Encoding: gzip
User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: oauth2.googleapis.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 771
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=<lots of encoded stuff>
回复是 gzip-encoded 不记名令牌,用于 API 调用:
GET /drive/v3/files?fields=nextPageToken,%20files(id,%20name)&pageSize=10 HTTP/1.1
Accept-Encoding: gzip
Authorization: Bearer ya29.c.Eln_BSgrx0afa85mdMstW5jzEvM5dotWpctSXl-DE1jeO2mmu1h0FErr_EZO05YnC-B1yz30IBwOyFXoWr_wwKxlZk08R6eZldNU-EAfrQ1yNftymn_Qqc_pfg
很明显这是JWT profile of oauth2。但现在呢?我需要以某种方式获取不记名令牌,而无需通过特定库实际进行 API 调用。 Google OAuth2 库似乎不支持这种请求类型,至少我没有看到 TokenRequest 的“JWT”风格。我可以直接编写 OAuth2 调用,还是创建一个支持 JWT 的 TokenRequest 子类?
有更好的主意吗?
Google 不支持 grant_type=client_credentials 这就是您使用 OAuth 客户端 ID 和密码执行 2LO 的方式。
您可以使用服务帐户进行 2LO:https://developers.google.com/identity/protocols/OAuth2ServiceAccount
好的,我终于弄明白了如何制作 JWT、发送 OAuth2 请求并提取访问令牌。为了更容易与 google OAuth2 客户端集成,我将 TokenRequest 子类化:
import com.google.api.client.auth.oauth2.TokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collection;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
/**
* @author jlilley
*/
public class JWTTokenRequest extends TokenRequest {
private String serviceKeyJson;
private boolean doRsa = true;
public JWTTokenRequest(HttpTransport transport, JsonFactory jsonFactory, GenericUrl tokenServerUrl) {
super(transport, jsonFactory, tokenServerUrl, "urn:ietf:params:oauth:grant-type:jwt-bearer");
}
@Override
public JWTTokenRequest setTokenServerUrl(GenericUrl tokenServerUrl) {
return (JWTTokenRequest)super.setTokenServerUrl(tokenServerUrl);
}
public JWTTokenRequest setServiceKey(String json) throws Exception {
this.serviceKeyJson = json;
return this;
}
public JWTTokenRequest setServiceKey(InputStream is) throws Exception {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(is))) {
return setServiceKey(buffer.lines().collect(Collectors.joining("\n")));
}
}
@Override
public JWTTokenRequest setScopes(Collection<String> scopes) {
return (JWTTokenRequest) super.setScopes(scopes);
}
@Override
public JWTTokenRequest set(String fieldName, Object value) {
return (JWTTokenRequest) super.set(fieldName, value);
}
/**
* Create a JWT for the given project id, signed with the given RSA key.
*/
private String signJwtRsa(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Create a JWT for the given project id, signed with the given ES key.
*/
private String signJwtEs(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
return jwtBuilder.signWith(SignatureAlgorithm.ES256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Finalize the JWT and set it in the assertion property of the web service call
* @throws java.io.IOException
*/
void makeAssertion() {
JsonParser parser = new JsonParser();
JsonObject jsonDoc = (JsonObject) parser.parse(serviceKeyJson);
String pkStr = jsonDoc.get("private_key").getAsString()
.replace("\n", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
byte[] pkBytes = Base64.getDecoder().decode(pkStr);
DateTime now = new DateTime();
JwtBuilder jwtBuilder = Jwts.builder()
.setIssuedAt(now.toDate())
.setExpiration(now.plusMinutes(60).toDate())
.setAudience(getTokenServerUrl().toString())
.setIssuer(jsonDoc.get("client_email").getAsString());
if (getScopes() != null) {
jwtBuilder = jwtBuilder.claim("scope", getScopes());
}
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkBytes);
String pkId = jsonDoc.get("private_key_id").getAsString();
jwtBuilder.setHeaderParam("kid", pkId);
jwtBuilder.setHeaderParam("typ", "JWT");
set("assertion", doRsa ? signJwtRsa(jwtBuilder, spec) : signJwtEs(jwtBuilder, spec));
}
/**
* Finalize the JWT, set it in the assertion property of the web service call, and make the token request.
*/
@Override
public TokenResponse execute() throws IOException {
makeAssertion();
return super.execute();
}
}
给这个,我可以从服务帐户 JSON 密钥文件设置令牌请求,执行()它,并获取生成的访问令牌。请注意,令牌续订责任取决于调用方。