首页 > 解决方案 > MSAL.Net 没有向 AcquireTokenSilent 调用传递帐户或登录提示

问题描述

我见过许多相同或相似的问题,如果有的话,我尝试了他们所有的答案,但这些都不适合我。

我使用Microsoft 的 Github 帐户中的这个示例作为我的项目基础。

它适用于仅登录用户。

该项目有 1 个 WebApi,1 个 Angular 应用程序。

然后我按照这个Microsoft 示例添加代码来调用 Graph API。这是控制器代码:

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class BillsController : ControllerBase
{
    static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
    readonly ITokenAcquisition tokenAcquisition;
    readonly WebOptions webOptions;

    public BillsController(ITokenAcquisition tokenAcquisition,
                          IOptions<WebOptions> webOptionValue)
    {
        this.tokenAcquisition = tokenAcquisition;
        this.webOptions = webOptionValue.Value;
    }

    [HttpGet]
    [AuthorizeForScopes(Scopes = new[] { Constants.ScopeUserRead, Constants.ScopeMailRead })]
    public async Task<IActionResult> Profile()
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

        var subject = string.Empty;
        try
        {
            // Initialize the GraphServiceClient. 
            Graph::GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ScopeUserRead, Constants.ScopeMailRead });

            var me = await graphClient.Me.Request().GetAsync();
            // Get user photo
            var messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().GetAsync();
            subject = messages.First().Subject;
            return Ok(subject);
        }
        catch (System.Exception ex)
        {
            throw ex;
        }
    }

    private Graph::GraphServiceClient GetGraphServiceClient(string[] scopes)
    {
        return GraphServiceClientFactory.GetAuthenticatedGraphClient(async () =>
        {
            string result = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
            return result;
        }, webOptions.GraphApiUrl);
    }
}

对于 Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        // Setting configuration for protected web api
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddProtectedWebApi(Configuration);

        services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { Constants.ScopeUserRead, Constants.ScopeMailRead })
            .AddInMemoryTokenCaches();

        services.AddOptions();
        services.AddGraphService(Configuration);

        // Creating policies that wraps the authorization requirements
        services.AddAuthorization();

        services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));

        services.AddControllers();

        // Allowing CORS for all domains and methods for the purpose of sample
        services.AddCors(o => o.AddPolicy("default", builder =>
        {
            builder.AllowAnyOrigin()
                   .AllowAnyMethod()
                   .AllowAnyHeader();
        }));
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            // Since IdentityModel version 5.2.1 (or since Microsoft.AspNetCore.Authentication.JwtBearer version 2.2.0),
            // Personal Identifiable Information is not written to the logs by default, to be compliant with GDPR.
            // For debugging/development purposes, one can enable additional detail in exceptions by setting IdentityModelEventSource.ShowPII to true.
            // Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
            app.UseDeveloperExceptionPage();
        }
        else
        {
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseExceptionHandler("/error");

        app.UseCors("default");
        app.UseHttpsRedirection();
        app.UseCookiePolicy();
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

在 Angular 应用程序上,我添加了一个按钮来调用此 Profile() 控制器操作。

todo-view.component.ts

  getEmails(): void {
    this.service.getEmails().subscribe({
      next: (emails: any) => {
        alert(emails);
      },
      error: (err: any) => {
        console.log("error happened~!");
        console.log(err);
      }
    });
  }

todo-view.component.html

<button (click)="getEmails();">Get Emails</button>

在此处输入图像描述


我将以下代码添加到我的 Startup.cs 中并删除了 AddWebAppCallsProtectedWebApi。services.AddProtectedWebApiCallsProtectedWebApi(配置).AddInMemoryTokenCaches();

现在它给我一个不同的错误信息:

在此处输入图像描述

标签: asp.net-coremicrosoft-graph-apimsalmicrosoft-identity-platform

解决方案


我在使用 React 应用时遇到了同样的问题。由于 AuthorizeForScopes 用于返回视图,因此不适用于 API 解决方案。我能够添加一些配置选项以使其正常工作。

我做的第一件事是使用 SQL 缓存。这有助于在站点重新启动时阻止“无登录提示”错误。之后,令牌将正常工作,直到超时,之后令牌将从缓存中删除并且错误将再次出现。

为此,我开始查看配置设置。我将配置更改为以下内容。

services
    .AddWebAppCallsProtectedWebApi(new string[] { "User.Read" }, idOps =>
    {
        Configuration.Bind("AzureAd", idOps);
        idOps.SaveTokens = true;
        idOps.RefreshOnIssuerKeyNotFound = true;
        idOps.SingletonTokenAcquisition = true;
        idOps.UseTokenLifetime = true;
    },
    ops => Configuration.Bind("AzureAd", ops))
    .AddDistributedTokenCaches()
    .AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = Configuration.GetConnectionString("Site_DbContext");
        options.SchemaName = "dbo";
        options.TableName = "_TokenCache";
    });

我没有做太多的测试来找出神奇的组合,但甜蜜点似乎是SingletonTokenAcquisition。有了这个集合,它的行为似乎就像一个混合缓存。首次设置时,它将令牌拉入内存并保存,因此如果将其从数据库缓存中删除,它仍然可以访问它。

刷新可能需要其他设置,但我还没有测试过。

我确实注意到的一件事是令牌在刷新之前不会被添加回 SQL 缓存,因此如果在删除令牌并且站点出现故障并清除内存的情况下发生某些事情,错误可能会再次出现,但这是最好的到目前为止我找到的解决方案。我能够让我的 SPA 运行 24 小时,并且仍然能够提取新数据。


推荐阅读