【发布时间】:2020-12-10 15:57:17
【问题描述】:
我正在使用 IdentityServer4 NuGet 包在我的 asp.net 核心服务器应用程序中设置 IdentityServer。 同一个应用程序托管一个受保护的 API,在通过服务器应用程序的 IdentityServer 部分进行身份验证后,应该可以通过 REST 客户端访问该 API。 我在服务器上的本地认证/授权有效。客户端也能够从服务器获取有效令牌。但是在服务器上调用 API 时出现“未授权”错误,并且 API 返回登录表单。 有没有人观察到同样的情况?出了什么问题?
运行 IdentityServer 和 API 的 ServerApp
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole().AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Information);
loggingBuilder.AddDebug();
});
// For Entity Framework
services.AddDbContextPool<ApplicationDbContext>(options =>
{
options.UseLazyLoadingProxies()
.UseMySql(Configuration.GetConnectionString("ConnStr"), MySQLServerVersion.ServerVersion);
options.EnableSensitiveDataLogging();
});
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//.AddDefaultUI();
services.AddTransient<IEmailSender, EmailSender>();
services.AddControllersWithViews();
services.AddRazorPages().AddRazorRuntimeCompilation();
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// configure identity server with mysql stores, keys, clients and scopes
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddAspNetIdentity<ApplicationUser>()
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b =>
b.UseMySql(Configuration.GetConnectionString("ConnStr"), MySQLServerVersion.ServerVersion,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b =>
b.UseMySql(Configuration.GetConnectionString("ConnStr"), MySQLServerVersion.ServerVersion,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
})
.AddJwtBearerClientAuthentication()
.AddProfileService<ProfileService>();
services.AddLocalApiAuthentication();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// base-address of your identityserver
options.Authority = "https://localhost:5001";
// name of the API resource
options.ApiName = "api1";
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
services.AddTransient<IProfileService, ProfileService>();
// not recommended for production - you need to store your key material somewhere secure
//builder.AddDeveloperSigningCredential();
var fileName = Path.Combine("cert.pfx");
if (!File.Exists(fileName))
{
throw new FileNotFoundException("Signing Certificate is missing!");
}
var cert = new X509Certificate2(fileName, "secret");
builder.AddSigningCredential(cert);
// Register the Swagger services
services.AddSwaggerDocument(options =>
{
options.Title = "TEST";
options.AddSecurity("oauth2", new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = "https://localhost:5001/connect/authorize",
TokenUrl = "https://localhost:5001/connect/token",
Scopes = new Dictionary<string, string> { { "api1", "Demo API - full access" } }
}
}
});
options.OperationProcessors.Add(new OperationSecurityScopeProcessor("oauth2"));
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// this will do the initial DB population
app.InitializeDatabase();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseBrowserLink();
app.UseDatabaseErrorPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// 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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// app.UseAuthentication(); // not needed, since UseIdentityServer adds the authentication middleware
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
// Register the Swagger generator and the Swagger UI middlewares
app.UseOpenApi();
app.UseSwaggerUi3(options =>
{
options.DocumentTitle = "DEMO";
options.OAuth2Client = new OAuth2ClientSettings
{
ClientId = "demo_api_swagger",
AppName = "Demo API - Swagger",
UsePkceWithAuthorizationCodeGrant = true
};
});
}
Config.cs
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
public static IEnumerable<ApiResource> ApiResources =>
new List<ApiResource>
{
new ApiResource("api1", "My API")
};
public static IEnumerable<Client> Clients =>
new List<Client>
{
// machine to machine client
new Client
{
ClientId = "client",
ClientSecrets = {new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.ClientCredentials,
// scopes that client has access to
AllowedScopes = {"api1"}
},
// external user login
new Client
{
ClientId = "external",
ClientSecrets = {new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
// scopes that client has access to
AllowedScopes = {"api1"}
},
// interactive ASP.NET Core MVC client
new Client
{
ClientId = "mvc",
ClientSecrets = {new Secret("secret".Sha256())},
AllowedGrantTypes = GrantTypes.Code,
// where to redirect to after login
RedirectUris =
{
"https://localhost:5003/signin-oidc",
},
// where to redirect to after logout
PostLogoutRedirectUris =
{
"https://localhost:5003/signout-callback-oidc",
},
// scopes that client has access to
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
},
//swagger
new Client
{
ClientId = "demo_api_swagger",
ClientName = "Swagger UI for demo_api",
ClientSecrets = {new Secret("secret".Sha256())}, // change me!
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = false,
RequireClientSecret = false,
// where is it allowed to redirect to after login
RedirectUris =
{
"https://localhost:5001/swagger/oauth2-redirect.html",
"https://localhost:5003/swagger/oauth2-redirect.html"
},
AllowedCorsOrigins =
{
"https://localhost:5001",
"https://localhost:5003"
},
AllowedScopes = {"api1"}
}
};
}
服务器APP中被标记的Controller
[Authorize("ApiScope")]
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
和客户端APP
public void ConfigureServices(IServiceCollection services)
{
//Session data
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.Cookie.Name = "MVCClient";
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.ExpireTimeSpan = new TimeSpan(12, 0, 0, 0);
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("api1");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.ClaimActions.Add(new JsonKeyClaimAction(JwtClaimTypes.Role, null, "role"));
options.ClaimActions.Add(new JsonKeyClaimAction("isactive", null, "isactive"));
options.ClaimActions.Add(new JsonKeyClaimAction("title", null, "title"));
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
});
// adds an authorization policy to make sure the token is for scope 'api1'
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
services.AddControllersWithViews();
services.AddHttpContextAccessor();
services.AddHttpClient("TheClient", c =>
{
c.BaseAddress = new Uri("https://localhost:5001");
// access the DI container
var serviceProvider = services.BuildServiceProvider();
// Find the HttpContextAccessor service
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
// Get the bearer token from the request context (header)
var bearerToken = httpContextAccessor?.HttpContext?.GetTokenAsync("access_token").Result;
c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
});
services.AddRazorPages().AddRazorRuntimeCompilation();
services.RegisterDataTables();
services.AddExpressiveAnnotations();
}
这就是我在请求客户端应用程序(控制台应用程序)时获得的令牌的内容: TOKEN my ConsoleApp got
【问题讨论】:
-
如果您发布 API 的启动代码和 IDS4 配置会有所帮助 - 我想这与 API 资源和范围有关,这就是为什么要求代码
-
@nahidf: 请看上面,我更新了我的帖子
标签: c# asp.net rest identityserver4