ASP.NET Web API和OpenID Connect:如何从授权码获取访问令牌

22

我试图启用OpenID Connect,我的Web API用户已经获取了一个OpenID Connect提供者的授权码。我应该如何将此代码传递给我的ASP.NET Web API?我必须如何配置OWIN中间件以便使用授权码获得访问令牌?

更新: SPA使用AJAX与我的Web服务(ASP.NET Web API)通信。在我的Web服务中,我使用OWIN中间件并设置OpenIDConnect作为身份验证机制。当第一次调用Web服务时,它成功地将用户重定向到OpenID Connect Provider的登录页面。用户可以登录并获得授权码。据我所知,这个代码现在可以被我的Web服务用于获得访问令牌。然而,我不知道如何将这个代码发送回我的Web服务(是否使用标头?)及其如何配置以获得访问令牌。我想我可以手动调用令牌终结点,但我希望利用OWIN组件。


我不清楚你想要实现什么。你是在询问Web API的OWIN中间件吗?还是一些调用API的客户端应用程序? - BenV
好的,我刚刚更新了我的问题。 - Dunken
5个回答

14

BenV已经回答了这个问题,但还有更多需要考虑。

class partial Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        // ...

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
          {
            ClientId = clientId,
            Authority = authority,
            Notifications = new OpenIdConnectAuthenticationNotifications() {
                AuthorizationCodeReceived = (context) => {
                   string authorizationCode = context.Code;
                   // (tricky) the authorizationCode is available here to use, but...
                   return Task.FromResult(0);
                }
            }
          }
    }
}

有两个问题:

  • 首先,authorizationCode 很快就会过期,将其存储没有任何意义。
  • 第二个问题是,在授权码没有过期且存储在会话中时,AuthorizationCodeReceived 事件将不会在任何页面重新加载时触发。

你需要做的 是调用 AcquireTokenByAuthorizationCodeAsync 方法,它会将其缓存并在 TokenCache.DefaultShare 中适当地处理:

AuthorizationCodeReceived = (context) => {
    string authorizationCode = context.Code;
    AuthenticationResult tokenResult = await context.AcquireTokenByAuthorizationCodeAsync(authorizationCode, new Uri(redirectUri), credential);
    return Task.FromResult(0);
}

现在,在调用资源之前,每次都调用AcquireTokenSilentAsync来获取accessToken(它将使用TokenCache或静默使用refreshToken)。如果token过期,它将引发AdalSilentTokenAcquisitionException异常(调用访问代码更新过程)。

// currentUser for ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
AuthenticationResult authResult = await context.AcquireTokenSilentAsync(resourceUri, credential, currentUser);

如果令牌已缓存,调用AcquireTokenSilentAsync非常快。


如果调用 AcquireTokenSilentAsync 时由于 authorizationCode 过期而抛出异常,下一步该怎么做呢? 获取新的授权码吗? 在我的应用程序中,当发生这种情况时,我使用了 AcquireToken 并返回了一个错误令牌。 - Hiep Lam
@HiepLam,你需要首先调用AcquireTokenByAuthorizationCodeAsync(),就像示例中一样。调用AcquireTokenSilentAsync()是为了在请求处理期间进行进一步的调用,因此您不会存储“authorizationCode”。但是,也有可能您没有访问资源的权限,因此会出现异常。 - andrew.fox
但是它需要通过验证,然后身份验证服务器才会发送回“授权码”(OWIN管道上的“OnOpenIdAuthorizationCodeReceived”事件),对吗?那么当代码过期时,我该如何获取另一个“授权码”? - Hiep Lam
@HiepLam,您不必存储“授权代码”。调用AcquireTokenByAuthorizationCodeAsync()将其放入内部缓存中,进一步调用AcquireTokenSilentAsync()会在内部使用此缓存(因此您不必自行存储它)。 - andrew.fox
我理解你的想法。一开始,我在“AuthorizationCodeReceived”事件中调用了“AcquireTokenByAuthorizationCodeAsync”,一切都很顺利。但是过了一段时间,“授权码”过期了,并且在调用“AcquireTokenSilentAsync”时抛出异常。所以那个时候我想知道如何获取新的“授权码”。或者是否有其他方法来解决这个问题? - Hiep Lam

12

看起来推荐的方法是使用 AuthorizationCodeReceived 事件来交换身份验证代码(Auth code)以获取访问令牌(Access Token)。Vittorio 写了一篇博客文章概述了整个流程。

以下是一个示例,展示了如何在 Startup.Auth.cs 中设置此项配置,示例源码存放于GitHub 上的这个应用中

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ClientId = clientId,
        Authority = Authority,
        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            AuthorizationCodeReceived = (context) =>
           {
               var code = context.Code;
               ClientCredential credential = new ClientCredential(clientId, appKey);
               string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
               string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
               AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantID), new EFADALTokenCache(signedInUserID));
               AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                           code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceID);

               return Task.FromResult(0);
            },
            ...
    }

注意: AuthorizationCodeReceived 事件只在授权实际发生时调用一次。如果授权码已经生成并存储,此事件不会被调用。您需要注销或清除 cookies 才能强制触发此事件。


5
谢谢,明白了。然而,在我的情况下AuthorizationCodeReceived从未触发过...我想我必须找到一种方法将授权码从SPA传递到Web服务端... - Dunken

5

现在已经内置于Microsoft.Owin 4.1.0或更高版本。您可以使用SaveTokens使id_token可用,然后使用RedeemCode获取访问令牌并使其可用。

       app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = authority,
                    PostLogoutRedirectUri = postLogoutRedirectUri,
                    ClientSecret = "redacted",
                    RedirectUri = postLogoutRedirectUri,
                    //This allows multitenant
                    //https://github.com/Azure-Samples/guidance-identity-management-for-multitenant-apps/blob/master/docs/03-authentication.md
                    TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = false
                    },

                    Notifications = new OpenIdConnectAuthenticationNotifications()
                    {
                        AuthenticationFailed = (context) =>
                        {
                            return Task.FromResult(0);
                        }
                    },
                    SaveTokens = true,
                    // Required for the authorization code flow to exchange for tokens automatically
                    // using this means you will need to provide RedirectUri and ClientSecret
                    RedeemCode = true
                }
                );

您可以通过HttpContext对象访问这些令牌。

var result = Request.GetOwinContext().Authentication.AuthenticateAsync("Cookies").GetAwaiter().GetResult();
string idToken = result.Properties.Dictionary["id_token"];
string accessToken = result.Properties.Dictionary["access_token"];

参考来源: 如何使用 Owin 和 Mvc 5 从 HttpContext 获取访问令牌


首先,对于这个解决方案要点赞。这在任何微软文档中都没有提到,但却非常有效。现在我能够从“GetOwinContext().Authentication.AuthenticateAsync("Cookies")”获取access_token、id_token和refresh_token了,但问题是如何从同一属性中获取access_token的发行和过期时间?ExpireUtC给出了Id token的时间……但是否有任何代码可以明确地给出access_token的过期时间? - Mahajan344

1

您需要绕过默认的OWIN验证以进行自定义授权:

           new OpenIdConnectAuthenticationOptions
            {
                ...,
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateIssuer = false
                },

0
TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = ClaimTypes.Role },

这行代码解决了我的问题。我们需要验证发行人为假。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接