问题是,IdentityServer已经提供了用于使用Identity Core实现IS4的代码库。但是,该代码库确实 不包含任何用户注册码。底层.NET Core 身份用户注册码存在。您需要连接的所有内容 这样做是为了创建一个像.NET Core一样调用它的MVC API Identity的样本项目。但是,我无法使其与 在 HttpContext 级别上存在IdentityServer4。
似乎实际上是开发人员未创建注册功能的原因...
我们得到的最接近的答案是在这里: User Registration Process with IdentityServer4
但是,这是没有任何代码示例的理论答案。这是我为了找到答案而可以提供的代码。
这是我的身份认证初创公司
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Nozomi.Base.Identity;
using Nozomi.Base.Identity.Debugging;
using Nozomi.Base.Identity.Models;
using Nozomi.Base.Identity.Models.Identity;
using Nozomi.Repo.Identity.Data;
using Nozomi.Service.Identity;
using Nozomi.Service.Identity.Factories;
using Nozomi.Service.Identity.Managers;
using Nozomi.Service.Identity.Stores;
using Nozomi.Service.Identity.Stores.Interfaces;
namespace Nozomi.Ticker.StartupExtensions
{
public static class IdentityServerStartup
{
public static void ConfigureNozomiAuth(this IServiceCollection services, IConfiguration configuration)
{
var env = // Code omitted for brevity
services.AddIdentity<User, Role>()
.AddEntityFrameworkStores<NozomiAuthContext>()
.AddUserManager<NozomiUserManager>()
.AddSignInManager<NozomiSignInManager>()
.AddUserStore<NozomiUserStore>()
.AddRoleStore<NozomiRoleStore>()
.AddClaimsPrincipalFactory<NozomiUserClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
// Configure Authentication
// https://github.com/aspnet/Security/issues/1414 Redundant to add in this
//
// Findings have shown this affects IdentityServerAuthenticationService. Removing this will
// default all constants to .NET Core Identity's defaults.
services.AddAuthentication(options =>
{
options.DefaultScheme = NozomiAuthConstants.ApplicationScheme;
options.DefaultAuthenticateScheme = NozomiAuthConstants.ApplicationScheme;
options.DefaultChallengeScheme = NozomiAuthConstants.ApplicationScheme;
options.DefaultForbidScheme = NozomiAuthConstants.ApplicationScheme;
options.DefaultSignInScheme = NozomiAuthConstants.ApplicationScheme;
options.DefaultSignOutScheme = NozomiAuthConstants.ApplicationScheme;
}).AddCookie(options =>
{
options.Cookie.Name = NozomiAuthConstants.ApplicationScheme;
// Cookie settings
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
// TODO: finish this
options.LoginPath = "/account/login";
options.LogoutPath = "/account/logout";
options.AccessDeniedPath = "/account/accessdenied";
options.SlidingExpiration = true;
});
// Configure Authorization
services.AddAuthorization();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
options.User.RequireUniqueEmail = true;
});
services.AddIdentityServer()
.AddRequiredPlatformServices()
//.AddSigningCredential(cert)
.AddResourceStore<ResourceStore>()
.AddClientStore<ClientStore>()
.AddAspNetIdentity<User>()
.AddProfileService<NozomiProfileService>();
services.AddTransient<IClientStore, ClientStore>();
services.AddTransient<IResourceStore, ResourceStore>();
}
public static void UseNozomiAuth(this IApplicationBuilder app)
{
app.UseIdentityServer();
}
}
}
因此,基本上,此注入计划告诉您我正在尝试结合.NET Core身份实现IS4。然后,我进一步注入AddAuthentication()方法,以确保正确放置了我的自定义方案名称。
请注意,您在下面看到的内容相当于一天的逐步调试。
仅供参考,由IS4完成的注入操作不会声明您自己的常量(方案),因此您必须确保“ AddAuthentication”
在您调用“ AddIdentityServer”时,这只是IS4的一小部分
/// <summary>
/// Adds the default cookie handlers and corresponding configuration
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static IIdentityServerBuilder AddCookieAuthentication(this IIdentityServerBuilder builder)
{
builder.Services.AddAuthentication(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddCookie(IdentityServerConstants.DefaultCookieAuthenticationScheme)
.AddCookie(IdentityServerConstants.ExternalCookieAuthenticationScheme);
builder.Services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureInternalCookieOptions>();
builder.Services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureInternalCookieOptions>();
builder.Services.AddTransientDecorator<IAuthenticationService, IdentityServerAuthenticationService>();
builder.Services.AddTransientDecorator<IAuthenticationHandlerProvider, FederatedSignoutAuthenticationHandlerProvider>();
return builder;
}
如果您真的想知道,它实际上就是这样做的。
/// <summary>
/// Adds IdentityServer.
/// </summary>
/// <param name="services">The services.</param>
/// <returns></returns>
public static IIdentityServerBuilder AddIdentityServer(this IServiceCollection services)
{
var builder = services.AddIdentityServerBuilder();
builder
.AddRequiredPlatformServices()
.AddCookieAuthentication()
.AddCoreServices()
.AddDefaultEndpoints()
.AddPluggableServices()
.AddValidators()
.AddResponseGenerators()
.AddDefaultSecretParsers()
.AddDefaultSecretValidators();
// provide default in-memory implementation, not suitable for most production scenarios
builder.AddInMemoryPersistedGrants();
return builder;
}
最初的实现无效。我必须创建自己的用户存储。但是只有一种方法需要更新,而这就是更新。
/// <summary>
/// Gets the user identifier for the specified <paramref name="user" />.
/// </summary>
/// <param name="user">The user whose identifier should be retrieved.</param>
/// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the identifier for the specified <paramref name="user" />.</returns>
public override Task<string> GetUserIdAsync(User user, CancellationToken cancellationToken = default (CancellationToken))
{
try
{
if (cancellationToken != null)
cancellationToken.ThrowIfCancellationRequested();
if (user == null)
throw new ArgumentException(nameof(user));
var res = _unitOfWork.GetRepository<User>().Get(u =>
u.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase)
|| user.UserName.Equals(user.UserName, StringComparison.InvariantCultureIgnoreCase))
.Select(u => u.Id)
.SingleOrDefault();
if (res == null)
throw new ArgumentOutOfRangeException(nameof(user));
return Task.FromResult(res.ToString());
}
catch (Exception ex)
{
return Task.FromResult("-1");
}
}
您还必须覆盖现有的SignInManager以支持您的自定义方案。
/// <summary>
/// Returns true if the principal has an identity with the application cookie identity
/// </summary>
/// <param name="principal">The <see cref="ClaimsPrincipal"/> instance.</param>
/// <returns>True if the user is logged in with identity.</returns>
public override bool IsSignedIn(ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal?.Identities != null &&
principal.Identities.Any(i => i.AuthenticationType == NozomiAuthConstants.ApplicationScheme);
}
/// <summary>
/// Regenerates the user's application cookie, whilst preserving the existing
/// AuthenticationProperties like rememberMe, as an asynchronous operation.
/// </summary>
/// <param name="user">The user whose sign-in cookie should be refreshed.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
public override async Task RefreshSignInAsync(User user)
{
var auth = await Context.AuthenticateAsync(NozomiAuthConstants.ApplicationScheme);
var authenticationMethod = auth?.Principal?.FindFirstValue(ClaimTypes.AuthenticationMethod);
await SignInAsync(user, auth?.Properties, authenticationMethod);
}
/// <summary>
/// Signs in the specified <paramref name="user"/>.
/// </summary>
/// <param name="user">The user to sign-in.</param>
/// <param name="isPersistent">Flag indicating whether the sign-in cookie should persist after the browser is closed.</param>
/// <param name="authenticationMethod">Name of the method used to authenticate the user.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
public override Task SignInAsync(User user, bool isPersistent, string authenticationMethod = null)
{
return SignInAsync(user, new AuthenticationProperties { IsPersistent = isPersistent }, authenticationMethod);
}
/// <summary>
/// Signs in the specified <paramref name="user"/>.
/// </summary>
/// <param name="user">The user to sign-in.</param>
/// <param name="authenticationProperties">Properties applied to the login and authentication cookie.</param>
/// <param name="authenticationMethod">Name of the method used to authenticate the user.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
public override async Task SignInAsync(User user, AuthenticationProperties authenticationProperties, string authenticationMethod = null)
{
try
{
var userPrincipal = await CreateUserPrincipalAsync(user);
// Review: should we guard against CreateUserPrincipal returning null?
if (authenticationMethod != null)
{
userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
}
await Context.SignInAsync(NozomiAuthConstants.ApplicationScheme,
userPrincipal,
authenticationProperties ?? new AuthenticationProperties());
// https://github.com/aspnet/Security/issues/1131#issuecomment-280896191
// Context.User = userPrincipal;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
/// <summary>
/// Signs the current user out of the application.
/// </summary>
public override async Task SignOutAsync()
{
await Context.SignOutAsync(NozomiAuthConstants.ApplicationScheme);
await Context.SignOutAsync(IdentityConstants.ApplicationScheme);
await Context.SignOutAsync(IdentityConstants.ExternalScheme);
await Context.SignOutAsync(IdentityConstants.TwoFactorUserIdScheme);
}
然后,我更新了索赔工厂以正确支持我制定的方案。
namespace Nozomi.Service.Identity.Factories
{
public class NozomiUserClaimsPrincipalFactory: UserClaimsPrincipalFactory<User, Role>
{
public new NozomiUserManager UserManager;
public new RoleManager<Role> RoleManager;
public NozomiUserClaimsPrincipalFactory(NozomiUserManager userManager, RoleManager<Role> roleManager,
IOptions<IdentityOptions> options) : base(userManager, roleManager, options)
{
UserManager = userManager;
RoleManager = roleManager;
}
/// <summary>
/// Creates a <see cref="T:System.Security.Claims.ClaimsPrincipal" /> from an user asynchronously.
/// </summary>
/// <param name="user">The user to create a <see cref="T:System.Security.Claims.ClaimsPrincipal" /> from.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous creation
/// operation, containing the created <see cref="T:System.Security.Claims.ClaimsPrincipal" />.</returns>
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
if ((object) user == null)
throw new ArgumentNullException(nameof (user));
return new ClaimsPrincipal(await GenerateClaimsAsync(user));
}
/// <summary>Generate the claims for a user.</summary>
/// <param name="user">The user to create a <see cref="T:System.Security.Claims.ClaimsIdentity" /> from.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous creation operation, containing the created <see cref="T:System.Security.Claims.ClaimsIdentity" />.</returns>
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user)
{
var userId = await UserManager.GetUserIdAsync(user);
var userNameAsync = await UserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity(NozomiAuthConstants.ApplicationScheme,
this.Options.ClaimsIdentity.UserNameClaimType, this.Options.ClaimsIdentity.RoleClaimType);
id.AddClaim(new Claim(this.Options.ClaimsIdentity.UserIdClaimType, userId));
id.AddClaim(new Claim(this.Options.ClaimsIdentity.UserNameClaimType, userNameAsync));
ClaimsIdentity claimsIdentity;
if (this.UserManager.SupportsUserSecurityStamp)
{
claimsIdentity = id;
string type = this.Options.ClaimsIdentity.SecurityStampClaimType;
claimsIdentity.AddClaim(new Claim(type, await this.UserManager.GetSecurityStampAsync(user)));
claimsIdentity = (ClaimsIdentity) null;
type = (string) null;
}
if (this.UserManager.SupportsUserClaim)
{
claimsIdentity = id;
claimsIdentity.AddClaims((IEnumerable<Claim>) await this.UserManager.GetClaimsAsync(user));
claimsIdentity = (ClaimsIdentity) null;
}
return id;
}
}
}
我们应该能够通过创建一个注册API来立即登录,该API在成功注册后自动登录。
//
// POST: /Account/Register
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new User
{
UserName = model.Username,
Email = model.Email
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
#if DEBUG
await _userManager.ForceConfirmEmail(user);
await _signInManager.SignInAsync(user, false, NozomiAuthConstants.ApplicationScheme);
_logger.LogInformation(3, "User created a new account with password.");
#else
// For more information on how to enable account confirmation and password reset
// please visit http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code },
protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
"Please confirm your account by clicking this link: <a href=\"" + callbackUrl + "\">link</a>");
#endif
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
ForceConfirmEmail是一种自定义方法,用于默认情况下手动将EmailConfirmed布尔值设置为true。
注册后,所有内容都可以顺利通过数据库(您的用户帐户已保存在数据库中)。但是,尽管cookie中存在注册的剩余内容,但您无法从剃刀视图中得到任何东西。
如果您已经注意到,从一开始我就一直在使用NozomiAuthConstants.ApplicationScheme
。就像您知道的那样,它实际上等于'idsrv'。
我在下面附加了一张图片,向您显示登录后会得到什么。您一无所获,甚至没有索偿。声明的长度= 0,任何IsAuthenticated =假。
再次运行整个mvc应用程序后,前端仍将其显示为未经身份验证。我回到绘图板上,找出什么为空,然后我意识到获取HttpContext的代码中断了,并为此修复了DI,最终在我的自定义SignInManager中实现了这一点,以使将来的调试过程更轻松。 ,(我还添加了私有变量/对象和构造函数以供进一步参考):
private readonly IHttpContextAccessor _contextAccessor;
private HttpContext _context;
private new readonly NozomiUserManager UserManager;
private new readonly NozomiUserClaimsPrincipalFactory ClaimsFactory;
public NozomiSignInManager(NozomiUserManager userManager, IHttpContextAccessor contextAccessor,
NozomiUserClaimsPrincipalFactory claimsFactory, IOptions<IdentityOptions> optionsAccessor,
ILogger<NozomiSignInManager> logger, IAuthenticationSchemeProvider schemes) :
base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
_contextAccessor = contextAccessor;
UserManager = userManager;
ClaimsFactory = claimsFactory;
}
/// <summary>
/// The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" /> used.
/// </summary>
public new HttpContext Context
{
get
{
var httpContext = _context ?? _contextAccessor?.HttpContext;
if (httpContext != null)
return httpContext;
throw new InvalidOperationException("HttpContext must not be null.");
}
set => _context = value;
}
这是我正确注入HttpContextAccessor的方式:
services.AddIdentityServer()
.AddRequiredPlatformServices()
//.AddSigningCredential(cert)
.AddResourceStore<ResourceStore>()
.AddClientStore<ClientStore>()
.AddAspNetIdentity<User>()
.AddProfileService<NozomiProfileService>();
此行
.AddRequiredPlatformServices()
确实
builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddOptions();
builder.Services.AddSingleton(
resolver => resolver.GetRequiredService<IOptions<IdentityServerOptions>>().Value);
return builder;
这是一个值得每周工作的工作(我在工作日没有太多时间,因此请考虑2-3天的工作。如果您有任何疑问,请告诉我,因为我或多或少地调试了步骤Identity Core代码的每个部分。)
现在,我所缺少的是找出为什么ClaimsPrincipal没有正确地注入到HttpContext中。
答案 0 :(得分:0)
AspIdentity +
身份服务器+
身份快速入门.UI +
数据库
我们将打开一个运行窗口
在键盘上按
Windoes Key + R
等待
我们将打开一个cmd窗口
在“运行”窗口中输入文字输入
cmd
在键盘上按
Enter
确保App1 / appsettings.json具有此连接字符串,默认情况下将存在该连接字符串(如果未添加的话)
DataSource=app.db
我们将创建一个目录并将其作为cmd的工作目录
在CMD窗口上写
mkdir D:\Projects\P.IT.Support\asp.net
cd /d D:\Projects\P.IT.Support\asp.net
我们将下载并运行脚本
在CMD窗口上写
set scriptUrl="https://gist.githubusercontent.com/Elrashid/133d308902b453c280725379dda02684/raw/Flow4-AspIdentity-Sqlite-RequireConsent-Two-App-For-API-And-Web.bat"
PowerShell -Command "(new-object System.Net.WebClient).DownloadFile('%scriptUrl%','Flow4-AspIdentity-Sqlite-RequireConsent-Two-App-For-API-And-Web.bat')"
Flow4-AspIdentity-Sqlite-RequireConsent-Two-App-For-API-And-Web.bat