1

首先第一件事:我知道这是一个大帖子,但我跟踪这个问题几个星期,我收集了很多信息,可能是问题的根源。

我正在使用带有 OpenIddict 身份验证的 Angular2 应用程序。我在客户端应用程序上获得 access_token、refresh_token。我可以使用 refresh_token 来获取新的 access_token,一切正常。几乎。

在某些时候,我从服务器收到错误响应:

POST https://mydomain:2000/api/authorization/token 400(错误请求)

和回应:

error:"invalid_grant"
error_description:"Invalid ticket"

我对所有内容进行了三次检查,我发送的 refresh_token 是正确的。

关于设计:
在我向服务器发出请求之前,我检查 access_token 是否过期。如果过期,我发送请求以使用 refresh_token 获取新的 access_token。

它适用于随机时间,但在某些随机时间(重复) refresh_token 变得无效。
我虽然它与 . 有关系AddEphemeralSigningKey,但我将其更改为AddSigningCertificate. (详细信息在这个线程中。)

我认为,IIS 在一段时间不活动后会杀死 Kestrel。我的应用程序池配置是:

StartMode: OnDemand
Idle Time-out (minutes): 20
Idle Time-out (action): Terminate

我怀疑在发出新请求后,OpenIddict 错误地解密了 refresh_token,因为 Kestrel 已重新启动?还是我错了?

我还检查了 OpenIddict 表和 OpenIddictApplications,OpenIddictAuthorizations 和 OpenIddictScopes 都是空的。只有 OpenIddictTokens 包含一些数据(并且都是类型 refresh_token):

OpenIddictTokens

我希望 refresh_tokens 保存在某个地方。在哪里?也许这是源头问题,为什么我的 refresh_tokens 在一些随机时间后无效(可能是重新启动 Kestrel 时)。

IIS 日志:

Hosting environment: Production
Content root path: D:\Podatki\OpPISWeb\WWWProduction
Now listening on: http://localhost:1408
Application started. Press Ctrl+C to shut down.
fail: AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware[0]
      The token request was rejected because the authorization code or the refresh token was invalid.
fail: AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware[0]
      The token request was rejected because the authorization code or the refresh token was invalid.

这是我的 Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    try
    {
        services.Configure<IISOptions>(options =>
        {
        });

        services.AddMvc();
        services.AddMvcCore().AddDataAnnotations();

        services.AddEntityFrameworkSqlServer();

        services.AddScoped<UserStore<AppUser, AppRole, AppDbContext, int, AppUserClaim, AppUserRole, AppUserLogin, AppUserToken, AppRoleClaim>, AppUserStore>();
        services.AddScoped<UserManager<AppUser>, AppUserManager>();
        services.AddScoped<RoleManager<AppRole>, AppRoleManager>();
        services.AddScoped<SignInManager<AppUser>, AppSignInManager>();
        services.AddScoped<RoleStore<AppRole, AppDbContext, int, AppUserRole, AppRoleClaim>, AppRoleStore>();

        var connection = Configuration["ConnectionStrings:Web"];
        services.AddDbContext<AppDbContext>(options =>
        {
            options.UseSqlServer(connection);
            options.UseOpenIddict<int>();
            if (this.env.IsDevelopment())
                options.EnableSensitiveDataLogging();
        });


        services
            .AddIdentity<AppUser, AppRole>()
            .AddUserStore<AppUserStore>()
            .AddUserManager<AppUserManager>()
            .AddRoleStore<AppRoleStore>()
            .AddRoleManager<AppRoleManager>()
            .AddSignInManager<AppSignInManager>()
            .AddDefaultTokenProviders();

        services.Configure<IdentityOptions>(options =>
            {
                options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
                options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
                options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
            });

        services.AddOpenIddict<int>(options =>
        {
            options.AddEntityFrameworkCoreStores<AppDbContext>();
            options.AddMvcBinders();
            options.EnableTokenEndpoint("/API/authorization/token");
            options.AllowPasswordFlow();
            options.AllowRefreshTokenFlow();
            options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:google_identity_token");
            options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:logedin");
            options.UseJsonWebTokens();
            if (this.env.IsDevelopment())
                options.AddEphemeralSigningKey();  
            else
                options.AddSigningCertificate(new FileStream(
                    Directory.GetCurrentDirectory() + "/Resources/cert.pfx", FileMode.Open), "password");
            options.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
            options.SetRefreshTokenLifetime(TimeSpan.FromDays(14));
            if (this.env.IsDevelopment())
                options.DisableHttpsRequirement();
        });

        services.AddSingleton<DbSeeder>();
        services.AddSingleton<IConfiguration>(c => { return Configuration; });

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
        throw;
    }
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, DbSeeder dbSeeder)
{
    loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
        {
            HotModuleReplacement = true
        });
    }
    app.UseStaticFiles();

    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(this.Configuration["Directories:Upload"]),
        RequestPath = new PathString("/Files")
    });

    app.UseOpenIddict();

    var JwtOptions = new JwtBearerOptions()
    {
        Authority = this.Configuration["Authentication:OpenIddict:Authority"],
        Audience = "OpPISWeb",
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,

        RequireHttpsMetadata = false
    };
    JwtOptions.RequireHttpsMetadata = !env.IsDevelopment();
    app.UseJwtBearerAuthentication(JwtOptions);

    app.UseMvc();

    using (var context = new AppDbContext(this.Configuration))
    {
        context.Database.Migrate();
    }
    try
    {
        dbSeeder.SeedAsync();
    }
    catch (AggregateException e)
    {
        throw new Exception(e.ToString());
    }
}

控制台截图: 获取 refresh_token 发送 refresh_token

更新:
最后我要做的就是:

services.AddDataProtection()
                    .SetApplicationName(this.Configuration["Authentication:ApplicationId"])
                    .PersistKeysToFileSystem(new DirectoryInfo(this.Configuration["Directories:Keys"]));

不要忘记为 Directory:Keys 文件夹添加 IIS 权限。

4

1 回答 1

1

我希望 refresh_tokens 保存在某个地方。在哪里?

无处。OpenIddict 发布的授权代码、刷新令牌和访问令牌(使用默认格式时)是自包含的,出于安全原因从不存储(只有与令牌关联的主题或授权标识符等元数据)。

您看到的问题可能是由于您没有将环境配置为正确保留 ASP.NET Core 数据保护堆栈 OpenIddict 所使用的加密密钥来加密其令牌。您可以阅读OpenIddict: 401 errors when two or more service instance count有关如何解决该问题的更多信息。

于 2017-04-20T12:15:07.473 回答