【问题标题】:ASP.NET Identity in MVC client using IdentityServer causes loop使用 IdentityServer 的 MVC 客户端中的 ASP.NET Identity 导致循环
【发布时间】:2020-06-18 18:04:34
【问题描述】:

我有一个使用 IdentityServer 进行身份验证的 Asp.NET Core 应用程序。
这很好用。
现在我想在我的应用程序中使用 ASP.NET Core Identity 来管理角色、声明等。

文档说我应该为此添加service.AddIdentity ...
但是,当我在客户端中将其添加到 Startup.cs 时,使用 IdentityServer 的登录不再有效。

我将被重定向到 IdentityServer,登录并重定向回客户端(这工作正常)
但是,然后我的客户端会引发有关授权的错误并再次重定向到 IdentityServer。这会导致无限循环

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 POST http://localhost:44331/signin-oidc application/x-www-form-urlencoded 5297
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Cookies signed in.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 184.9938ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44331/  
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: Identity.Application was not authenticated. Failure message: Unprotect ticket failed
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Index", controller = "Home", page = "", area = ""}. Executing action TestApplication.Controllers.HomeController.Index (TestApplication)
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:Information: AuthenticationScheme: oidc was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action TestApplication.Controllers.HomeController.Index (TestApplication) in 15.4912ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 29.286ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 POST http://localhost:44331/signin-oidc application/x-www-form-urlencoded 5297
-- and it starts all over again

有人知道我做错了什么吗?

这是我的 Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration, IHostingEnvironment environment)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(environment.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", optional: true);

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();            
    }

    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)
    {
        services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddTransient<ApiService>();
        services.AddSingleton(Configuration);
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => false;
            options.MinimumSameSitePolicy = SameSiteMode.None;                       
        });


        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie()
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = 'Cookies';
            options.UseTokenLifetime = true; 

            options.Authority = 'https://localhost:44350; 
            options.RequireHttpsMetadata = true;

            options.ClientId = Configuration.GetValue<string>("IdentityServer:ClientId");
            options.ClientSecret = Configuration.GetValue<string>("IdentityServer:ClientSecret");
            options.ResponseType = "code id_token token"

            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Scope.Add("api1");                
            options.Scope.Add("offline_access");
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");

            options.TokenValidationParameters.NameClaimType = "name";
            options.TokenValidationParameters.RoleClaimType = "role";

        });

        services.AddMvc(config =>
        {
            var policy = new AuthorizationPolicyBuilder()
                             .RequireAuthenticatedUser()
                             .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
        });

        services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore)
            .AddJsonOptions(x => x.SerializerSettings.NullValueHandling = NullValueHandling.Ignore);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseAuthentication();
        app.UseHttpsRedirection();
        app.UseCookiePolicy();


        app.UseMvc(routes =>
        {
            routes.MapRoute(
               name: "areas",
               template: "{area:exists}/{controller}/{action}/{id?}");

            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

    }
}

【问题讨论】:

  • 您必须在您的身份服务器中配置 ASP.NET 身份,而不是在您的 MVC 应用程序中。用户将在那里登录(Id Server),它也可以管理角色,并将它们作为声明返回。在您的 MVC 应用程序中,您只需添加普通的 Policies 即可根据角色在端点中应用授权。
  • 所以你是说现在所有的用户管理等都应该在 IdentityServer 中完成?嗯,这很烦人。
  • 不一定。您可以将 ASP.Identity 与它挂钩。这意味着它将从该数据库中读取并验证用户,并通过用户声明(角色)向您返回访问令牌。如果您愿意,可以使用其他应用程序来管理“身份”数据库。
  • 是的,我的 IdentityServer 中有 ASP.Identity。我正在尝试在我的客户端应用程序中找到一种在不破坏登录机制的情况下使用和扩展 IdentityUser 的方法。

标签: asp.net-core asp.net-identity identityserver4


【解决方案1】:

在 Identity Server 端,您可以创建 Profile Service 以使 IDS4 在发布令牌时包含角色声明。然后在客户端,您可以将角色声明从您的 JWT 令牌映射到您的声明原则。您可以参考here的代码示例。

要管理用户或角色,您可以在 Identity Server4 应用程序中提供 API 端点,或者创建一个新应用程序作为另一个资源来管理您的数据库,您的客户端应用程序将获取访问令牌以访问您的新应用程序表单 Identity Server 4 ,通过将令牌附加到 HTTP 授权标头来发送请求以进行管理 API 调用。

【讨论】:

  • 基本上就是我在 cmets 中所说的 :)
  • @jpgrassi 是的,还提供了如何在 IDS4 中使用 asp.net 身份映射角色的代码示例:)
  • IdentityServer GitHub repo 上已经有身份样本,他们可能将角色映射到那里的声明。为了清楚起见,我并没有试图减少或讽刺你的回答。只是陈述事实,因为看起来 OP 无法将我的想法与他的解决方案联系起来。
【解决方案2】:

如果其他人遇到此问题并仍想通过 IdSrv 进行身份验证但在 IdSrv 之外管理用户,请替换此:

services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

在您的客户端应用中使用此

services.AddIdentityCore<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    //.AddDefaultUI() // use standard identity UI forms for register, forgot pass etc.
    .AddDefaultTokenProviders();

取消注释上面的行以使用来自 MS 身份的标准 UI 表单,但请注意,之后您必须覆盖默认登录逻辑。这是通过将标识脚手架添加到您的项目来完成的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-28
    • 2015-09-17
    • 2022-01-15
    • 2020-08-22
    • 1970-01-01
    • 2013-12-12
    • 1970-01-01
    • 2021-10-21
    相关资源
    最近更新 更多