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

Posted

技术标签:

【中文标题】ASP.NET Web API 和 OpenID Connect:如何从授权码中获取访问令牌【英文标题】:ASP.NET Web API and OpenID Connect: how to get Access Token from Authorization Code 【发布时间】:2014-10-05 17:32:26 【问题描述】:

我试图让 OpenID Connect 运行...我的 Web API 用户设法获得了 OpenID Connect 提供者的授权码。我应该如何将此代码传递给我的 ASP.NET Web API?如何配置 OWIN 中间件,以便我可以使用授权码获取访问令牌?

更新: SPA 使用 AJAX 与我的 Web 服务 (ASP.NET Web API) 进行通信。在我的网络服务中使用 OWIN 中间件。我将 OpenIDConnect 设置为身份验证机制。第一次调用 Web 服务时,它成功地将用户重定向到 OpenID Connect Provider 的登录页面。结果,用户可以登录并获得授权码。 AFAIK 现在可以(由我的 Web 服务)将此代码用于访问令牌。但是,我不知道如何将此代码返回到我的 Web 服务(这是使用标头完成的吗?),然后如何配置以获取访问令牌。我想我可以手动调用令牌端点,但我想改用 OWIN 组件。

【问题讨论】:

我不清楚你想要完成什么。您是在询问 Web API 的 OWIN 中间件吗?还是某个调用 API 的客户端应用? 好的,我刚刚更新了我的问题。 【参考方案1】:

看起来推荐的方法是使用AuthorizationCodeReceived 事件将身份验证代码交换为访问令牌。 Vittorio has a blog entry 概述了整个流程。

这是一个来自this sample app on GitHub 的Startup.Auth.cs 代码示例:

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 事件只被调用一次。如果已经生成并存储了验证码,则不会调用此事件。您必须注销或清除 cookie 才能强制执行此事件。

【讨论】:

谢谢,有道理。但是,在我的情况下,AuthorizationCodeReceived 永远不会触发......我想我必须以某种方式将授权码从 SPA 传递到网络服务......【参考方案2】:

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 事件不会因为任何页面重新加载而被触发,只要 authenticationCode 未过期并存储在会话中。

你需要做的是调用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 )。如果令牌过期,会引发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,它返回了一个错误令牌 @HiepLam,您需要先调用 AcquireTokenByAuthorizationCodeAsync(),如示例所示。调用 AcquireTokenSilentAsync() 是为了在请求处理期间进行进一步调用,因此您不存储“授权代码”。但是,也有可能是您没有对该资源的权限,因此例外。 但是它需要通过验证然后认证服务器发回authorization code 对(OnOpenIdAuthorizationCodeReceived OWIN 管道上的事件)?那么,当代码过期时,我如何获得另一个 authorization code @HiepLam,您不必存储authorization code。调用 AcquireTokenByAuthorizationCodeAsync() 会将其放入内部缓存中,进一步调用 AcquireTokenSilentAsync() 在内部使用此缓存(因此您不必自己存储它)。 我理解你的想法。第一次,我在AuthorizationCodeReceived 事件中打电话给AcquireTokenByAuthorizationCodeAsync,一切正常。一段时间后,authorization code 过期,调用AcquireTokenSilentAsync 时抛出异常。所以当时我想知道如何获得一个新的authorization code。或者有其他方法可以解决这个问题吗?【参考方案3】:

你需要绕过默认的owin验证来做自定义授权:

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

【讨论】:

【参考方案4】:
TokenValidationParameters = new TokenValidationParameters  NameClaimType = "name", RoleClaimType = ClaimTypes.Role ,

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

【讨论】:

以上是关于ASP.NET Web API 和 OpenID Connect:如何从授权码中获取访问令牌的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net Core API OpenId-Connect 身份验证与 JWT 令牌使用 IdentityModel

如何通过 IdentityServer4 将 OpenId Connect 添加到 ASP.NET Core 服务器端 Blazor Web 应用程序?

Asp.Net Web API 2第六课——Web API路由和动作选择

Asp.Net Web API 2第十三课——ASP.NET Web API中的JSON和XML序列化

Web API1.1 ASP.NET Web API入门

如何在 asp.net mvc4 中将 OpenID 迁移到 OAuth