简体   繁体   English

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

[英]ASP.Net Core 2.1 IdentityCore (Role Claims not being added on user sign in)

I'm using ASP.Net Core 2.1 with IdentityCore Service , the application is a pure API, no views at all. 我将ASP.Net Core 2.1IdentityCore Service结合使用 ,该应用程序是纯API,完全没有视图。 For authentication I'm purely using Steam authentication (No User/Pass login) provided by, https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers 对于身份验证,我纯粹使用https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers提供的Steam身份验证 (无用户/密码登录)

This API has been created to fit a very specific authentication workflow (User's can only login to the API with Steam) the Angular SPA sitting as the frontend handles the workflow just fine. 创建此API的目的是为了适应非常具体的身份验证工作流(用户只能使用Steam登录到API),因为前端Angular SPA可以很好地处理工作流。

The problem is that when I add a Role to a user (I've already got roles seeded and I've added my own steam account to the Admin Role), the role type claims are not being added on login, therefore when an admin user attempts to access an API route protected by [Authorize(Roles = "Admin") I'm being returned an Unauthorized Redirect. 问题是,当我向用户添加角色时(我已经播种了角色,并且已经将自己的Steam帐户添加到了“管理员角色”中),因此在登录时不会添加角色类型声明,因此当管理员用户尝试访问受[Authorize(Roles =“ Admin”)保护的API路由,我将收到未经授权的重定向。

Below I have added all code snippets I think is required (feel free to request more). 在下面,我添加了我认为是必需的所有代码段(可以随意请求更多代码段)。

If I use (I am currently using this as a temporary solution, but it is not ideal for future development); 如果我使用(我目前正在将其用作临时解决方案,但对于将来的开发而言并不理想);

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

The application correctly adds the role claims on user sign in (and the Authorize attributes work), using all existing code from AuthController.cs, yet using IdentityCore it fails. 该应用程序使用AuthController.cs中的所有现有代码在用户登录时正确添加角色声明(并且Authorize属性起作用),但是使用IdentityCore失败。 I feel I am missing a single line that is responsible for this but after trolling MSDN docs for days I am finally outwitted. 我觉得我错过了对此负责的一行,但是在拖了MSDN文档几天之后,我终于被淘汰了。

NOTE: The API will correctly authenticate and set the users cookies on sign in, but does not add the users roles to the users identity claims. 注意: API将在登录时正确地验证身份并设置用户cookie,但不会将用户角色添加到用户身份声明中。 Therefore, Authentication is Working, Authorization is not. 因此,身份验证有效,授权无效。 If I utilise the [Authorize] attribute without specifying a Role it works flawlessly and only allows Authenticated users to access the route whilst denying unAuthenticated users. 如果我在未指定角色的情况下使用[Authorize]属性,则它可以正常工作,并且仅允许经过身份验证的用户访问路由,同时拒绝未经身份验证的用户。 This can be seen in the Testing Screenshot at the end, identities[0].isAuthenticated = True, but the admin role is not being added to the Identity's Claims. 可以在最后的“测试屏幕截图”中看到identities [0] .isAuthenticated = True,但是没有将admin角色添加到Identity的Claims中。 As noted above, if I do not use AddIdentityCore and use AddIdentity, the roles are added to the user's claims correctly and the [Authorize(Role = "Admin")] attribute will work as expected, only allowing users that are apart of the Admin role to access it. 如上所述,如果我不使用AddIdentityCore而是使用AddIdentity,则会将角色正确添加到用户的声明中,并且[Authorize(Role =“ Admin”)]属性将按预期工作,仅允许与Admin不在同一范围的用户角色来访问它。

Startup.cs (Omitted irrelevant parts, eg. Database Connection) 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 => The only code responsible for authenticating against the Identity.Application scheme 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 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);
    }
}

Testing Screenshot: claims/identities/roletest API Response 测试屏幕截图: 声明/身份/最原始的API响应

Posted this issue to ASP.Net Identity GitHub repo, it is a known bug and has been resolved in ASP.Net Core 2.2 将此问题发布到ASP.Net Identity GitHub存储库中,这是一个已知的错误,已在ASP.Net Core 2.2中解决。

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

You have tow way for this issue. 您必须为这个问题拖路。

When you send a request to any WebService , If you set, Authorization be run Now: 将请求发送到任何WebService ,如果已设置,则立即运行Authorization

  1. Before login If you want to send a request to your WebService , and want to ignore Authorization you have to use of Allowanonymus Attribute like: login之前如果要向WebService发送请求,并且想忽略Authorization ,则必须使用Allowanonymus Attribute,例如:

    [Allowanonymous] Public void Login() { // here } [Allowanonymous] Public void Login(){//这里}

With this attribute, Authorization will ignore the request. 使用此属性,授权将忽略该请求。

  1. Now! 现在! If you want to send a request after login , you should to create you cookie In login time, and send a response to client , and also you have save that cookie in your localStorage in client, for Identifying in client. 如果要在login后发送请求,则应在登录时创建cookie ,然后将响应发送给client ,并且还将该cookie保存在client的localStorage中,以在client中进行标识。 After this you have to set that cookie in header of every request. 之后,您必须在每个请求的header中设置该cookie With this way, you authorization will Don! 这样,您的授权将成为唐!

Now If you want, I can create a sample for Authorization in best practice. 现在,如果您愿意,我可以按照最佳实践创建一个授权示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM