如何使用 System.IdentityModel.Tokens.Jwt 生成具有 Google OAuth2 兼容算法 RSA SHA-256 的 JWT?
Posted
技术标签:
【中文标题】如何使用 System.IdentityModel.Tokens.Jwt 生成具有 Google OAuth2 兼容算法 RSA SHA-256 的 JWT?【英文标题】:How to produce JWT with Google OAuth2 compatible algorithm RSA SHA-256 using System.IdentityModel.Tokens.Jwt? 【发布时间】:2014-12-16 04:55:47 【问题描述】:我正在尝试使用System.IdentityModel.Tokens.Jwt 创建一个 JWT 以使用Google documentation 中所述的服务帐户进行授权。我有以下代码:
byte[] key = Convert.FromBase64String("...");
var certificate = new X509Certificate2(key, "notasecret");
DateTime now = DateTime.UtcNow;
TimeSpan span = now - UnixEpoch;
Claim[] claims =
new Claim("iss", "email@developer.gserviceaccount.com"),
new Claim("scope", "https://www.googleapis.com/auth/plus.me"),
new Claim("aud", "https://accounts.google.com/o/oauth2/token"),
new Claim("iat", span.TotalSeconds.ToString()),
new Claim("exp", span.Add(TimeSpan.FromHours(1)).TotalSeconds.ToString())
;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var descriptor = new SecurityTokenDescriptor
SigningCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(key),
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256"),
Subject = new ClaimsIdentity(claims)
;
JwtSecurityToken jwtSecurityToken = (JwtSecurityToken)handler.CreateToken(descriptor);
string json = handler.WriteToken(jwtSecurityToken);
哪个输出:
"typ" : "JWT" , "alg" : "HS256"
虽然 Google 明确声明它支持 SHA-256:
服务帐号依赖于 RSA SHA-256 算法和 JWT 令牌格式
根据wtSecurityTokenHandler.InboundAlgorithmMap:
RS256 => http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
HS256 => http://www.w3.org/2001/04/xmldsig-more#hmac-sha256
所以当我更改代码时:
new SigningCredentials(
new InMemorySymmetricSecurityKey(key),
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256");
我遇到了一个异常:
System.InvalidOperationException: IDX10632: SymmetricSecurityKey.GetKeyedHashAlgorithm( 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' ) threw an exception.
SymmetricSecurityKey: 'System.IdentityModel.Tokens.InMemorySymmetricSecurityKey'
SignatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', check to make sure the SignatureAlgorithm is supported.
这是否意味着微软不支持谷歌独家支持的算法?
【问题讨论】:
也许尝试使用签名和摘要算法的内置常量? (msdn.microsoft.com/en-us/library/…) @Jeff:嘿,抱歉,错过了关于您的评论的通知。好点子。但不幸的是仍然无法正常工作。 【参考方案1】:这个问题被问到已经有一段时间了,但我认为对于未来访问此页面的人来说,可能值得知道的是,使用 .NET Google 只需几行代码就可以很容易地获得相同的结果身份验证 API(其 nuget 可在此处获得:Google.Apis.Auth
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
namespace GoogleTest
public class GoogleOAuth2
/// <summary>
/// Authorization scope for our requests
/// </summary>
private readonly string _defaultScope;
/// <summary>
/// Service account will be of the form nnnnnnn@developer.gserviceaccount.com
/// </summary>
private readonly string _serviceAccount;
/// <summary>
/// Set this to the full path to your service account private key file.
/// </summary>
private readonly string _certificateFile;
public GoogleOAuth2(string defaultScope, string serviceAccount, string certificateFile)
_defaultScope = defaultScope;
_serviceAccount = serviceAccount;
_certificateFile = certificateFile;
/// <summary>
/// Access Token returned by Google Token Server
/// </summary>
public string AccessToken get; set;
public async Task<bool> RequestAccessTokenAsync()
var certificate = new X509Certificate2(_certificateFile, "notasecret", X509KeyStorageFlags.Exportable);
var serviceAccountCredential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(_serviceAccount)
Scopes = new[] _defaultScope
.FromCertificate(certificate));
var status = await serviceAccountCredential.RequestAccessTokenAsync(CancellationToken.None);
if (status)
AccessToken = serviceAccountCredential.Token.AccessToken;
return status;
要获取访问令牌,您只需调用方法 RequestAccessTokenAsync ,如果结果成功,您将在 AccessToken 属性中获得令牌。
请注意,此实现假定在开发人员控制台中,您已将私钥导出为 .P12 文件。
希望这个答案会有所帮助。
【讨论】:
试过这段代码,但我总是得到错误的状态:-(有什么想法吗? 这段代码 sn-p 很好用。如果要使用 FCM 私钥,请将 serviceAccountCredential 实例化更改为: var serviceAccountCredential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(_serviceAccount) Scopes = new[] _defaultScope .FromPrivateKey(_privateKey));其中 _privateKey 是 google-services.json 文件中的“private_key”值。【参考方案2】:private static async Task<string> GetAuthorizationToken(GoogleAuthOptions authOptions)
string jwt = CreateJwt(authOptions);
var dic = new Dictionary<string, string>
"grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" ,
"assertion", jwt
;
var content = new FormUrlEncodedContent(dic);
var httpClient = new HttpClient BaseAddress = new Uri("https://accounts.google.com") ;
var response = await httpClient.PostAsync("/o/oauth2/token", content);
response.EnsureSuccessStatusCode();
dynamic dyn = await response.Content.ReadAsAsync<dynamic>();
return dyn.access_token;
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
private static string CreateJwt(GoogleAuthOptions authOptions)
var certificate = new X509Certificate2(Convert.FromBase64String(authOptions.CertificateKey), authOptions.CertificateSecret);
DateTime now = DateTime.UtcNow;
var claimset = new
iss = authOptions.Issuer,
scope = "https://www.googleapis.com/auth/plus.me",
aud = authOptions.Audience,
iat = ((int)now.Subtract(UnixEpoch).TotalSeconds).ToString(CultureInfo.InvariantCulture),
exp = ((int)now.AddMinutes(55).Subtract(UnixEpoch).TotalSeconds).ToString(CultureInfo.InvariantCulture)
;
// header
var header = new typ = "JWT", alg = "RS256" ;
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = TextEncodings.Base64Url.Encode(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = TextEncodings.Base64Url.Encode(claimsetBytes);
// input
var input = String.Join(".", headerEncoded, claimsetEncoded);
var inputBytes = Encoding.UTF8.GetBytes(input);
// signiture
var rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
var cspParam = new CspParameters
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
;
var cryptoServiceProvider = new RSACryptoServiceProvider(cspParam) PersistKeyInCsp = false ;
var signatureBytes = cryptoServiceProvider.SignData(inputBytes, "SHA256");
var signatureEncoded = TextEncodings.Base64Url.Encode(signatureBytes);
// jwt
return String.Join(".", headerEncoded, claimsetEncoded, signatureEncoded);
【讨论】:
@DaImTo: 当然,这里是github.com/abatishchev/ab/blob/master/Ab/Configuration/… 谢谢我已经为此工作了将近一周。我即将获得 invalid_grant。【参考方案3】:我不得不稍微修改@abatishchev 的代码。否则,在部署到非开发环境时生成证书会出现问题。
问题有两个方面。如果证书没有被标记为可导出,它会抛出一个异常,比如“keyset 不存在”。它只会在服务器上发生,而不是在本地发生,所以我怀疑 Windows 的服务器版本限制性更强。
此外,由于证书是在用户密钥集中创建的,因此它会引发有关计算机信任问题的加密异常。我们的应用程序池设置为不在高级选项中导入用户配置文件,您可以这样做。但由于与其他应用程序的兼容性问题,这不是我们的选择。设置要在机器密钥集中创建的证书可以缓解该问题。
2 个更改的部分用 cmets 标记。
private static async Task<string> GetAuthorizationToken(GoogleAuthOptions authOptions)
string jwt = CreateJwt(authOptions);
var dic = new Dictionary<string, string>
"grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" ,
"assertion", jwt
;
var content = new FormUrlEncodedContent(dic);
var httpClient = new HttpClient BaseAddress = new Uri("https://accounts.google.com") ;
var response = await httpClient.PostAsync("/o/oauth2/token", content);
response.EnsureSuccessStatusCode();
dynamic dyn = await response.Content.ReadAsAsync<dynamic>();
return dyn.access_token;
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
private static string CreateJwt(GoogleAuthOptions authOptions)
/* changed */
const X509KeyStorageFlags certificateFlags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable;
var certificate = new X509Certificate2(Convert.FromBase64String(authOptions.CertificateKey), authOptions.CertificateSecret, certificateFlags);
/* end of change */
DateTime now = DateTime.UtcNow;
var claimset = new
iss = authOptions.Issuer,
scope = "https://www.googleapis.com/auth/plus.me",
aud = authOptions.Audience,
iat = ((int)now.Subtract(UnixEpoch).TotalSeconds).ToString(CultureInfo.InvariantCulture),
exp = ((int)now.AddMinutes(55).Subtract(UnixEpoch).TotalSeconds).ToString(CultureInfo.InvariantCulture)
;
// header
var header = new typ = "JWT", alg = "RS256" ;
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = TextEncodings.Base64Url.Encode(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = TextEncodings.Base64Url.Encode(claimsetBytes);
// input
var input = String.Join(".", headerEncoded, claimsetEncoded);
var inputBytes = Encoding.UTF8.GetBytes(input);
// signiture
var rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
var cspParam = new CspParameters
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
/* changed */
KeyNumber = (int) KeyNumber.Exchange,
Flags = CspProviderFlags.UseMachineKeyStore
/* end of change */
;
var cryptoServiceProvider = new RSACryptoServiceProvider(cspParam) PersistKeyInCsp = false ;
var signatureBytes = cryptoServiceProvider.SignData(inputBytes, "SHA256");
var signatureEncoded = TextEncodings.Base64Url.Encode(signatureBytes);
// jwt
return String.Join(".", headerEncoded, claimsetEncoded, signatureEncoded);
【讨论】:
嘿,你能解释一下你面临的问题吗? 如果您愿意,我可以将您的更改合并到我的答案中。 我编辑了帖子以提供有关问题的更多详细信息。它们可能与服务器配置有关。所以我会把我的帖子分开,这样它可以帮助有这些特定问题的人。 CSP 参数更改让我很开心。非常感谢!以上是关于如何使用 System.IdentityModel.Tokens.Jwt 生成具有 Google OAuth2 兼容算法 RSA SHA-256 的 JWT?的主要内容,如果未能解决你的问题,请参考以下文章
我如何在 ASP.Net Core 2.1 mvc 应用程序中包含 System.Identitymodel 4.0
使用 Microsoft System.IdentityModel.Tokens.Jwt 在 Asp.net WebApi 中实现 JWT 身份验证
System.IdentityModel.Tokens 和 Microsoft.IdentityModel.Tokens 有啥区别?我应该在 ASP.NET Core 应用程序中使用哪一个?
System.IdentityModel.Tokens.JwtSecurityToken 自定义属性
使用 System.IdentityModel.Tokens.Jwt 使用 RS512 验证 JWT 签名
如何使用ConfigurationManager? (Microsoft.IdentityModel.Protocols)