Azure DevOps Oauth身份验证:无法获取访问令牌(BadRequest无法反序列化JsonWebToken对象)
Azure DevOps Oauth身份验证:无法获取访问令牌(BadRequest无法反序列化JsonWebToken对象)
我正在尝试为Azure DevOps的自定义Web应用程序实现OAuth 2.0流程。我正在遵循此文档(https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops)以及此OauthWebSample(https://github.com/microsoft/azure-devops-auth-samples/tree/master/OAuthWebSample),但是使用ASP.NET Core(我也阅读了一个类似的SO问题,但并不相同:https://stackoverflow.com/questions/56527208/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"; } [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 TaskCallback(string code, Guid state) { string token = await GetAccessToken(code, state); return Ok(); } public async Task GetAccessToken(string code, Guid state) { Dictionary form = new Dictionary () { { "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) { string body = await responseMessage.Content.ReadAsStringAsync(); // TODO 解析body并返回访问令牌 return ""; } else { string content = await responseMessage.Content.ReadAsStringAsync(); throw new Exception($"{responseMessage.ReasonPhrase} {(string.IsNullOrEmpty(content) ? "" : $"({content})")}"); } } }