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

时间:2018-10-07 06:49:30

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

我正在将 ASP.Net Core 2.1 IdentityCore Service 一起使用,该应用程序是纯API,完全没有视图。对于身份验证,我仅使用https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers

提供的 Steam身份验证(无用户/密码登录)

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

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

下面,我添加了我认为是必需的所有代码段(可以随时请求更多代码段)。

如果我使用(我目前正在使用它作为临时解决方案,但对于将来的开发而言并不理想);

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,但是没有将admin角色添加到Identity的Claims中。如上所述,如果我不使用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

2 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:0)

您必须解决此问题。

将请求发送到任何WebService时,如果已设置,Authorization立即运行:

  1. login之前如果要向WebService发送请求,并且想忽略Authorization,则必须使用Allowanonymus属性,例如:< / p>

    [允许匿名] 公共无效Login() {     // 这里 }

使用此属性,授权将忽略该请求。

  1. 现在!如果您想在login之后发送请求,则应该在登录时创建cookie,并发送对client的响应,并且您还将该Cookie保存在{{ 1}},用于在客户端进行标识。之后,您必须在其中设置localStorage 每个请求中的cookie。这样,您的授权就可以了!

现在,如果您愿意,我可以按照最佳实践创建一个授权示例。