c# - ASP.Net Core 2.1 IdentityCore(用户登录时未添加角色声明)

标签 c# asp.net asp.net-web-api asp.net-core asp.net-identity

我正在使用 ASP.Net Core 2.1IdentityCore 服务,该应用程序是一个纯 API,根本没有 View 。对于身份验证,我纯粹使用由 https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers 提供的 Steam 身份验证(无用户/密码登录)

创建此 API 是为了适应非常具体的身份验证工作流程(用户只能使用 Steam 登录 API),作为前端的 Angular SPA 可以很好地处理工作流程。

问题 是当我向用户添加角色时(我已经播种角色并且我已将自己的 Steam 帐户添加到管理员角色),角色类型声明是未在登录时添加,因此当管理员用户尝试访问受 [Authorize(Roles = "Admin") 保护的 API 路由时,我将返回未经授权的重定向。

下面我添加了我认为需要的所有代码片段(请随时请求更多)。

如果我使用(我目前使用这个作为临时解决方案,但它不适合 future 的发展);

services.AddIdentity<User, Role>()
   .AddEntityFrameworkStores<RSContext>()
   .AddSignInManager<SignInManager<User>>()
   .AddRoleManager<RoleManager<Role>>()
   .AddDefaultTokenProviders();

该应用程序使用 AuthController.cs 中的所有现有代码正确地添加了用户登录时的角色声明(并且 Authorize 属性有效),但使用 IdentityCore 失败了。我觉得我遗漏了一行对此负责的内容,但在查阅了 MSDN 文档数天之后,我终于被愚弄了。

注意:API 将正确验证 并在用户登录时设置 cookie,但不会将用户角色添加到用户身份声明中。因此,身份验证有效,授权无效。如果我在没有指定角色的情况下使用 [Authorize] 属性,它可以完美地工作,并且只允许经过身份验证的用户访问路由,同时拒绝未经身份验证的用户。这可以在最后的测试屏幕截图中看到,identities[0].isAuthenticated = True,但是管理员角色没有被添加到身份的声明中。如上所述,如果我不使用 AddIdentityCore 而使用 AddIdentity,则角色会正确添加到用户的声明中,并且 [Authorize(Role = "Admin")] 属性将按预期工作,只允许 Admin 之外的用户访问它的角色。

Startup.cs(省略无关部分,如数据库连接)

public void ConfigureServices(IServiceCollection services)
{
    IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
    {
        opt.Password.RequireDigit = true;
        opt.Password.RequiredLength = 6;
        opt.Password.RequireNonAlphanumeric = true;
        opt.Password.RequireUppercase = true;
        opt.User.AllowedUserNameCharacters += ":/";
    });

    builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
    builder.AddEntityFrameworkStores<RSContext>();
    builder.AddSignInManager<SignInManager<User>>();
    builder.AddRoleValidator<RoleValidator<Role>>();
    builder.AddRoles<Role>();
    builder.AddRoleManager<RoleManager<Role>>();
    builder.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<User>>();
    builder.AddDefaultTokenProviders();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
        options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignOutScheme = IdentityConstants.ApplicationScheme;
        options.DefaultForbidScheme = IdentityConstants.ApplicationScheme;
    })
        .AddSteam(options =>
        {
            options.ApplicationKey = Configuration.GetSection("Authentication:Steam:Key").Value;
            options.CallbackPath = "/api/auth/steam/callback";
            options.Events.OnAuthenticated = OnClientAuthenticated;
        })
        .AddIdentityCookies(options =>
        {
            options.ApplicationCookie.Configure(appCookie =>
            {
                appCookie.Cookie.Name = "RaidSimulator";
                appCookie.LoginPath = "/api/auth/login";
                appCookie.LogoutPath = "/api/auth/logout";
                appCookie.Cookie.HttpOnly = true;
                appCookie.Cookie.SameSite = SameSiteMode.Lax;
                appCookie.Cookie.IsEssential = true;
                appCookie.SlidingExpiration = true;
                appCookie.Cookie.Expiration = TimeSpan.FromMinutes(1);
                appCookie.Cookie.MaxAge = TimeSpan.FromDays(7);
            });
            options.ExternalCookie.Configure(extCookie =>
            {
                extCookie.Cookie.Name = "ExternalLogin";
                extCookie.LoginPath = "/api/auth/login";
                extCookie.LogoutPath = "/api/auth/logout";
                extCookie.Cookie.HttpOnly = true;
                extCookie.Cookie.SameSite = SameSiteMode.Lax;
                extCookie.Cookie.IsEssential = true;
                extCookie.Cookie.Expiration = TimeSpan.FromMinutes(10);
            });
        });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, RoleManager<Role> roleManager)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    RolesSeed.Seed(roleManager).Wait();

    app.UseCors();
    app.UseAuthentication();
    app.UseMvc();
}

// Responsible for storing/updating steam profile in database
private async Task OnClientAuthenticated(OpenIdAuthenticatedContext context)
{
    var rsContext = context.HttpContext.RequestServices.GetRequiredService<RSContext>();
    var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>();

    var profile = context.User?.Value<JObject>(SteamAuthenticationConstants.Parameters.Response)
                        ?.Value<JArray>(SteamAuthenticationConstants.Parameters.Players)?[0]?.ToObject<SteamProfile>();

    // TODO: Handle this better, Redir user to an informative error page or something
    if (profile == null)
        return;

    var dbProfile = await rsContext.SteamProfiles.FindAsync(profile.SteamId);
    if (dbProfile != null)
    {
        rsContext.Update(dbProfile);
        dbProfile.UpdateProfile(profile);
        await rsContext.SaveChangesAsync();
    }
    else
    {
        await rsContext.SteamProfiles.AddAsync(profile);
        await rsContext.SaveChangesAsync();
    }
}

AuthController.cs => 负责根据 Identity.Application 方案进行身份验证的唯一代码

[HttpGet("callback")]
[Authorize(AuthenticationSchemes = "Steam")]
public async Task<IActionResult> Callback([FromQuery]string ReturnUrl)
{
    ReturnUrl = ReturnUrl?.Contains("api/") == true ? "/" : ReturnUrl;

    if (HttpContext.User.Claims.Count() > 0)
    {
        var provider = HttpContext.User.Identity.AuthenticationType;
        var nameIdentifier = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
        var name = HttpContext.User.FindFirstValue(ClaimTypes.Name);

        var loginResult = await signInManager.ExternalLoginSignInAsync(provider, nameIdentifier, false);
        if (loginResult.Succeeded)
        {
            return Redirect(ReturnUrl ?? "/api/auth/claims");
        }

        var result = await userManager.CreateAsync(new User { UserName = nameIdentifier, SteamId = nameIdentifier.Split("/").Last() });
        if (result.Succeeded)
        {
            var user = await userManager.FindByNameAsync(nameIdentifier);
            var identity = await userManager.AddLoginAsync(user, new UserLoginInfo(provider, nameIdentifier, name));

            if (identity.Succeeded)
            {
                await signInManager.ExternalLoginSignInAsync(provider, nameIdentifier, false);
                return Redirect(ReturnUrl ?? "/api/auth/claims");
            }
        }
    }

    return BadRequest(new { success = false });
}

[HttpGet("claims")]
[Authorize]
public async Task<IActionResult> GetClaims()
{
    var user = await userManager.GetUserAsync(User);
    var claims =
        User.Claims.Select(c => new
        {
            c.Type,
            c.Value
        });

    var inAdmin = new string[] {
        "User.IsInRole(\"Admin\") = " + User.IsInRole("Admin"),
        "User.IsInRole(\"ADMIN\") = " + User.IsInRole("ADMIN"),
        "User.IsInRole(\"admin\") = " + User.IsInRole("admin"),
        "userManager.IsInRoleAsync(user, \"admin\") = " + await userManager.IsInRoleAsync(user, "admin")
    };

    return Ok(new { success = true, data = new { claims, inAdmin, User.Identities } });
}

RoleSeeder.cs

public static async Task Seed(RoleManager<Role> roleManager)
{
    // Developer Role
    if(!await roleManager.RoleExistsAsync("Developer"))
    {
        var role = new Role("Developer");
        await roleManager.CreateAsync(role);
    }
    // Community Manager Role
    if (!await roleManager.RoleExistsAsync("Community Manager"))
    {
        var role = new Role("Community Manager");
        await roleManager.CreateAsync(role);
    }
    // Admin Role
    if (!await roleManager.RoleExistsAsync("Admin"))
    {
        var role = new Role("Admin");
        await roleManager.CreateAsync(role);
    }
    // Moderator Role
    if (!await roleManager.RoleExistsAsync("Moderator"))
    {
        var role = new Role("Moderator");
        await roleManager.CreateAsync(role);
    }
}

测试截图: claims/identities/roletest API Response

最佳答案

已将此问题发布到 ASP.Net Identity GitHub 存储库,这是一个已知错误并已在 ASP.Net Core 2.2 中解决

链接:https://github.com/aspnet/Identity/issues/1997

关于c# - ASP.Net Core 2.1 IdentityCore(用户登录时未添加角色声明),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52686139/

相关文章:

validation - ASP.NET Web API 是否支持 IValidatableObject?

android - 如何在 ASP.NET Web Api 中使用安全性(身份验证和授权)

c# - 指定的 key 太长;最大 key 长度为 767 字节 - ASPNet Identity MySQL

c# - 有什么理由不在生产版本中启用 CODE_ANALYSIS 吗?

c# - 获取数组中最常见(频繁)的字符串条目

asp.net - 来自 Visual Studio 调试的 localhost 的 ERR_SSL_PROTOCOL_ERROR

c# - 将 excel 工作表保存到我在本地计算机上创建的特定文件夹,并将此 excel 工作表设置为只读

c# - 无法使用 MigraDoc/PDFsharp 打印

c# - 无法从 system.collections.generic.list 转换为字符串到 Vba.collections

c# - 在 EF 的 ASP.Net WebAPI 方法中使用 Async 有什么好处?