Asp.NET Core OpenIddict invalid_grant

时间:2017-04-20 11:43:50

标签: openiddict

第一件事:我知道这是一个很大的帖子,但我会跟踪这个问题好几个星期,而且我收集了很多可能是问题根源的信息。 < /强>

我正在使用带有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(详情请见this帖子。)

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

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

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

我还检查了OpenIddict表和OpenIddictApplications,OpenIddictAuthorizations和OpenIddictScopes都是空的。只有OpenIddictTokens包含一些数据(并且都是Type 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());
    }
}

控制台截图: Getting refresh_token Sending refresh_token

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

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

不要忘记为目录添加IIS的权限:密钥文件夹。

1 个答案:

答案 0 :(得分:0)

  

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

<强>无处即可。 OpenIddict发布的授权码,刷新令牌和访问令牌(使用默认格式时)是自包含,并且出于安全原因从不存储(仅限主题之类的元数据或与之关联的授权标识符)令牌是)。

您看到的问题可能是由于您尚未将环境配置为正确保留ASP.NET核心数据保护堆栈使用的加密密钥这一事实OpenIddict依赖于加密其令牌。您可以阅读OpenIddict: 401 errors when two or more service instance count以获取有关如何解决该问题的更多信息。