asp.net-core - Azure Devops Oauth 身份验证:无法获取访问令牌(BadRequest 无法反序列化 JsonWebToken 对象)
问题描述
我正在尝试为 Azure Devops 的自定义 Web 应用程序实现 OAUth 2.0 流。我正在关注这个https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops文档以及这个https://github.com /microsoft/azure-devops-auth-samples/tree/master/OAuthWebSample OauthWebSample 但使用 ASP.NET Core(我还阅读了关于 SO 的一个问题,看起来相似但不是:Access Azure DevOps REST API with oAuth)
再生产
我已经在https://app.vsaex.visualstudio.com/app/register注册了一个 azdo 应用程序,并且授权步骤似乎工作正常,即用户可以授权应用程序,并且重定向到我的应用程序返回的东西看起来像有效的 jwt 令牌:
header: {
"typ": "JWT",
"alg": "RS256",
"x5t": "oOvcz5M_7p-HjIKlFXz93u_V0Zo"
}
payload: {
"aui": "b3426a71-1c05-497c-ab76-259161dbcb9e",
"nameid": "7e8ce1ba-1e70-4c21-9b51-35f91deb6d14",
"scp": "vso.identity vso.work_write vso.authorization_grant",
"iss": "app.vstoken.visualstudio.com",
"aud": "app.vstoken.visualstudio.com",
"nbf": 1587294992,
"exp": 1587295892
}
下一步是获取一个访问令牌,该令牌失败并显示BadReqest: invalid_client, Failed to deserialize the JsonWebToken object。
这是完整的示例:
public class Config
{
public string ClientId { get; set; } = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
public string Secret { get; set; } = "....";
public string Scope { get; set; } = "vso.identity vso.work_write";
public string RedirectUri { get; set; } = "https://....ngrok.io/azdoaccount/callback";
}
/// <summary>
/// Create azdo application at https://app.vsaex.visualstudio.com/
/// Use configured values in above 'Config' (using ngrok to have a public url that proxies to localhost)
/// navigating to localhost:5001/azdoaccount/signin
/// => redirect to https://app.vssps.visualstudio.com/oauth2/authorize and let user authorize (seems to work)
/// => redirect back to localhost:5001/azdoaccount/callback with auth code
/// => post to https://app.vssps.visualstudio.com/oauth2/token => BadReqest: invalid_client, Failed to deserialize the JsonWebToken object
/// </summary>
[Route("[controller]/[action]")]
public class AzdoAccountController : Controller
{
private readonly Config config = new Config();
[HttpGet]
public ActionResult SignIn()
{
Guid state = Guid.NewGuid();
UriBuilder uriBuilder = new UriBuilder("https://app.vssps.visualstudio.com/oauth2/authorize");
NameValueCollection queryParams = HttpUtility.ParseQueryString(uriBuilder.Query ?? string.Empty);
queryParams["client_id"] = config.ClientId;
queryParams["response_type"] = "Assertion";
queryParams["state"] = state.ToString();
queryParams["scope"] = config.Scope;
queryParams["redirect_uri"] = config.RedirectUri;
uriBuilder.Query = queryParams.ToString();
return Redirect(uriBuilder.ToString());
}
[HttpGet]
public async Task<ActionResult> Callback(string code, Guid state)
{
string token = await GetAccessToken(code, state);
return Ok();
}
public async Task<string> GetAccessToken(string code, Guid state)
{
Dictionary<string, string> form = new Dictionary<string, string>()
{
{ "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" },
{ "client_assertion", config.Secret },
{ "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" },
{ "assertion", code },
{ "redirect_uri", config.RedirectUri }
};
HttpClient httpClient = new HttpClient();
HttpResponseMessage responseMessage = await httpClient.PostAsync(
"https://app.vssps.visualstudio.com/oauth2/token",
new FormUrlEncodedContent(form)
);
if (responseMessage.IsSuccessStatusCode) // is always false for me
{
string body = await responseMessage.Content.ReadAsStringAsync();
// TODO parse body and return access token
return "";
}
else
{
// Bad Request ({"Error":"invalid_client","ErrorDescription":"Failed to deserialize the JsonWebToken object."})
string content = await responseMessage.Content.ReadAsStringAsync();
throw new Exception($"{responseMessage.ReasonPhrase} {(string.IsNullOrEmpty(content) ? "" : $"({content})")}");
}
}
}
解决方案
推荐阅读
- javascript - 如何查看此“jQuery 绑定/取消绑定自定义函数”功能的源代码?
- ionic4 - 如何掩盖弹出窗口左侧的迷你滚动条?
- javascript - 当模板中的数组值为空或长度 === 0 时,如何显示输入框?
- javascript - 对象构造函数中的“对象”到底是什么意思?
- c# - C# Windows 服务文件移动到不同的目录
- hadoop - 将 hive 查询结果从一个 hadoop 集群传输到另一个 hadoop 集群
- postman - 从集合中提取请求的方法在哪里?
- python-3.7 - 为什么这个定义返回 False 而不是 True?
- laravel - PHP 函数 stripos() 期望参数 1 是字符串,给定对象
- java - 我怎样才能找到以下函数的时间复杂度?