我正在使用ASP。为Android客户端提供API。Android以Google帐户登录,并将JWT (ID令牌)作为承载令牌传递给API。我让应用正常工作,它确实通过了认证检查,但我不认为它在验证令牌签名。
根据Google的文档,我可以调用这个url: https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123,但是我在服务器端找不到合适的钩子来做这件事。此外,根据谷歌文档,我可以以某种方式使用客户端访问api来做到这一点,而无需每次调用服务器。
配置代码:
app.UseJwtBearerAuthentication( new JwtBearerOptions()
{
Authority = "https://accounts.google.com",
Audience = "hiddenfromyou.apps.googleusercontent.com",
TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidIssuer = "accounts.google.com"
},
RequireHttpsMetadata = false,
AutomaticAuthenticate = true,
AutomaticChallenge = false,
});
如何让JWTBearer中间件验证签名?
有几种不同的方法可以在服务器端验证ID令牌的完整性:
- "手动"-不断下载谷歌的公钥,验证签名,然后每个字段,包括
iss
;我在这里看到的主要优势(尽管在我看来是一个小优势)是,您可以最大限度地减少发送到Google的请求数量。 - "自动"-在Google的端点上执行GET操作来验证此令牌
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}
- 使用Google API客户端库-就像官方的一样。
第二个字符是这样的:
private const string GoogleApiTokenInfoUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}";
public ProviderUserDetails GetUserDetails(string providerToken)
{
var httpClient = new MonitoredHttpClient();
var requestUri = new Uri(string.Format(GoogleApiTokenInfoUrl, providerToken));
HttpResponseMessage httpResponseMessage;
try
{
httpResponseMessage = httpClient.GetAsync(requestUri).Result;
}
catch (Exception ex)
{
return null;
}
if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
{
return null;
}
var response = httpResponseMessage.Content.ReadAsStringAsync().Result;
var googleApiTokenInfo = JsonConvert.DeserializeObject<GoogleApiTokenInfo>(response);
if (!SupportedClientsIds.Contains(googleApiTokenInfo.aud))
{
Log.WarnFormat("Google API Token Info aud field ({0}) not containing the required client id", googleApiTokenInfo.aud);
return null;
}
return new ProviderUserDetails
{
Email = googleApiTokenInfo.email,
FirstName = googleApiTokenInfo.given_name,
LastName = googleApiTokenInfo.family_name,
Locale = googleApiTokenInfo.locale,
Name = googleApiTokenInfo.name,
ProviderUserId = googleApiTokenInfo.sub
};
}
GoogleApiTokenInfo类:
public class GoogleApiTokenInfo
{
/// <summary>
/// The Issuer Identifier for the Issuer of the response. Always https://accounts.google.com or accounts.google.com for Google ID tokens.
/// </summary>
public string iss { get; set; }
/// <summary>
/// Access token hash. Provides validation that the access token is tied to the identity token. If the ID token is issued with an access token in the server flow, this is always
/// included. This can be used as an alternate mechanism to protect against cross-site request forgery attacks, but if you follow Step 1 and Step 3 it is not necessary to verify the
/// access token.
/// </summary>
public string at_hash { get; set; }
/// <summary>
/// Identifies the audience that this ID token is intended for. It must be one of the OAuth 2.0 client IDs of your application.
/// </summary>
public string aud { get; set; }
/// <summary>
/// An identifier for the user, unique among all Google accounts and never reused. A Google account can have multiple emails at different points in time, but the sub value is never
/// changed. Use sub within your application as the unique-identifier key for the user.
/// </summary>
public string sub { get; set; }
/// <summary>
/// True if the user's e-mail address has been verified; otherwise false.
/// </summary>
public string email_verified { get; set; }
/// <summary>
/// The client_id of the authorized presenter. This claim is only needed when the party requesting the ID token is not the same as the audience of the ID token. This may be the
/// case at Google for hybrid apps where a web application and Android app have a different client_id but share the same project.
/// </summary>
public string azp { get; set; }
/// <summary>
/// The user's email address. This may not be unique and is not suitable for use as a primary key. Provided only if your scope included the string "email".
/// </summary>
public string email { get; set; }
/// <summary>
/// The time the ID token was issued, represented in Unix time (integer seconds).
/// </summary>
public string iat { get; set; }
/// <summary>
/// The time the ID token expires, represented in Unix time (integer seconds).
/// </summary>
public string exp { get; set; }
/// <summary>
/// The user's full name, in a displayable form. Might be provided when:
/// The request scope included the string "profile"
/// The ID token is returned from a token refresh
/// When name claims are present, you can use them to update your app's user records. Note that this claim is never guaranteed to be present.
/// </summary>
public string name { get; set; }
/// <summary>
/// The URL of the user's profile picture. Might be provided when:
/// The request scope included the string "profile"
/// The ID token is returned from a token refresh
/// When picture claims are present, you can use them to update your app's user records. Note that this claim is never guaranteed to be present.
/// </summary>
public string picture { get; set; }
public string given_name { get; set; }
public string family_name { get; set; }
public string locale { get; set; }
public string alg { get; set; }
public string kid { get; set; }
}
根据这个github问题,您现在可以使用GoogleJsonWebSignature.ValidateAsync
方法来验证google签名的JWT。只需将idToken
字符串传递给该方法。
var validPayload = await GoogleJsonWebSignature.ValidateAsync(idToken);
Assert.IsNotNull(validPayload);
如果不是一个有效的令牌,它将返回null
。
注意,要使用此方法,您需要先安装google . api . auth nuget。
Google在openId connect文档中的声明
出于调试目的,您可以使用Google的tokeninfo端点。假设你的ID令牌的值是XYZ123。
您不应该使用该端点来验证JWT。
ID令牌的验证需要以下几个步骤:
- 验证ID令牌是否被发行者正确签名。google颁发的令牌使用在发现文档的jwks_uri字段中指定的URI中找到的证书之一进行签名。
- 验证ID令牌中iss的值是否等于https://accounts.google.com或accounts.google.com
- 验证ID令牌中的aud值是否等于您的应用程序的客户端ID。
- 验证ID令牌的过期时间(exp)未超过。
- 如果您在请求中传递了hd参数,请验证ID令牌具有与您的G Suite托管域匹配的hd声明。
这里有一个关于如何验证它们的官方示例项目。不幸的是,我们还没有将它添加到Google . net客户端库中。它已被记录为一个问题
我认为值得一提的是,您可以使用@Alexandru Marculescu答案进行身份验证,但文档中有一个注意事项,不要使用方法2。
根据调用tokeninfo端点(已更改为https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123)下的文档
It is not suitable for use in production code as requests may be throttled or otherwise subject to intermittent errors.
验证idToken的推荐方法是调用Google API Client Library。这就是我执行验证检查
的方式using Google.Apis.Auth;
...
public async Task<GoogleJsonWebSignature.Payload> ValidateIdTokenAndGetUserInfo(string idToken)
{
if (string.IsNullOrWhiteSpace(idToken))
{
return null;
}
try
{
return await GoogleJsonWebSignature.ValidateAsync(idToken);
}
catch (Exception exception)
{
_Logger.LogError(exception, $"Error calling ValidateIdToken in GoogleAuthenticateHttpClient");
return null;
}
}
所以,我发现的是,作为OpenIDConnect规范有一个/。众所周知的/url,其中包含验证令牌所需的信息。这包括对签名公钥的访问。JWT中间件从权威机构生成. known url,检索信息,然后继续自己验证它。
对这个问题的简短回答是,验证已经在中间件中发生了,没有什么要做的了。
private const string GoogleApiTokenInfoUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}";
Public ProviderUserDetails ValidateGoogleToken(string providerToken)
{
var httpClient = new HttpClient();
var requestUri = new Uri(string.Format(GoogleApiTokenInfoUrl, providerToken));
HttpResponseMessage httpResponseMessage;
try
{
httpResponseMessage = httpClient.GetAsync(requestUri).Result;
}
catch (Exception ex)
{
return null;
}
if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
{
return null;
}
var response = httpResponseMessage.Content.ReadAsStringAsync().Result;
var googleApiTokenInfo = JsonConvert.DeserializeObject<GoogleApiTokenInfo>(response);
return new ProviderUserDetails
{
Email = googleApiTokenInfo.email,
FirstName = googleApiTokenInfo.given_name,
LastName = googleApiTokenInfo.family_name,
Locale = googleApiTokenInfo.locale,
Name = googleApiTokenInfo.name,
ProviderUserId = googleApiTokenInfo.sub
};
}
使用id_token从Google API解码JWT令牌。有没有支持Google登录的Django RestFramework库?有人能分享任何Django Rest后端git代码的例子吗从Google服务器验证你的应用令牌的id_token的Google认证