Asp.NET 核心 OpenIddict invalid_grant
Asp.NET Core OpenIddict invalid_grant
第一件事:我知道这个问题很大post,但是我跟踪这个问题已经好几个星期了,我收集了很多信息可能是什么问题的根源。
我正在使用带有 OpenIddict 身份验证的 Angular2 应用程序。我在客户端应用程序上得到 access_token、refresh_token。我可以使用 refresh_token 获取新的 access_token,一切正常。差不多。
有时我从服务器收到错误响应:
POST https://mydomain:2000/api/authorization/token 400 (Bad Request)
和响应:
error:"invalid_grant"
error_description:"Invalid ticket"
我检查了所有内容,refresh_token 我发送的是正确的。
关于设计:
在我向服务器发出请求之前,我检查 access_token 是否过期。如果过期,我会发送请求以使用 refresh_token 获取新的 access_token。
它适用于随机时间,但在某些随机时间(重复)refresh_token 变得无效。
我虽然和AddEphemeralSigningKey
有关,但我把它改成了AddSigningCertificate
。 (详情在 线程中。)
我认为,IIS 在输入一段时间后会杀死 Kestrel-activity。我的应用程序池配置是:
StartMode: OnDemand
Idle Time-out (minutes): 20
Idle Time-out (action): Terminate
我怀疑在发出新请求后,OpenIddict 错误地解密了 refresh_token,因为 Kestrel 已重新启动?还是我错了?
我还检查了 OpenIddict 表和 OpenIddictApplications、OpenIddictAuthorizations 和 OpenIddictScopes 都是空的。只有 OpenIddictTokens 包含一些数据(并且都是类型 refresh_token):
我希望 refresh_token 保存在某个地方。 Where? 也许这是源问题,为什么我的 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());
}
}
控制台截图:
更新:
最后我所要做的就是:
services.AddDataProtection()
.SetApplicationName(this.Configuration["Authentication:ApplicationId"])
.PersistKeysToFileSystem(new DirectoryInfo(this.Configuration["Directories:Keys"]));
不要忘记为 Directories:Keys 文件夹添加 IIS 权限。
I would expect, that refresh_tokens are saved somewhere. Where?
无处可去。 OpenIddict 颁发的授权代码、刷新令牌和访问令牌(使用默认格式时)是 独立的 并且出于安全原因从不存储(仅元数据,如主题或授权标识符与令牌关联的是)。
您看到的问题可能是由于您没有配置您的环境以正确保存 ASP.NET 核心数据保护堆栈 OpenIddict 所依赖的加密其令牌所使用的加密密钥。您可以阅读 以了解有关如何解决该问题的更多信息。
第一件事:我知道这个问题很大post,但是我跟踪这个问题已经好几个星期了,我收集了很多信息可能是什么问题的根源。
我正在使用带有 OpenIddict 身份验证的 Angular2 应用程序。我在客户端应用程序上得到 access_token、refresh_token。我可以使用 refresh_token 获取新的 access_token,一切正常。差不多。
有时我从服务器收到错误响应:
POST https://mydomain:2000/api/authorization/token 400 (Bad Request)
和响应:
error:"invalid_grant"
error_description:"Invalid ticket"
我检查了所有内容,refresh_token 我发送的是正确的。
关于设计:
在我向服务器发出请求之前,我检查 access_token 是否过期。如果过期,我会发送请求以使用 refresh_token 获取新的 access_token。
它适用于随机时间,但在某些随机时间(重复)refresh_token 变得无效。
我虽然和AddEphemeralSigningKey
有关,但我把它改成了AddSigningCertificate
。 (详情在
我认为,IIS 在输入一段时间后会杀死 Kestrel-activity。我的应用程序池配置是:
StartMode: OnDemand
Idle Time-out (minutes): 20
Idle Time-out (action): Terminate
我怀疑在发出新请求后,OpenIddict 错误地解密了 refresh_token,因为 Kestrel 已重新启动?还是我错了?
我还检查了 OpenIddict 表和 OpenIddictApplications、OpenIddictAuthorizations 和 OpenIddictScopes 都是空的。只有 OpenIddictTokens 包含一些数据(并且都是类型 refresh_token):
我希望 refresh_token 保存在某个地方。 Where? 也许这是源问题,为什么我的 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());
}
}
控制台截图:
更新:
最后我所要做的就是:
services.AddDataProtection()
.SetApplicationName(this.Configuration["Authentication:ApplicationId"])
.PersistKeysToFileSystem(new DirectoryInfo(this.Configuration["Directories:Keys"]));
不要忘记为 Directories:Keys 文件夹添加 IIS 权限。
I would expect, that refresh_tokens are saved somewhere. Where?
无处可去。 OpenIddict 颁发的授权代码、刷新令牌和访问令牌(使用默认格式时)是 独立的 并且出于安全原因从不存储(仅元数据,如主题或授权标识符与令牌关联的是)。
您看到的问题可能是由于您没有配置您的环境以正确保存 ASP.NET 核心数据保护堆栈 OpenIddict 所依赖的加密其令牌所使用的加密密钥。您可以阅读