Recall that two days before doing the project, the certification has been relatively headache place.
So taking the time to read some of the content, starting form authentication, see the identity, the process is just a simple browser, but before the concept can be considered to have some deepened.
In fact, these two genius to know, form authentication is already a thing of the past in .net, Identity has been replaced. After Nonetheless, I read form authentication, certification of the whole process is also have some understanding, but here is get down some identity-related content.
Owin
Owin identity is based on the framework. Owin actually a specification to achieve its .net, called kantana, but in fact they use, I think that basically is interconnected.
Owin no longer rely on IIS request pipeline, but a registration mechanism to customize the components of the individual that is already similar to the practice of the .net core.
Owin and IIS
Owin can run on IIS, you can also run independently, Owin operate independently with its own SelfHost components, when IIS-based, Owin dependent Microsoft.Owin.Host.SystemWeb
libraries complete the integration of IIS.
The IIS Global.asax
and Owin of StartUp
callbacks also be present at the same time, it will be called.
If integrated into the IIS, Owin the initialization call, almost triggered after Application_Start event.
Owin starting class
Owin the pipeline is arranged in the starting class Configuration process.
There are two configurations, one is specified in Web.config AppSettings node:
<add key="owin:AppStartup" value="起始类" />
Another property configuration by, containing Configuration
labeling properties on the class methods
[assembly: OwinStartup(typeof(StartupDemo.TestStartup))]
Identity
Identity replace the original Form Authentication to some extent, but if you do not use Owin, I still have to understand the use of Form Authentication.
Identity is actually in response to the emergence of third-party certification under the network environment (google, fb, etc.), two-factor authentication (2FA) demand, the traditional way of Form are less likely to expand.
Reference to this part of the SO to some extent, the answer to this question
Identity components increase
Apart Owin dependence, Identity also on EntityFramework, require several components Nuget:
- Microsoft.AspNet.Identity.Owin
- Microsoft.AspNet.Identity.EntityFramework
- Microsoft.Owin.Host.SystemWeb
If you need to use MySql, also you need to install
- MySql.Data.Entity
Configuration and initialization
Profiles
Web.config configuration needs to add some content
Entity back to this part of the automatic increase in the installation of the Entity, in fact, do not have to manually modify:
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
In particular, I use is based on Owin Startup Configuration, so in the AppSettings node increases my portal arrangement
<add key="owin:AppStartup" value="Demo.WEBUI.IdentityConfig" />
Identity by curing Entity user information, it is necessary to configure the ConnectionString Entity used, this is part of a common convention .net:
<connectionStrings>
<add name="DefaultConnection" connectionString="server=localhost;database=dbname;uid=root;pwd=123456" providerName="MySql.Data.MySqlClient" />
</connectionStrings>
Entity increase in support for MySql, usually installed by default, it is the SqlServer:
<entityFramework>
<defaultConnectionFactory type="MySql.Data.Entity.MySqlConnectionFactory, MySql.Data.Entity.EF6" />
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.10.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
Also Entity, increase DbProvider, this node does not find a better explanation, but not without words.
<system.data>
<DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient"></remove>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.10.8.0" />
</DbProviderFactories>
</system.data>
Auxiliary class
user
Identity of the user class, can be used directly IdentityUser, can also be extended from his foundation, add custom fields, which will join the User table, I've added three fields.
public class ApplicationUser : IdentityUser
{
public virtual DateTime? LastLoginTime { get; set; }
public virtual DateTime? RegistrationTime { get; set; }
public virtual bool IsEnabled { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
User Management
Here you have configured user authentication settings, such as password strength, cookie expiration time
public class ApplicationUserManager : UserManager<ApplicationUser>
{
private IUserStore<ApplicationUser> _store;
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
_store = store;
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = true,
RequireUniqueEmail = false
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 4,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = false;
//manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
//manager.MaxFailedAccessAttemptsBeforeLockout = 5;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
Log Management
User login method calls, which will depend on the previous stepApplicationUserManager
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
Role Management
Here I did not do more extensions. Identity itself can not role-based, but on Claim permissions control.
Therefore, the role of management is not required.
public class ApplicationRoleManager : RoleManager<IdentityRole, string>
{
public ApplicationRoleManager(IRoleStore<IdentityRole, string> roleStore)
: base(roleStore)
{
}
public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
{
return new ApplicationRoleManager(new RoleStore<IdentityRole, string, IdentityUserRole>(context.Get<ApplicationDbContext>()));
}
}
Initialization class
If the new project with a .net mvc authentication will automatically add the initial class.
I Identity add on an existing project, where the initialization function to initialize class from copy vs created automatically and do not need to delete some of the functions (only local login).
public void Configuration(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Login/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
//app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
//app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
//app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
use
The main focus ApplicationUserManager
, ApplicationSignInManager
user registration login and other operations around the ApplicationRoleManager
user rights management.
registered
The basic logic or from registered vs the default framework, but adds some customized features.
Here I increased user role settings and define the user first registered as an administrator, then for the average user.
Applications, other places will come to judge the operation can be carried out through user roles.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
try
{
var user = new ApplicationUser
{
UserName = model.UserName,
Email = model.Password,
IsEnabled = false, //default disabed
LastLoginTime = DateTime.Now,
RegistrationTime = DateTime.Now
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//init roles
if (!RoleManager.Roles.Any())
{
await RoleManager.CreateAsync(new IdentityRole(UserRole.NORMAL));
await RoleManager.CreateAsync(new IdentityRole(UserRole.ADMINISTRATOR));
}
var adminRoleId = (await RoleManager.FindByNameAsync(UserRole.ADMINISTRATOR)).Id;
var hasAdmin = UserManager.Users.Any(p => p.Roles.Any(x => x.RoleId == adminRoleId));
var userRole = UserRole.NORMAL;
if (!hasAdmin)
{
userRole = UserRole.ADMINISTRATOR;
//make the first admin enabled
user.IsEnabled = true;
await UserManager.UpdateAsync(user);
}
await UserManager.AddToRoleAsync(user.Id, userRole);
//let the user manual log in
//await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
return RedirectToAction("Login", "Login");
}
AddErrors(result);
}
catch (System.Data.Entity.Validation.DbEntityValidationException e)
{
Console.WriteLine(e.ToString());
throw e;
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
log in
When I define a user adds custom fields if the user login time and user enabled.
Here the two fields have made use of.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.UserName);
if (null == user)
{
ModelState.AddModelError("", "登录失败.");
return View(model);
}
if ((!await UserManager.IsInRoleAsync(user.Id, UserRole.ADMINISTRATOR)) && !user.IsEnabled)
{
ModelState.AddModelError("", "账户未启用,请联系管理员.");
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
user.LastLoginTime = DateTime.Now;
await UserManager.UpdateAsync(user);
return RedirectToLocal(returnUrl);
default:
ModelState.AddModelError("", "登录失败.");
return View(model);
}
}
Some extensions use
reset Password
More secure password reset logic is by mail, phone, etc., but I was in the system, the easiest way is to use the administrator set directly.
It is more common to token may be sent to the user by means of connection, by connecting the user to manually change the password.
[HttpPost]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
if (!IsAdmin && !model.UserName.Equals(User.Identity.Name))
{
ModelState.AddModelError("", "无效的用户名");
return View();
}
var user = await UserManager.FindByNameAsync(model.UserName);
if (user == null)
{
return View();
}
var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var result = await UserManager.ResetPasswordAsync(user.Id, token, model.Password);
if (!result.Succeeded)
{
AddErrors(result);
}
else
{
ViewBag.msg = "修改成功!";
}
return View();
}
Access control
In fact, based on Claim by the way, but the system is relatively simple, controlled by Claim somewhat complicated, so I used the Role direct way.
There are two ways, first is the User Properties Controller / WebViewPage the inside by a User.IsInRole(UserRole.ADMINISTRATOR);
method for determining user permissions, here is the incoming Role name, not RoleId.
Another is the ApplicationUserManager UserManager.GetRoles(user.Id)
method, you can get all the Role Name user, and then determines a desired one by Role.
problem
MySql connection is unsuccessful
Sometimes NuGet 8.xx version will be installed for the system MySql.Data
, search, I found that many people encounter problems in this version, downgrade to 6.xx version.
RememberMe function
This problem has not been resolved, when a user logs calls
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
Here's third argument is valid logon settings, set theory false
, the limitation period is Cookie CurrentSession, and true
then, will be a long period of time.
In fact we found that passed true
, the expiration time is 15 days (this relatively normal); and passed false
, would be valid N/A
, never expire. There is no way to solve the discovery.