C# Web开发教程(九)标识框架[Identity]

标识(Identity)框架

  • AuthenticationAuthorization
- Authentication: 认证,你是谁
- Authorization: 权限,你有什么权限
  • 1、标识(Identity)框架: 采用基于角色(Role)的访问控制
- (Role-Based Access Control,简称RBAC)策略,内置了对用户、角色等表的管理以及相关的接口,支持外部登录、2FA等。
- 标识框架使用EF Core对数据库进行操作,因此标识框架支持几乎所有数据库。
  • Identity框架使用
    - IdentityUser<TKey>、IdentityRole<TKey>,TKey代表主键的类型。我们一般编写继承自ldentityUser<TKey>、IdentityRole<TKey>等的自定义类,可以增加自定义属性。
    - NuGet安装: 
    	- Install-Package Microsoft.EntityFrameworkCore -Version 6.0.0
    	- Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 6.0.0
    	- Install-Package Microsoft.EntityFrameworkCore.Tools -Version 6.0.0
    	- Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore  -Version 6.0.0
    - 创建继承自ldentityDbContext的类
    - 可以通过ldDbContext类来操作数据库,不过框架中提供了RoleManager、UserManager等类来简化对数据库的操作。
    - 部分方法的返回值为Task<IdentityResult>类型


// MyUser.cs

using Microsoft.AspNetCore.Identity;

namespace WebApplicationAboutIdentity
{
	// 默认的 IdentityUser 使用 string 类型主键,这里改为 long
    public class MyUser:IdentityUser<long>
    {
    }
}

// MyRole.cs
using Microsoft.AspNetCore.Identity;

namespace WebApplicationAboutIdentity
{
    public class MyRole:IdentityRole<long>
    {
    }
}

// MyDbContext.cs

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace WebApplicationAboutIdentity
{
	// 继承 IdentityDbContext,指定自定义的用户类型、角色类型和主键类型
	// 会自动创建 Identity 相关的所有表(Users、Roles、UserRoles 等)
    public class MyDbContext : IdentityDbContext<MyUser,MyRole,long>
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {
        }
    }
}

// DbContextDesignTimeFactory.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

namespace WebApplicationAboutIdentity
{
    public class DbContextDesignTimeFactory: IDesignTimeDbContextFactory<MyDbContext>
    {
        public MyDbContext CreateDbContext(string[] args)
        {
        	// 配置数据库连接,用于 EF Core 迁移命令
            DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
            string connStr = "Server=.;Database=idtest1;Trusted_Connection=True;";
            builder.UseSqlServer(connStr);
            MyDbContext ctx = new MyDbContext(builder.Options);
            return ctx;
        }
    }
}

// Program.cs

......
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebApplicationAboutIdentity;

var builder = WebApplication.CreateBuilder(args);
......

// 数据库配置
builder.Services.AddDbContext<MyDbContext>(opt =>
{
    opt.UseSqlServer("Server=.;Database=idtest1;Trusted_Connection=True;");
});
// Identity 配置
builder.Services.AddDataProtection();
builder.Services.AddIdentityCore<MyUser>(options =>
{
	// 简化密码策略
    options.Password.RequireDigit = false;
    options.Password.RequiredLength = 6;
    options.Password.RequireLowercase = false;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireUppercase = false;
    // 设置令牌提供程序
    options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
    options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
});
// Identity 构建器
// 指定用户和角色类型,配置使用 Entity Framework 存储,添加默认令牌提供程序,注册用户管理器和角色管理器
IdentityBuilder idBuilder = new(typeof(MyUser),typeof(MyRole),builder.Services);
idBuilder.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders().AddUserManager<UserManager<MyUser>>().AddRoleManager<RoleManager<MyRole>>();

var app = builder.Build();
......

app.Run();

- 最后,作数据库迁移并更新数据库
  • 小结:这个配置为应用程序提供了完整的用户认证和授权基础架构
- 自定义主键类型: 使用 long 代替默认的 string
- 简化的密码策略: 降低了密码复杂度要求
- 完整的 Identity 功能: 包含用户管理、角色管理、令牌生成等
- SQL Server 存储: 使用 Entity Framework Core 和 SQL Server

数据库

Identity Demo 演示---UserManager和RoleManager

  • 演示接口: 使用 ASP.NET Core Identity 进行身份管理和授权的控制器代码
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationAboutIdentity.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class DemoController : ControllerBase
    {
       
		// 依赖注入
        private readonly UserManager<MyUser> userManager;
        private readonly RoleManager<MyRole> roleManager;

        public DemoController(UserManager<MyUser> userManager, RoleManager<MyRole> roleManager)
        {
            this.userManager = userManager;
            this.roleManager = roleManager;
        }

        [HttpPost]
        public async Task<ActionResult<string>> Test1()
        {
        	// 检查 "admin" 角色是否存在,如果不存在就创建该角色
            if (await roleManager.RoleExistsAsync("admin") == false)
            {
                MyRole role = new() { Name = "admin" };
                var result = await roleManager.CreateAsync(role);
                if (!result.Succeeded) return BadRequest("roleManage.CreateAsync failed");
            }
			
			// 查找用户名为 "yzk" 的用户,如果用户不存在,创建新用户并设置密码为 "123456"
            MyUser user1 = await userManager.FindByNameAsync("yzk");

            if (user1 == null)
            {
                user1 = new() { UserName = "yzk" };
                var result = await userManager.CreateAsync(user1, "123456");
                if (!result.Succeeded) return BadRequest("userManage.CreateAsync failed");
            }
			
			// 检查user1是否已在 "admin" 角色中,如果不在,就将用户添加到该角色
            if(!await userManager.IsInRoleAsync(user1, "admin"))
            {
                var result = await userManager.AddToRoleAsync(user1, "admin");
                if (!result.Succeeded) return BadRequest("userManage.AddToRoleAsync failed");
            }

            return "ok";
        }

    }   
}

  • 演示接口: 完成用户密码验证
// CheckPwdRequest.cs

namespace WebApplicationAboutIdentity
{
	// 使用 record 类型定义请求数据模型,这是一个不可变的数据传输对象
	// 含两个属性:UserName(用户名)和 Password(密码)
	// 用途: 匹配前端传过来的UerName和Password
    public record CheckPwdRequest(string UserName, string Password);
}

// 主程序

......
[HttpPost]								// 接收前端传过来的参数
public async Task<ActionResult> CheckPwd(CheckPwdRequest req)
{
    string userName = req.UserName;
    string pwd = req.Password;
    var user = await userManager.FindByNameAsync(userName);
    if(user == null)
    {
        // 在开发环境显示详细错误,生产环境返回模糊错误,避免信息泄露
        if (hostEnvironment.IsDevelopment())
        {
            return BadRequest("用户名不存在");
        }
        else
        {
            return BadRequest();
        }
    }
	
	// 检查用户是否因多次登录失败被锁定,如果锁定,返回锁定结束时间
    if (await userManager.IsLockedOutAsync(user)) return BadRequest("用户已被锁定,结束时间" + user.LockoutEnd);
    // 验证密码哈希
    if (await userManager.CheckPasswordAsync(user,pwd))
    {
    	// 验证成功后重置失败计数(清零)
        await userManager.ResetAccessFailedCountAsync(user);
        return Ok("登录成功");
    }
    else
    {
    	// AccessFailedAsync 记录一次登录失败,达到阈值会自动锁定用户
        await userManager.AccessFailedAsync(user);
        return BadRequest("用户名或密码错误");
    }
}
  • 接口演示: 重置密码流程

    • 生成重置Token,发给用户(短信,邮箱),用户根据Token完成密码的重置
      • 说明事项: 为了演示实例的简单,发送的逻辑改为控制台输出Token
    [HttpPost]
    public async Task<ActionResult> SendResetPasswordToken(string userName)
    {
        var user = await userManager.FindByNameAsync(userName);
        if (user == null)
        {
            return BadRequest("用户名不存在");
        }
        string token = await userManager.GeneratePasswordResetTokenAsync(user);
        Console.WriteLine($"验证码是{token}"); // 验证码是758683
        return Ok();
    }
    
    // Program.cs
    ......
    builder.Services.AddIdentityCore<MyUser>(options =>
    {
        ......
        // 如果注释掉,Token就是一堆长长的字符串
        options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
        ......
    });
    
    • 用户带着用户名,验证码新密码,完成重置密码流程
    [HttpPut]
    public async Task<ActionResult> ResetPassword(string userName,string token,string newPassword)
    {
        var user = await userManager.FindByNameAsync(userName);
        if (user == null) return BadRequest("用户名不存在");
        var result = await userManager.ResetPasswordAsync(user,token,newPassword);
        if (result.Succeeded)
        {
            await userManager.ResetAccessFailedCountAsync(user);
            return Ok("密码重置成功");
        }
        else
        {
            await userManager.AccessFailedAsync(user);
            return BadRequest("密码重置失败");
        }
    }
    
posted @ 2025-10-27 11:14  清安宁  阅读(3)  评论(0)    收藏  举报