Outlook 日历 .Net Core Web API GetList(tokenCredential 参数不能为空。(参数 'tokenCredential'))

2024-02-01

我有一个与 Outlook 日历集成相关的 API 项目。通过此项目,您可以访问您的 Outlook 帐户。 我想在访问日历部分后列出所有事件。我看了你的文字记录,但有些地方我不明白。我怎样才能得到这个列表? 这份文件但我不明白:

appsettings.json(授权)

  {
   "AzureAd": {
   "Instance": "https://login.microsoftonline.com/",
   "Domain": "outlook.com.tr",
   "TenantId": "*********************************",
   "ClientId": "*********************************"
  },
   "Logging": {
   "LogLevel": {
   "Default": "Information",
   "Microsoft": "Warning",
   "Microsoft.Hosting.Lifetime": "Information"
    }
  },
 "AllowedHosts": "*"

}

启动.cs

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Threading.Tasks;
  using Microsoft.AspNetCore.Authentication.JwtBearer;
  using Microsoft.AspNetCore.Builder;
  using Microsoft.AspNetCore.Hosting;
  using Microsoft.AspNetCore.HttpsPolicy;
  using Microsoft.AspNetCore.Mvc;
  using Microsoft.Extensions.Configuration;
  using Microsoft.Extensions.DependencyInjection;
  using Microsoft.Extensions.Hosting;
  using Microsoft.Extensions.Logging;
  using NSwag;
  using NSwag.AspNetCore;
  using NSwag.Generation.Processors.Security;

namespace EvetOutlookAPI
{
   public class Startup
   {
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the 
     container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        // Enable JWT Bearer Authentication
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(options =>
        {
            Configuration.Bind("AzureAd", options);
            // Authority will be Your AzureAd Instance and Tenant Id
            options.Authority = $"{Configuration["AzureAd:Instance"]}{Configuration["AzureAd:TenantId"]}/v2.0";

            // The valid audiences are both the Client ID(options.Audience) and api://{ClientID}
            options.TokenValidationParameters.ValidAudiences = new string[] { Configuration["AzureAd:ClientId"], $"api://{Configuration["AzureAd:ClientId"]}" };
        });

        AddSwagger(services);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });

        // Add Swagger UI
        app.UseOpenApi();
        app.UseSwaggerUi3(settings =>
        {
            settings.OAuth2Client = new OAuth2ClientSettings
            {
                // Use the same client id as your application.
                // Alternatively you can register another application in the portal and use that as client id
                // Doing that you will have to create a client secret to access that application and get into space of secret management
                // This makes it easier to access the application and grab a token on behalf of user
                ClientId = Configuration["AzureAd:ClientId"],
                AppName = "Swagger-UI-Client",
            };
        });
    }

    /// <summary>
    /// Function to Generate Swagger UI Document and authenticate Swagger UI against the Azure Ad Application
    /// </summary>
    /// <param name="services"></param>
    private void AddSwagger(IServiceCollection services)
    {
        services.AddOpenApiDocument(document =>
        {
            document.AddSecurity("bearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme
            {
                Type = OpenApiSecuritySchemeType.OAuth2,
                Description = "Azure AAD Authentication",
                Flow = OpenApiOAuth2Flow.Implicit,
                Flows = new OpenApiOAuthFlows()
                {
                    Implicit = new OpenApiOAuthFlow()
                    {
                        Scopes = new Dictionary<string, string>
                    {
                        { $"api://{Configuration["AzureAd:ClientId"]}/Calendars.ReadWrite", "Access Application" },
                    },
                        AuthorizationUrl = $"{Configuration["AzureAd:Instance"]}{Configuration["AzureAd:TenantId"]}/oauth2/v2.0/authorize",
                        TokenUrl = $"{Configuration["AzureAd:Instance"]}{Configuration["AzureAd:TenantId"]}/oauth2/v2.0/token",
                    },
                },
            });

            // To add bearer token in request to APIs with Authorize attribute
            document.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("bearer"));
        });
    }
}

}

我使用 Microsoft.Graph 库来执行这些操作。

这就是我想列出的部分。

日历控制器

    [Authorize]
    [HttpGet("api/Get/Calendar")]
    public async Task<IActionResult> Get()
    {
        GraphServiceClient graphClient = new GraphServiceClient(tokenCredential: null);

        var calendars = await graphClient.Me.Calendars
                .Request()
                .GetAsync();

        return (IActionResult) calendars;
      }
    }

我想知道的是:如何创建一个从 Outlook 返回日历列表的端点?


为了设置 Swagger 进行身份验证,我遵循这个博客 https://www.c-sharpcorner.com/article/enable-oauth-2-authorization-using-azure-ad-and-swagger-in-net-5-0/并在我这边成功设置,这是我的代码,我安装了<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />使用 Swagger。

using Microsoft.Identity.Web;

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(opts =>
    {
        opts.Filters.Add(typeof(SampleActionFilter));
    });
    services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");
    services.AddSwaggerGen(c =>
    {
        c.OperationFilter<CustomHeaderSwaggerAttribute>();
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
        c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
        {
            Type = SecuritySchemeType.OAuth2,
            Flows = new OpenApiOAuthFlows()
            {
                Implicit = new OpenApiOAuthFlow()
                {
                    AuthorizationUrl = new Uri(Configuration["AuthorizationUrl"]),
                    TokenUrl = new Uri(Configuration["TokenUrl"]),
                    Scopes = new Dictionary<string, string> {
                {
                    Configuration["ApiScope"], "read the api"
                }
            }
                }
            }
        });
        c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
                {
                    new OpenApiSecurityScheme {
                        Reference = new OpenApiReference {
                                Type = ReferenceType.SecurityScheme,
                                    Id = "oauth2"
                            },
                            Scheme = "oauth2",
                            Name = "oauth2",
                            In = ParameterLocation.Header
                    },
                    new List < string > ()
                }
            });
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApi v1");
            c.OAuthClientId(Configuration["OpenIdClientId"]);
            c.OAuthUseBasicAuthenticationWithAccessCodeGrant();
        });
    }
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}


"AuthorizationUrl": "https://login.microsoftonline.com/tenant_name.onmicrosoft.com/oauth2/v2.0/authorize",
"TokenUrl": "https://login.microsoftonline.com/tenant_name.onmicrosoft.com/oauth2/v2.0/token",
"ApiScope": "api://clientid_of_the_app_exposed_api/User.Read",
"OpenIdClientId": "azure_ad_clientid",
"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "azure_ad_clientid",
    "Domain": "tenant_name.onmicrosoft.com",
    "TenantId": "common",
    "Audience": "clientid_of_the_app_exposed_api"
}

=================================================== =======

以我的拙见,您的要求应该是在您的项目中创建一个 api,然后当用户调用此 api 时,该 api 将代表用户调用 ms graph api 来获取日历列表。您提供的代码和屏幕截图主要与用于测试的 swagger 相关,但我认为我们最好先让您的 api 工作。

这是我的 api 方法,当我使用令牌向该 api 发送请求时,该令牌可用于调用图形 api 来获取所有日历,它可以为我工作。

public class CalendarController : ControllerBase
{
    [HttpGet]
    //[Authorize]
    //[RequiredScope("Calendars.ReadWrite")]
    public async Task<IUserCalendarsCollectionPage> Get()
    {
        string header = HttpContext.Request.Headers["Authorization"];
        string token = header.Remove(0,7);
        GraphServiceClient graphClient = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(async (requestMessage) =>
        {
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
        }));
        var calendars = await graphClient.Me.Calendars
                .Request()
                .GetAsync();
        return calendars;
                }
}

那么如果你想用Swagger测试这个api,我还没有完成那部分。

接下来,我认为这样做不太好...通常,当我们托管一个用于调用图api的api时,我们应该使用代表流 https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow,这意味着您需要在azure ad中公开一个api,然后您可以向您的控制器发送带有合法访问令牌的请求,然后该令牌将被使用并用于通过代表流生成另一个访问令牌来调用图形 API。看起来会带来很多麻烦...

另一种方法是你有自己的身份验证方法,它将验证传入的请求是否合法,如果合法,然后解码传入的令牌以获取原理并获取使用id,然后你可以使用客户端凭证流 https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow生成访问令牌以使用用户 ID 单独调用图形 api。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Outlook 日历 .Net Core Web API GetList(tokenCredential 参数不能为空。(参数 'tokenCredential')) 的相关文章

随机推荐