C# .NET 代码验证 WSO2 API 网关 JWT 签名,使用 SHA256withRSA 算法
C# .NET Code to validate WSO2 API Gateway JWT signature, using SHA256withRSA algorithm
有人可以提供示例 C# .NET 代码来验证 WSO2 API 网关发布的 JWT,该网关使用 SHA256withRSA 算法签名。我很确定我需要设置 TokenValidationParameters.IssuerSigningToken,然后调用 JwtSecurityTokenHandler.ValidateToken 方法,但我无法让它工作,也无法找到任何示例代码。
这是我目前拥有的:
// Use JwtSecurityTokenHandler to validate the JWT token
var tokenHandler = new JwtSecurityTokenHandler();
var convertedSecret = EncodeSigningToken(ConfigurationManager.AppSettings["ClientSecret"]);
// Read the JWT
var parsedJwt = tokenHandler.ReadToken(token);
// Set the expected properties of the JWT token in the TokenValidationParameters
var validationParameters = new TokenValidationParameters()
{
NameClaimType = "http://wso2.org/claims/enduser",
AuthenticationType = "http://wso2.org/claims/usertype",
ValidAudience = ConfigurationManager.AppSettings["AllowedAudience"],
ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
IssuerSigningToken = new BinarySecretSecurityToken(convertedSecret)
};
var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out parsedJwt);
来自 WSO2 API 网关的 JWT 不符合规范 (https://www.rfc-editor.org/rfc/rfc7519)。
我看到的所有样本都是以下形式:
<Base64lEncodedHeader>.<Base64EncodedPayload>.<OPTIONAL, Base64EncodedSignature>
但应该是:
<Base64UrlEncodedHeader>.<Base64UrlEncodedPayload>.<OPTIONAL, Base64UrlEncodedSignature>
问题是使用Base64而不是Base64Url编码。由于签名基于 <Base64EncodedHeader>.<Base64EncodedPayload>
,并且 MS JWT 框架正在根据预期的 <Base64UrlEncodedHeader>.<Base64UrlEncodedPayload>
验证签名,因此它始终无法通过验证。我不得不编写自己的自定义签名验证代码来解决这个问题。然后我在使用 JwtSecurityTokenHandler 进行解析和解码之前从令牌中剥离签名。
这是最终代码:
try
{
// Get data and signature from unaltered token
var data = Encoding.UTF8.GetBytes(token.Split('.')[0] + '.' + token.Split('.')[1]);
var signature = Convert.FromBase64String(token.Split('.')[2]);
// Get certificate from file
var x509 = new X509Certificate2(HttpContext.Current.Server.MapPath("~/App_Data/" + ConfigurationManager.AppSettings["CertFileName"]));
// Verify the data with the signature
var csp = (RSACryptoServiceProvider)x509.PublicKey.Key;
if (!csp.VerifyData(data, "SHA256", signature))
{
// Signature verification failed; data was possibly altered
throw new SecurityTokenValidationException("Data signature verification failed. Token cannot be trusted!");
}
// strip off signature from token
token = token.Substring(0, token.LastIndexOf('.') + 1);
// Convert Base64 encoded token to Base64Url encoding
token = token.Replace('+', '-').Replace('/', '_').Replace("=", "");
// Use JwtSecurityTokenHandler to validate the JWT token
var tokenHandler = new JwtSecurityTokenHandler();
// Read the JWT
var parsedJwt = tokenHandler.ReadToken(token);
// Set the expected properties of the JWT token in the TokenValidationParameters
var validationParameters = new TokenValidationParameters()
{
NameClaimType = "http://wso2.org/claims/enduser",
AuthenticationType = ((JwtSecurityToken)parsedJwt).Claims.Where(c => c.Type == "http://wso2.org/claims/usertype").First().Value,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = false,
RequireExpirationTime = true,
RequireSignedTokens = false,
//ValidAudience = ConfigurationManager.AppSettings["AllowedAudience"],
ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
//IssuerSigningToken = new X509SecurityToken(cert),
CertificateValidator = X509CertificateValidator.None
};
// Set both HTTP Context and Thread principals, so they will be in sync
HttpContext.Current.User = tokenHandler.ValidateToken(token, validationParameters, out parsedJwt);
Thread.CurrentPrincipal = HttpContext.Current.User;
// Treat as ClaimsPrincipal, extract JWT expiration and inject it into request headers
var cp = (ClaimsPrincipal)Thread.CurrentPrincipal;
context.Request.Headers.Add("JWT-Expiration", cp.FindFirst("exp").Value);
}
catch (SecurityTokenValidationException stvErr)
{
// Log error
if (context.Trace.IsEnabled)
context.Trace.Write("JwtAuthorization", "Error validating token.", stvErr);
}
catch (System.Exception ex)
{
// Log error
if (context.Trace.IsEnabled)
context.Trace.Write("JwtAuthorization", "Error parsing token.", ex);
}
WSO2 提供了一个选项,可以将 JWT 的格式更改为 URL 编码,之后将不需要自定义代码。
文档@https://docs.wso2.com/display/AM260/Passing+Enduser+Attributes+to+the+Backend+Using+JWT提到:
"However, for certain apps you might need to have it in Base64URL encoding. To encode the JWT using Base64URL encoding, add the URLSafeJWTGenerator class in the element in the /repository/conf/api-manager.xml"
有人可以提供示例 C# .NET 代码来验证 WSO2 API 网关发布的 JWT,该网关使用 SHA256withRSA 算法签名。我很确定我需要设置 TokenValidationParameters.IssuerSigningToken,然后调用 JwtSecurityTokenHandler.ValidateToken 方法,但我无法让它工作,也无法找到任何示例代码。
这是我目前拥有的:
// Use JwtSecurityTokenHandler to validate the JWT token
var tokenHandler = new JwtSecurityTokenHandler();
var convertedSecret = EncodeSigningToken(ConfigurationManager.AppSettings["ClientSecret"]);
// Read the JWT
var parsedJwt = tokenHandler.ReadToken(token);
// Set the expected properties of the JWT token in the TokenValidationParameters
var validationParameters = new TokenValidationParameters()
{
NameClaimType = "http://wso2.org/claims/enduser",
AuthenticationType = "http://wso2.org/claims/usertype",
ValidAudience = ConfigurationManager.AppSettings["AllowedAudience"],
ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
IssuerSigningToken = new BinarySecretSecurityToken(convertedSecret)
};
var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out parsedJwt);
来自 WSO2 API 网关的 JWT 不符合规范 (https://www.rfc-editor.org/rfc/rfc7519)。
我看到的所有样本都是以下形式:
<Base64lEncodedHeader>.<Base64EncodedPayload>.<OPTIONAL, Base64EncodedSignature>
但应该是:
<Base64UrlEncodedHeader>.<Base64UrlEncodedPayload>.<OPTIONAL, Base64UrlEncodedSignature>
问题是使用Base64而不是Base64Url编码。由于签名基于 <Base64EncodedHeader>.<Base64EncodedPayload>
,并且 MS JWT 框架正在根据预期的 <Base64UrlEncodedHeader>.<Base64UrlEncodedPayload>
验证签名,因此它始终无法通过验证。我不得不编写自己的自定义签名验证代码来解决这个问题。然后我在使用 JwtSecurityTokenHandler 进行解析和解码之前从令牌中剥离签名。
这是最终代码:
try
{
// Get data and signature from unaltered token
var data = Encoding.UTF8.GetBytes(token.Split('.')[0] + '.' + token.Split('.')[1]);
var signature = Convert.FromBase64String(token.Split('.')[2]);
// Get certificate from file
var x509 = new X509Certificate2(HttpContext.Current.Server.MapPath("~/App_Data/" + ConfigurationManager.AppSettings["CertFileName"]));
// Verify the data with the signature
var csp = (RSACryptoServiceProvider)x509.PublicKey.Key;
if (!csp.VerifyData(data, "SHA256", signature))
{
// Signature verification failed; data was possibly altered
throw new SecurityTokenValidationException("Data signature verification failed. Token cannot be trusted!");
}
// strip off signature from token
token = token.Substring(0, token.LastIndexOf('.') + 1);
// Convert Base64 encoded token to Base64Url encoding
token = token.Replace('+', '-').Replace('/', '_').Replace("=", "");
// Use JwtSecurityTokenHandler to validate the JWT token
var tokenHandler = new JwtSecurityTokenHandler();
// Read the JWT
var parsedJwt = tokenHandler.ReadToken(token);
// Set the expected properties of the JWT token in the TokenValidationParameters
var validationParameters = new TokenValidationParameters()
{
NameClaimType = "http://wso2.org/claims/enduser",
AuthenticationType = ((JwtSecurityToken)parsedJwt).Claims.Where(c => c.Type == "http://wso2.org/claims/usertype").First().Value,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = false,
RequireExpirationTime = true,
RequireSignedTokens = false,
//ValidAudience = ConfigurationManager.AppSettings["AllowedAudience"],
ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
//IssuerSigningToken = new X509SecurityToken(cert),
CertificateValidator = X509CertificateValidator.None
};
// Set both HTTP Context and Thread principals, so they will be in sync
HttpContext.Current.User = tokenHandler.ValidateToken(token, validationParameters, out parsedJwt);
Thread.CurrentPrincipal = HttpContext.Current.User;
// Treat as ClaimsPrincipal, extract JWT expiration and inject it into request headers
var cp = (ClaimsPrincipal)Thread.CurrentPrincipal;
context.Request.Headers.Add("JWT-Expiration", cp.FindFirst("exp").Value);
}
catch (SecurityTokenValidationException stvErr)
{
// Log error
if (context.Trace.IsEnabled)
context.Trace.Write("JwtAuthorization", "Error validating token.", stvErr);
}
catch (System.Exception ex)
{
// Log error
if (context.Trace.IsEnabled)
context.Trace.Write("JwtAuthorization", "Error parsing token.", ex);
}
WSO2 提供了一个选项,可以将 JWT 的格式更改为 URL 编码,之后将不需要自定义代码。
文档@https://docs.wso2.com/display/AM260/Passing+Enduser+Attributes+to+the+Backend+Using+JWT提到:
"However, for certain apps you might need to have it in Base64URL encoding. To encode the JWT using Base64URL encoding, add the URLSafeJWTGenerator class in the element in the /repository/conf/api-manager.xml"