[英]ASP.Net Core 2.1 IdentityCore (Role Claims not being added on user sign in)
我將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);
}
}
測試屏幕截圖: 聲明/身份/最原始的API響應
將此問題發布到ASP.Net Identity GitHub存儲庫中,這是一個已知的錯誤,已在ASP.Net Core 2.2中解決。
您必須為這個問題拖路。
將請求發送到任何WebService
,如果已設置,則立即運行Authorization
:
login
之前如果要向WebService
發送請求,並且想忽略Authorization
,則必須使用Allowanonymus
Attribute,例如:
[Allowanonymous] Public void Login(){//這里}
使用此屬性,授權將忽略該請求。
login
后發送請求,則應在登錄時創建cookie
,然后將響應發送給client
,並且還將該cookie保存在client的localStorage
中,以在client中進行標識。 之后,您必須在每個請求的header
中設置該cookie
。 這樣,您的授權將成為唐! 現在,如果您願意,我可以按照最佳實踐創建一個授權示例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.