我正在将 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);
}
}
答案 0 :(得分:2)
此问题已发布到ASP.Net Identity GitHub存储库中,这是一个已知的错误,已在ASP.Net Core 2.2中得到解决
答案 1 :(得分:0)
您必须解决此问题。
将请求发送到任何WebService
时,如果已设置,Authorization
立即运行:
在login
之前如果要向WebService
发送请求,并且想忽略Authorization
,则必须使用Allowanonymus
属性,例如:< / p>
[允许匿名] 公共无效Login() { // 这里 }
使用此属性,授权将忽略该请求。
login
之后发送请求,则应该在登录时创建cookie
,并发送对client
的响应,并且您还将该Cookie保存在{{ 1}},用于在客户端进行标识。之后,您必须在其中设置localStorage
每个请求中的cookie
。这样,您的授权就可以了!现在,如果您愿意,我可以按照最佳实践创建一个授权示例。