我正在使用ASP.Net Core 2.1和IdentityCore Service,该应用程序是一个纯API,根本没有视图。对于身份验证,我纯粹使用Steam 身份验证(无用户/通行证登录(由 https://github.com/aspnet-contrib/AspNet.Security.OpenId.Providers
创建此 API 是为了适应非常具体的身份验证工作流程(用户只能使用 Steam 登录 API(,Angular SPA 作为前端可以很好地处理工作流程。
问题是,当我向用户添加角色时(我已经设定了角色种子,并且我已经将自己的 Steam 帐户添加到管理员角色(,登录时不会添加角色类型声明,因此当管理员用户尝试访问受 [授权(角色 = "管理员"( 保护的 API 路由时,我会收到未经授权的重定向。
下面我添加了我认为需要的所有代码片段(请随时请求更多(。
如果我使用(我目前正在将其用作临时解决方案,但对于未来的开发并不理想(;
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<RSContext>()
.AddSignInManager<SignInManager<User>>()
.AddRoleManager<RoleManager<Role>>()
.AddDefaultTokenProviders();
应用程序在用户登录时正确添加角色声明(并且授权属性有效(,使用 AuthController.cs 中的所有现有代码,但使用 IdentityCore 失败。我觉得我错过了一行对此负责的行,但是在拖钓MSDN文档几天之后,我终于被智取了。
注意:API 将在登录时正确进行身份验证并设置用户 Cookie,但不会将用户角色添加到用户标识声明。因此,身份验证有效,授权无效。如果我在没有指定角色的情况下使用 [Authorize] 属性,它可以完美运行,只允许经过身份验证的用户访问路由,同时拒绝未经身份验证的用户。这可以在末尾的测试屏幕截图中看到,身份 [0].isAuthenticated = True,但管理员角色未添加到身份的声明中。如上所述,如果我不使用 AddIdentityCore 并使用 AddIdentity,则角色将正确添加到用户的声明中,并且 [Authorize(Role = "Admin"(] 属性将按预期工作,仅允许与 Admin 角色无关的用户访问它。
启动.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 } });
}
角色播种机.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 中解决
链接: https://github.com/aspnet/Identity/issues/1997
你必须为这个问题拖车。
当你向任意WebService
发送请求时,如果设置了,Authorization
立即运行:
-
在
login
之前 如果要向WebService
发送请求,并且想要忽略Authorization
则必须使用Allowanonymus
属性,例如:[允许匿名] 公共无效登录(( { 这里 }
使用此属性时,授权将忽略该请求。
- 现在!如果您想在
login
之后发送请求,您应该在登录时间内创建cookie
,并向client
发送响应,并且您还将该cookie保存在客户端的localStorage
中,以便在客户端中识别。在此之后,您必须cookie
在header
每个请求。这样,您的授权将唐!
现在,如果需要,我可以在最佳实践中创建授权示例。