您现在的位置是:网站首页> .NET Core

Asp.Net Core Identity 完成注册登录

摘要

 Identity是Asp.Net Core全新的一个用户管理系统,它是一个完善的全面的庞大的框架,提供的功能有:


创建、查询、更改、删除账户信息


验证和授权


密码重置


双重身份认证


支持扩展登录,如微软、Facebook、google、QQ、微信等


提供了一个丰富的API,并且这些API还可以进行大量的扩展


    接下来我们先来看下它的简单使用。首先在我们的DbContext中需要继承自IdentityDbContext。


复制代码

    public class AppDbContext:IdentityDbContext

    {


        public AppDbContext(DbContextOptions<AppDbContext> options):base(options)

        {

        }


        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)

        {

            base.OnModelCreating(modelBuilder);

            modelBuilder.Seed();

        }

    }

复制代码

    然后在Startup中注入其依赖,IdentityUser和IdentityRole是Identity框架自带的两个类,将其绑定到我们定义的AppDbContext中。


services.AddIdentity<IdentityUser, IdentityRole>()

    .AddEntityFrameworkStores<AppDbContext>();

    最后需要添加中间件UseAuthentication。


复制代码

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

    //如果环境是Development,调用 Developer Exception Page

    if (env.IsDevelopment())

    {

        app.UseDeveloperExceptionPage();

    }

    else

    {

        app.UseExceptionHandler("/Error");

    app.UseStatusCodePages();

    app.UseStatusCodePagesWithReExecute("/Error/{0}");

    }

    app.UseStaticFiles();


    app.UseAuthentication();


    app.UseMvc(routes =>

    {

        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

    });

}

复制代码

    接下来我们就可以使用数据库迁移 Add-Migration 来添加迁移,然后update-database我们的数据库。


    可以在数据库中看到其生成的表。


1.png


 


 


 


    在完成数据迁移之后,我们再来看下Identity中如何完成用户的注册和登录。


    我们定义一个ViewModel,然后定义一个AccountController来完成我们的注册和登录功能。Asp.Net Core Identity为我们提供了UserManger来对用户进行增删改等操作,提供了SignInManager的SignInAsync来登录,SignOutAsync来退出,IsSignedIn来判断用户是否已登录等。


复制代码

public class RegisterViewModel

{

    [Required]

    [Display(Name = "邮箱地址")]

    [EmailAddress]

    public string Email { get; set; }


    [Required]

    [Display(Name = "密码")]

    [DataType(DataType.Password)]

    public string Password { get; set; }


    [DataType(DataType.Password)]

    [Display(Name = "确认密码")]

    [Compare("Password",

        ErrorMessage = "密码与确认密码不一致,请重新输入.")]

    public string ConfirmPassword { get; set; }

}

public class LoginViewModel

{


    [Required]

    [EmailAddress]

    public string Email { get; set; }


    [Required]

    [DataType(DataType.Password)]

    public string Password { get; set; }


    [Display(Name = "记住我")]

    public bool RememberMe { get; set; }

}

复制代码

复制代码

using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Identity;

using Microsoft.AspNetCore.Mvc;

using StudentManagement.ViewModels;

using System.Threading.Tasks;


namespace StudentManagement.Controllers

{

    public class AccountController:Controller

    {

        private UserManager<IdentityUser> userManager;

        private SignInManager<IdentityUser> signInManager;


        public AccountController(UserManager<IdentityUser> userManager,

           SignInManager<IdentityUser> signInManager)

        {

            this.userManager = userManager;

            this.signInManager = signInManager;

        }


        [HttpGet]

        public IActionResult Register()

        {

            return View();

        }


        [HttpPost]

        public async Task<IActionResult> Register(RegisterViewModel model)

        {

            if (ModelState.IsValid)

            {

                //将数据从RegisterViewModel复制到IdentityUser

                var user = new IdentityUser

                {

                    UserName = model.Email,

                    Email = model.Email

                };


                //将用户数据存储在AspNetUsers数据库表中

                var result = await userManager.CreateAsync(user, model.Password);

            

                //如果成功创建用户,则使用登录服务登录用户信息

                //并重定向到home econtroller的索引操作

                if (result.Succeeded)

                {

                    await signInManager.SignInAsync(user, isPersistent: false);

                    return RedirectToAction("index", "home");

                }


                //如果有任何错误,将它们添加到ModelState对象中

                //将由验证摘要标记助手显示到视图中

                foreach (var error in result.Errors)

                {

                    if (error.Code== "PasswordRequiresUpper")

                    {

                        error.Description = "密码必须至少有一个大写字母('A'-'Z')。";

                    }


                    //PasswordRequiresUpper

                    //Passwords must have at least one uppercase ('A'-'Z').

                    ModelState.AddModelError(string.Empty, error.Description);

                }

            }

            return View(model);

        }


        [HttpPost]

        public async Task<IActionResult> Logout()

        {

            await signInManager.SignOutAsync();

            return RedirectToAction("index", "home");

        }


        [HttpGet]

        [AllowAnonymous]

        public IActionResult Login()

        {

            return View();

        }


        [HttpPost]

        [AllowAnonymous]

        public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)

        {

            if (ModelState.IsValid)

            {

                var result = await signInManager.PasswordSignInAsync(

                    model.Email, model.Password, model.RememberMe, false);

                if (result.Succeeded)

                {

                    if (!string.IsNullOrEmpty(returnUrl))

                    {

                        return Redirect(returnUrl);

                    }

                    else

                    {

                        return RedirectToAction("index", "home");

                    }

                }

                ModelState.AddModelError(string.Empty, "登录失败,请重试");

            }

            return View(model);

        }

    }

}

复制代码

复制代码

@model RegisterViewModel


@{

    ViewBag.Title = "用户注册";

}


<h1>用户注册</h1>

<div class="row">

    <div class="col-md-12">

        <form method="post">

            <div asp-validation-summary="All" class="text-danger"></div>

            <div class="form-group">

                <label asp-for="Email"></label>

                <input asp-for="Email" class="form-control" />

                <span asp-validation-for="Email" class="text-danger"></span>

            </div>

            <div class="form-group">

                <label asp-for="Password"></label>

                <input asp-for="Password" class="form-control" />

                <span asp-validation-for="Password" class="text-danger"></span>

            </div>

            <div class="form-group">

                <label asp-for="ConfirmPassword"></label>

                <input asp-for="ConfirmPassword" class="form-control" />

                <span asp-validation-for="ConfirmPassword" class="text-danger"></span>

            </div>

            <button type="submit" class="btn btn-primary">注册</button>

        </form>

    </div>

</div>

复制代码

复制代码

@model LoginViewModel


@{

    ViewBag.Title = "用户登录";

}


<h1>用户登录</h1>


<div class="row">

    <div class="col-md-12">

        <form method="post">

            <div asp-validation-summary="All" class="text-danger"></div>

            <div class="form-group">

                <label asp-for="Email"></label>

                <input asp-for="Email" class="form-control" />

                <span asp-validation-for="Email" class="text-danger"></span>

            </div>

            <div class="form-group">

                <label asp-for="Password"></label>

                <input asp-for="Password" class="form-control" />

                <span asp-validation-for="Password" class="text-danger"></span>

            </div>

            <div class="form-group">

                <div class="checkbox">

                    <label asp-for="RememberMe">

                        <input asp-for="RememberMe" />

                        @Html.DisplayNameFor(m => m.RememberMe)

                    </label>

                </div>

            </div>

            <button type="submit" class="btn btn-primary">登录</button>

        </form>

    </div>

</div>

复制代码

    在实际工作中,我们需要配置密码的复杂度来增强用户信息的安全性。而Asp.Net Core Identity也默认也提供了一套机制PasswordOptions,可以查看其源码。


https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/PasswordOptions.cs


    但是有时候我们需要自定义我们的密码校验模式,这时候可以在Startup中注入


复制代码

services.Configure<IdentityOptions>(options =>

{

    options.Password.RequiredLength = 6;

    options.Password.RequiredUniqueChars = 3;

    options.Password.RequireUppercase = false;

    options.Password.RequireLowercase = false;

    options.Password.RequireNonAlphanumeric = false;

});

复制代码

    同时,我们希望我们在注册的时候提示错误信息时使用中文显示,可以定义一个继承IdentityErrorDescriber的类。


复制代码

using Microsoft.AspNetCore.Identity;


namespace StudentManagement.Middleware

{

    public class CustomIdentityErrorDescriber : IdentityErrorDescriber

    {

        public override IdentityError DefaultError()

        {

            return new IdentityError { Code = nameof(DefaultError), Description = $"发生了未知的故障。" };

        }


        public override IdentityError ConcurrencyFailure()

        {

            return new IdentityError { Code = nameof(ConcurrencyFailure), Description = "乐观并发失败,对象已被修改。" };

        }


        public override IdentityError PasswordMismatch()

        {

            return new IdentityError { Code = nameof(PasswordMismatch), Description = "密码错误" };

        }


        public override IdentityError InvalidToken()

        {

            return new IdentityError { Code = nameof(InvalidToken), Description = "无效的令牌." };

        }


        public override IdentityError LoginAlreadyAssociated()

        {

            return new IdentityError { Code = nameof(LoginAlreadyAssociated), Description = "具有此登录的用户已经存在." };

        }


        public override IdentityError InvalidUserName(string userName)

        {

            return new IdentityError { Code = nameof(InvalidUserName), Description = $"用户名'{userName}'无效,只能包含字母或数字." };

        }


        public override IdentityError InvalidEmail(string email)

        {

            return new IdentityError { Code = nameof(InvalidEmail), Description = $"Email '{email}' is invalid." };

        }


        public override IdentityError DuplicateUserName(string userName)

        {

            return new IdentityError { Code = nameof(DuplicateUserName), Description = $"User Name '{userName}' is already taken." };

        }


        public override IdentityError DuplicateEmail(string email)

        {

            return new IdentityError { Code = nameof(DuplicateEmail), Description = $"Email '{email}' is already taken." };

        }


        public override IdentityError InvalidRoleName(string role)

        {

            return new IdentityError { Code = nameof(InvalidRoleName), Description = $"Role name '{role}' is invalid." };

        }


        public override IdentityError DuplicateRoleName(string role)

        {

            return new IdentityError { Code = nameof(DuplicateRoleName), Description = $"Role name '{role}' is already taken." };

        }


        public override IdentityError UserAlreadyHasPassword()

        {

            return new IdentityError { Code = nameof(UserAlreadyHasPassword), Description = "User already has a password set." };

        }


        public override IdentityError UserLockoutNotEnabled()

        {

            return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "Lockout is not enabled for this user." };

        }


        public override IdentityError UserAlreadyInRole(string role)

        {

            return new IdentityError { Code = nameof(UserAlreadyInRole), Description = $"User already in role '{role}'." };

        }


        public override IdentityError UserNotInRole(string role)

        {

            return new IdentityError { Code = nameof(UserNotInRole), Description = $"User is not in role '{role}'." };

        }


        public override IdentityError PasswordTooShort(int length)

        {

            return new IdentityError { Code = nameof(PasswordTooShort), Description = $"密码必须至少是{length}字符." };

        }


        public override IdentityError PasswordRequiresNonAlphanumeric()

        {

            return new IdentityError { Code = nameof(PasswordRequiresNonAlphanumeric), Description = "密码必须至少有一个非字母数字字符."

            };

        }


        public override IdentityError PasswordRequiresDigit()

        {

            return new IdentityError { Code = nameof(PasswordRequiresDigit), Description = $"密码必须至少有一个数字('0'-'9')." };

        }



        public override IdentityError PasswordRequiresUniqueChars(int uniqueChars)

        {

            return new IdentityError { Code = nameof(PasswordRequiresUniqueChars), Description = $"密码必须使用至少不同的{uniqueChars}字符。" };

        }


        public override IdentityError PasswordRequiresLower()

        {

            return new IdentityError { Code = nameof(PasswordRequiresLower), Description = "密码必须至少有一个小写字母('a'-'z')." };

        }


        public override IdentityError PasswordRequiresUpper()

        {

            return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = "密码必须至少有一个大写字母('A'-'Z')." };

        }

    }

}

复制代码

    最后需要在注入Identity的时候添加上这个类


services.AddIdentity<IdentityUser, IdentityRole>()

    .AddErrorDescriber<CustomIdentityErrorDescriber>()

    .AddEntityFrameworkStores<AppDbContext>();

    完成登录后,我们需要对访问资源进行授权,需要在controller或者action上使用Authorize属性来标记,也可以使用AllowAnonymous来允许匿名访问,在项目中使用授权需要引入中间件UseAuthentication


 app.UseAuthentication();

    但是如果项目中有很多controller需要添加Authorize属性,我们可以在startup中添加全局的授权,代码如下。


复制代码

services.AddMvc(config => {

    var policy = new AuthorizationPolicyBuilder()

                    .RequireAuthenticatedUser()

                    .Build();

    config.Filters.Add(new AuthorizeFilter(policy));

});

复制代码

    一般在用户登录成功后需要重定向到原始的 URL,这个通过请求参数中带returnUrl来实现,但是如果没有判断是否本地的Url时则会引发开放式重定向漏洞。


    解决开放式重定向漏洞的方式也很简单,就是在判断的时候添加Url.IsLocalUrl或者直接return LocalRedirect。


if (Url.IsLocalUrl(returnUrl))

{


}

return LocalRedirect(returnUrl);


Top