OpenID Connect AllowedRedirectUris 为空,导致 IdentityServer4 上的 "invalid redirect_uri"
OpenID Connect AllowedRedirectUris empty resulting in "invalid redirect_uri" on IdentityServer4
我在 .NET Core 项目上有一个 运行 IdentityServer4,我正在尝试使现有的 .NET Framework Web 应用程序成为该服务器的客户端。
该项目正在使用 Microsoft.Owin.Security
,我在 Startup.cs
中设置了身份验证设置。
然而,当请求发送到 IdentityServer 时,AllowedRedirectUris
参数只是一个空数组,因此 RedirectUri
参数与该空数组中的参数不匹配,导致 invalid redirect_uri
错误。
这是现有项目的Startup.cs
:
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
namespace MyProject.Services
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
RequireHttpsMetadata = false,
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RedirectUri = "http://localhost:8010/myproject.services/api/oidc",
PostLogoutRedirectUri = "http://localhost:8010/myproject.application",
ClientId = "CLIENT1",
ClientSecret = "a-local-testing-password",
Scope = "CLIENT1 offline_access",
ResponseType = "code id_token"
});
}
}
}
这是从日志记录中找到的配置生成的请求:
IdentityServer4.Endpoints.AuthorizeEndpoint[0]
{
"ClientId": "CLIENT1",
"ClientName": "CLIENT1_name",
"RedirectUri": "http://localhost:8010/myproject.services/api/oidc",
"AllowedRedirectUris": [], <-- is empty, but should contain the RedirectUri
"SubjectId": "anonymous",
"RequestedScopes": "",
"Raw": {
"client_id": "CLIENT1",
"redirect_uri": "http://localhost:8010/myproject.services/api/oidc",
"response_mode": "form_post",
"response_type": "code id_token",
"scope": "openid profile",
"state": "OpenIdConnect.AuthenticationProperties=[...]",
"nonce": "[...]",
"x-client-SKU": "ID_NET451",
"x-client-ver": "5.2.1.0"
}
}
那么有人知道我如何让 AllowedRedirectUris
包含给定的RedirectUri
以便我的 IdentityServer4 会接受请求吗?
更新:这是IdentityServer4项目中的客户端对象:
new IdentityServer4.Models.Client
{
ClientId = "CLIENT1",
ClientName = "CLIENT1-name",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new IdentityServer4.Models.Secret("a-local-testing-password".Sha256())
},
RedirectUris = { "http://localhost:8010/myproject.services/api/oidc" },
PostLogoutRedirectUris = { "http://localhost:8010/myproject.application" },
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = {
"CLIENT1"
},
Properties = new Dictionary<string, string>
{
{ "TenantName", "CLIENT1" },
{ "ImageUrl", "/Images/CLIENT1.png" },
},
IdentityProviderRestrictions = {
"LocalOnly"
},
AllowOfflineAccess = true
},
Update2:这是 IdentityServer4 的 Startup.cs
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MyProject.STS4.ManagementSite.Data;
using MyProject.STS4.ManagementSite.Models;
using MyProject.STS4.ManagementSite.Services;
using MyProject.STS4.ManagementSite.Tenant;
using IdentityServer4.Services;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Reflection;
using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Mappers;
using Microsoft.IdentityModel.Tokens;
using IdentityServer4;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Globalization;
using System.Security.Claims;
using MyProject.STS4.ManagementSite.Models.Mail;
namespace MyProject.STS4.ManagementSite
{
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)
{
// Read this part of the appsettings.json and convert it into the SmtpConfig
services.Configure<SmtpConfig>(Configuration.GetSection("Smtp"));
string connectionString = Configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)
));
// Explicitly added
//services.TryAddScoped<IUserClaimsPrincipalFactory<TenantUser>, UserClaimsPrincipalFactory<TenantUser, IdentityRole>>();
services.AddIdentity<TenantUser, IdentityRole>(options =>
{
// This should send a mail when registering
options.SignIn.RequireConfirmedEmail = false;
options.User.RequireUniqueEmail = false;
// Allows emails toe be used in the username
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddUserValidator<TenantUserValidator>(); // register our own validator
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddAspNetIdentity<TenantUser>()
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
});
// Add the possible authentication methods
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "[...]";
options.ClientSecret = "[...]";
})
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "http://localhost:5000/";
options.RequireHttpsMetadata = false;
options.ClientId = "implicit";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
// This is still to test Azure Auth
//.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
//{
// ClientId = Configuration["AzureAD:ClientId"],
// Authority = string.Format(CultureInfo.InvariantCulture, Configuration["AzureAd:AadInstance"], "common", "/v2.0"),
// ResponseType = OpenIdConnectResponseType.IdToken,
// Events = new OpenIdConnectEvents
// {
// OnTokenValidated = TokenValidated
// },
// TokenValidationParameters = new TokenValidationParameters
// {
// // Instead of using the default validation (validating against
// // a single issuer value, as we do in line of business apps),
// // we inject our own multitenant validation logic
// ValidateIssuer = false,
// NameClaimType = "name"
// }
//}
//);
// Registering this service allows for checking if user is active and enhancing the user with extra data (such as the claims)
services.AddTransient<IProfileService, TenantUserProfileService>();
}
private Task TokenValidated(TokenValidatedContext context)
{
/* ---------------------
// Replace this with your logic to validate the issuer/tenant
---------------------
// Retriever caller data from the incoming principal
string issuer = context.SecurityToken.Issuer;
string subject = context.SecurityToken.Subject;
string tenantID = context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
// Build a dictionary of approved tenants
IEnumerable<string> approvedTenantIds = new List<string>
{
"<Your tenantID>",
"9188040d-6c67-4c5b-b112-36a304b66dad" // MSA Tenant
};
o
if (!approvedTenantIds.Contains(tenantID))
throw new SecurityTokenValidationException();
--------------------- */
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));
return Task.FromResult(0);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// this will do the initial DB population
SeedData.EnsureSeedData(app.ApplicationServices);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// app.UseAuthentication(); // not needed, since UseIdentityServer adds the authentication middleware
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
IdentityServer4 项目创建了数据库,但配置使服务器使用内存数据而不是数据库数据。所以内存中的数据没有更新值...
我在 .NET Core 项目上有一个 运行 IdentityServer4,我正在尝试使现有的 .NET Framework Web 应用程序成为该服务器的客户端。
该项目正在使用 Microsoft.Owin.Security
,我在 Startup.cs
中设置了身份验证设置。
然而,当请求发送到 IdentityServer 时,AllowedRedirectUris
参数只是一个空数组,因此 RedirectUri
参数与该空数组中的参数不匹配,导致 invalid redirect_uri
错误。
这是现有项目的Startup.cs
:
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
namespace MyProject.Services
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
RequireHttpsMetadata = false,
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RedirectUri = "http://localhost:8010/myproject.services/api/oidc",
PostLogoutRedirectUri = "http://localhost:8010/myproject.application",
ClientId = "CLIENT1",
ClientSecret = "a-local-testing-password",
Scope = "CLIENT1 offline_access",
ResponseType = "code id_token"
});
}
}
}
这是从日志记录中找到的配置生成的请求:
IdentityServer4.Endpoints.AuthorizeEndpoint[0]
{
"ClientId": "CLIENT1",
"ClientName": "CLIENT1_name",
"RedirectUri": "http://localhost:8010/myproject.services/api/oidc",
"AllowedRedirectUris": [], <-- is empty, but should contain the RedirectUri
"SubjectId": "anonymous",
"RequestedScopes": "",
"Raw": {
"client_id": "CLIENT1",
"redirect_uri": "http://localhost:8010/myproject.services/api/oidc",
"response_mode": "form_post",
"response_type": "code id_token",
"scope": "openid profile",
"state": "OpenIdConnect.AuthenticationProperties=[...]",
"nonce": "[...]",
"x-client-SKU": "ID_NET451",
"x-client-ver": "5.2.1.0"
}
}
那么有人知道我如何让 AllowedRedirectUris
包含给定的RedirectUri
以便我的 IdentityServer4 会接受请求吗?
更新:这是IdentityServer4项目中的客户端对象:
new IdentityServer4.Models.Client
{
ClientId = "CLIENT1",
ClientName = "CLIENT1-name",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new IdentityServer4.Models.Secret("a-local-testing-password".Sha256())
},
RedirectUris = { "http://localhost:8010/myproject.services/api/oidc" },
PostLogoutRedirectUris = { "http://localhost:8010/myproject.application" },
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = {
"CLIENT1"
},
Properties = new Dictionary<string, string>
{
{ "TenantName", "CLIENT1" },
{ "ImageUrl", "/Images/CLIENT1.png" },
},
IdentityProviderRestrictions = {
"LocalOnly"
},
AllowOfflineAccess = true
},
Update2:这是 IdentityServer4 的 Startup.cs
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MyProject.STS4.ManagementSite.Data;
using MyProject.STS4.ManagementSite.Models;
using MyProject.STS4.ManagementSite.Services;
using MyProject.STS4.ManagementSite.Tenant;
using IdentityServer4.Services;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Reflection;
using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Mappers;
using Microsoft.IdentityModel.Tokens;
using IdentityServer4;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Globalization;
using System.Security.Claims;
using MyProject.STS4.ManagementSite.Models.Mail;
namespace MyProject.STS4.ManagementSite
{
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)
{
// Read this part of the appsettings.json and convert it into the SmtpConfig
services.Configure<SmtpConfig>(Configuration.GetSection("Smtp"));
string connectionString = Configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)
));
// Explicitly added
//services.TryAddScoped<IUserClaimsPrincipalFactory<TenantUser>, UserClaimsPrincipalFactory<TenantUser, IdentityRole>>();
services.AddIdentity<TenantUser, IdentityRole>(options =>
{
// This should send a mail when registering
options.SignIn.RequireConfirmedEmail = false;
options.User.RequireUniqueEmail = false;
// Allows emails toe be used in the username
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddUserValidator<TenantUserValidator>(); // register our own validator
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddAspNetIdentity<TenantUser>()
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
});
// Add the possible authentication methods
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "[...]";
options.ClientSecret = "[...]";
})
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "http://localhost:5000/";
options.RequireHttpsMetadata = false;
options.ClientId = "implicit";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
// This is still to test Azure Auth
//.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
//{
// ClientId = Configuration["AzureAD:ClientId"],
// Authority = string.Format(CultureInfo.InvariantCulture, Configuration["AzureAd:AadInstance"], "common", "/v2.0"),
// ResponseType = OpenIdConnectResponseType.IdToken,
// Events = new OpenIdConnectEvents
// {
// OnTokenValidated = TokenValidated
// },
// TokenValidationParameters = new TokenValidationParameters
// {
// // Instead of using the default validation (validating against
// // a single issuer value, as we do in line of business apps),
// // we inject our own multitenant validation logic
// ValidateIssuer = false,
// NameClaimType = "name"
// }
//}
//);
// Registering this service allows for checking if user is active and enhancing the user with extra data (such as the claims)
services.AddTransient<IProfileService, TenantUserProfileService>();
}
private Task TokenValidated(TokenValidatedContext context)
{
/* ---------------------
// Replace this with your logic to validate the issuer/tenant
---------------------
// Retriever caller data from the incoming principal
string issuer = context.SecurityToken.Issuer;
string subject = context.SecurityToken.Subject;
string tenantID = context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
// Build a dictionary of approved tenants
IEnumerable<string> approvedTenantIds = new List<string>
{
"<Your tenantID>",
"9188040d-6c67-4c5b-b112-36a304b66dad" // MSA Tenant
};
o
if (!approvedTenantIds.Contains(tenantID))
throw new SecurityTokenValidationException();
--------------------- */
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));
return Task.FromResult(0);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// this will do the initial DB population
SeedData.EnsureSeedData(app.ApplicationServices);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// app.UseAuthentication(); // not needed, since UseIdentityServer adds the authentication middleware
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
IdentityServer4 项目创建了数据库,但配置使服务器使用内存数据而不是数据库数据。所以内存中的数据没有更新值...