尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Sqlserver + JWT 认证的 .NET6 WebAPI 从零搭建实战

发布时间:2026/9/26 5:28:17

资讯中心
01
ARTICLE

Sqlserver + JWT 认证的 .NET6 WebAPI 从零搭建实战

Sqlserver + JWT 认证的 .NET6 WebAPI 从零搭建实战
简介这是一套基于.NET 6的Web API实战示例面向熟悉C#基础、希望掌握ASP.NET Core与SQL Server数据交互及JWT认证的初中级开发者。项目围绕增删改查接口展开完整演示从数据库设计到Swagger接口文档、JWT登录鉴权、依赖注入与ORM持久化方案的落地过程。资源包共665个文件包含项目源码cs、csproj、sln、编译产物dll、pdb、exe、配置与文档json、txt、xml等压缩包约34.81MB目录结构清晰便于按模块对照学习。已有1060人学习下载。通过学习可以直观理解RESTful API设计、数据库迁移、异常处理与安全防护等关键点适合毕业设计、企业级项目起步或.NET6新技术学习。1. 从零搭一套带认证的 .NET6 WebAPI说清 Sqlserver JWT 这条链路的真实工程量接手一个内部管理系统需求很直白前端要一套能登录、能增删改查的后端接口数据库用 Sqlserver认证用 JWT。听起来是 .NET 开发的基本功但真把 .NET6 WebAPI Sqlserver JWT 从空项目跑到线上中间涉及的连接配置、Token 有效期、EF Core 跟踪、IIS 发布每一步都可能卡人。这套资源就是把这整条链路打成一个可复现的项目数据库建表脚本、仓储层、JWT 签发与校验、控制器 CRUD 都在里面照着配置就能跑。适合刚接触 .NET6 的转岗开发也适合接了老系统维护但没从头建过认证接口的从业者。这篇文章会把每一步的参数和踩坑写透。2. 先打数据地基Sqlserver 连接字符串、EF Core 选型与仓储落点2.1 为什么用 EF Core 而不是 SqlSugar选数据访问层方案时网上吵得最多的就是 EF Core 和 SqlSugar。这套资源里用的是 EF Core理由不复杂.NET6 WebAPI 项目模板自带 EF Core 支持官方文档全跟迁移工具配合得最好。SqlSugar 的语法更轻但如果你要维护的是一个长期项目EF Core 的 LINQ 表达式树在做复杂查询时更顺而且微软的更新节奏稳定踩坑之后能在 StackOverflow 上找到大量同款问题。一个反直觉的点EF Core 的性能并不差。很多人一听 ORM 就觉得慢实际上对于增删改查这种场景EF Core 的查询计划缓存做得很好真正慢的往往是 N1 查询跟 ORM 本身没关系。把这个层面想明白后面写仓储层的时候就不会为了“性能”去手写一堆 SqlConnection。2.2 连接字符串与 appsettings.json 配置新建 .NET6 WebAPI 项目后先打开 appsettings.json把连接字符串和 JWT 配置一次写全{ ConnectionStrings: { DefaultConnection: Serverlocalhost;DatabaseDotnet6Demo;User Idsa;Password你的密码;TrustServerCertificateTrue;EncryptFalse }, Jwt: { Issuer: Dotnet6Demo.Server, Audience: Dotnet6Demo.Client, SecretKey: your-secret-key-here-must-be-long-enough-32bytes, ExpireMinutes: 120, RefreshExpireDays: 7 }, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: * }连接字符串里的EncryptFalse和TrustServerCertificateTrue是.SqlServer 2019/2022 连接时最容易踩的坑。新版 SqlClient 驱动默认强制加密如果你本地的 Sqlserver 没配 SSL 证书不加这两个参数就报证书链错误。JWT 配置里SecretKey用来签名 Token必须超过 32 字节否则 HMAC-SHA256 算法直接拒绝。Issuer和Audience是 Token 的发行方和受众前后端分离时前端拿到的 Token 里会带着这两个字段方便调试时用 jwt.io 查看。2.3 数据库建表与实体映射新建 Sqlserver 数据库和一张核心表在这套资源里领域的例子是“用户管理”。建表脚本直接跑在 Sqlserver Management Studio 里CREATE DATABASE Dotnet6Demo; GO USE Dotnet6Demo; GO CREATE TABLE [dbo].[Users] ( [Id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY, [UserName] NVARCHAR(50) NOT NULL, [PasswordHash] NVARCHAR(200) NOT NULL, [Email] NVARCHAR(100) NULL, [CreatedAt] DATETIME2(3) NOT NULL DEFAULT GETDATE(), [UpdatedAt] DATETIME2(3) NULL );PasswordHash字段长度给到 200是因为后面做 BCrypt 哈希时标准的哈希串有 60 个字符但为了兼容未来的算法升级预留足够的宽度。IDENTITY(1,1)是 Sqlserver 的自增主键ID 从 1 开始每次加 1。实体类直接对应这张表public class User { public int Id { get; set; } public string UserName { get; set; } public string PasswordHash { get; set; } public string? Email { get; set; } public DateTime CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } }DateTime?表示可空类型对应数据库里的 NULL。EF Core 的约定大于配置这个实体放在 Models 文件夹下不用写注解也能通过DbContext的 DbSet 属性映射到 Users 表。2.4 DbContext 与仓储接口的定义创建 ApplicationDbContextpublic class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptionsApplicationDbContext options) : base(options) { } public DbSetUser Users { get; set; } }Program.cs 里注册builder.Services.AddDbContextApplicationDbContext(options options.UseSqlServer( builder.Configuration.GetConnectionString(DefaultConnection)));DbContextOptionsApplicationDbContext是构造器注入的关键。Program.cs 里的AddDbContext默认作用域为 Scoped意思是每个 HTTP 请求周期内同一个 DbContext 实例被复用这能避免并发情况下上下文状态混乱。仓储接口定义在这里要格外注意粒度。接口设计成四个方法足够用配合这套资源的增删改查目标public interface IUserRepository { TaskUser? GetByIdAsync(int id); TaskListUser GetAllAsync(); Task AddAsync(User user); Task UpdateAsync(User user); Task DeleteAsync(User user); }不把数据库上下文直接暴露给控制器是这套资源用的分层方式。控制器只管接收参数、校验、返回状态码数据怎么取怎么映射是仓储层的事。入库时的外国人名带缩写或撇号如 OBrien、J. Smith在连接字符串或 SQL 拼接上处理不当就会触发格式错误。设计仓储基座时我给每个实体都配了一列显式 GUID 名这样能彻底避免子系统对接时因名称语义撞车导致的删除错位。但接下来的关键问题是让 JWT 不再是无状态纸片而是每次请求可验证的真实身份。3. JWT 认证链路从 Token 结构到中间件校验的完整落地3.1 JWT 的三段式结构与密钥配置JWT Token 由 Header、Payload、Signature 三段组成中间用点号分隔。Header 里声明算法Payload 里放用户信息Signature 由服务端密钥加工出来。这里的关键认知是Payload 只是 Base64 编码不是加密的任何人拿到 Token 解出来就能看到用户名和过期时间。所以敏感数据别放里面。在 Program.cs 里配置认证服务builder.Services.AddAuthentication(options { options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, ValidateAudience true, ValidateLifetime true, ValidateIssuerSigningKey true, ValidIssuer builder.Configuration[Jwt:Issuer], ValidAudience builder.Configuration[Jwt:Audience], IssuerSigningKey new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration[Jwt:SecretKey])) }; });ValidateLifetime true是默认行为但我要强调一点这个配置只管校验 Token 的 exp 字段是否过期不管 Token 是否被撤销。如果用户在退出登录时前端只是丢弃 Token这个 Token 在有效期内还是能用的。所以后面做注销接口时不能只靠 JWT 本身要加白名单或黑名单机制。3.2 登录接口与 Token 签发登录接口要做两件事校验用户名和密码签发 Token。密码匹配用的是 BCrypt[HttpPost(login)] public async TaskIActionResult Login([FromBody] LoginRequest request) { var user await _userRepository.GetByUserNameAsync(request.UserName); if (user null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) { return Unauthorized(new { message 用户名或密码错误 }); } var token GenerateToken(user); return Ok(new { token, expireAt DateTime.UtcNow.AddMinutes(120) }); }BCrypt.Verify的调用方式很直接数据库里存的是哈希串传入明文密码做比对。BCrypt 是故意慢的哈希算法单次验证大约 100ms 左右对暴力破解的抵抗性比 MD5 强得多。GenerateToken 方法private string GenerateToken(User user) { var claims new[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new Claim(JwtRegisteredClaimNames.Name, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config[Jwt:SecretKey])); var creds new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token new JwtSecurityToken( issuer: _config[Jwt:Issuer], audience: _config[Jwt:Audience], claims: claims, expires: DateTime.Now.AddMinutes(120), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }Jti是一个随机 ID用来唯一标识这个 Token。如果以后要做 Token 撤销功能jti 就是黑名单的索引。expires用的时间是 120 分钟和配置里的ExpireMinutes对齐。3.3 中间件校验顺序与自定义策略Program.cs 里中间件的注册顺序是很多人的知识盲区包括我自己也在这上面吃过亏。正确顺序是app.UseAuthentication(); app.UseAuthorization();这两行必须在 UseRouting 之后、UseEndpoints 之前。如果反了认证中间件不会拦截任何请求而且不会报错只是所有接口都匿名可以访问。这种静默失败最坑。还要注意在 .NET6 的 Minimal API 里app.MapControllers 的位置也有讲究。常规顺序是先用 app.MapControllers() 再跑 UseAuthentication实际正确姿势是 UseAuthentication 和 UseAuthorization 必须在 MapControllers 之前。这个错误不会触发任何编译警告项目照样启动直到你用 Postman 测登录保护接口时才发现所有人都能进。增删改查的控制器并不能幸免于认证中间件的配置题。接下来进入接口层看看 DTO 的正确写法。4. 增删改查实战DTO、控制器与统一返回格式的一次性写对4.1 为什么需要 DTO 而不是直接暴露实体很多新手图省事直接把 User 实体放在控制器里接收参数。这样做会在两个问题上翻车一是前端传了不属于实体的字段比如把 Id 传进来导致 EF Core 报并发冲突二是 PasswordHash 字段被序列化返回给前端登录凭据直接泄露。DTO 在这套资源里的定位就是隔离层。以创建用户为例接收参数的 DTOpublic class CreateUserRequest { [Required(ErrorMessage 用户名不能为空)] [StringLength(50, MinimumLength 3)] public string UserName { get; set; } [Required(ErrorMessage 密码不能为空)] [StringLength(100, MinimumLength 6)] public string Password { get; set; } [EmailAddress] public string? Email { get; set; } }返回给前端的 DTO 则不带密码字段避免敏感数据出网。多了一个[ApiController]属性模型验证失败会自动返回 400 响应不用手动写唇舌校验逻辑。4.2 控制器里 CRUD 的四个标准动作在 UsersController 里增删改查四个方法要按 RESTful 风格写[HttpGet] public async TaskActionResultListUserDto GetAll() { var users await _userRepository.GetAllAsync(); var result users.Select(u new UserDto { Id u.Id, UserName u.UserName, Email u.Email, CreatedAt u.CreatedAt }).ToList(); return Ok(result); } [HttpGet({id:int})] public async TaskActionResultUserDto GetById(int id) { var user await _userRepository.GetByIdAsync(id); if (user null) { return NotFound(new { message 用户不存在 }); } return Ok(new UserDto { Id user.Id, UserName user.UserName }); } [HttpPost] public async TaskActionResultUserDto Create([FromBody] CreateUserRequest request) { var existing await _userRepository.GetByUserNameAsync(request.UserName); if (existing ! null) { return Conflict(new { message 用户名已存在 }); } var user new User { UserName request.UserName, PasswordHash BCrypt.Net.BCrypt.HashPassword(request.Password), Email request.Email, CreatedAt DateTime.UtcNow }; await _userRepository.AddAsync(user); return CreatedAtAction(nameof(GetById), new { id user.Id }, user); } [HttpPut({id:int})] public async TaskIActionResult Update(int id, [FromBody] UpdateUserRequest request) { if (id ! request.Id) { return BadRequest(new { message 路径 ID 与请求体 ID 不一致 }); } var user await _userRepository.GetByIdAsync(id); if (user null) { return NotFound(new { message 用户不存在 }); } user.Email request.Email; user.UpdatedAt DateTime.UtcNow; await _userRepository.UpdateAsync(user); return NoContent(); } [HttpDelete({id:int})] public async TaskIActionResult Delete(int id) { var user await _userRepository.GetByIdAsync(id); if (user null) { return NotFound(new { message 用户不存在 }); } await _userRepository.DeleteAsync(user); return NoContent(); }注意事项有几个挡住新手的问题。第一[HttpPut]不是只管传参路径里的 id 和请求体里的 Id 必须一致这一步拦截在后端的防错意义上非常有价值。第二CreatedAtAction返回的 201 状态码Location 头指向 GetById 路由这是在告诉前端资源已经创建成功在哪里能取到这个资源。第三删除成功后返回 204 NoContent别返回 200 加空 JSONRESTful 语义对不上。4.3 并发冲突的响应处理EF Core 在并发更新时默认有乐观并发控制需要在实体里加一个字段配合。更常见的做法是在 UpdateAsync 里捕获 DbUpdateConcurrencyExceptionpublic async Task UpdateAsync(User user) { try { _context.Users.Update(user); await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { throw new InvalidOperationException(数据已被其他请求修改请刷新后重试); } }_context.Users.Update(user)会把整个实体标记为 Modified所有列都会更新。如果你只想更新个别字段应该重新加载实体后逐个赋值否则前端传入的 NULL 可能会覆盖数据库里有值的字段。这一点在踩坑章还会展开。但除了上述设计期的取舍运行期的故障往往更让人措手不及。Sqlserver 与 JWT 的组合在真实环境里有一批高频坑值得专门梳理。5. 避坑指南Sqlserver 与 JWT 场景下五个高频翻车点5.1 字符串转数字的隐式转换问题现象在 Sqlserver 里执行带字符串参数的查询时报错提示类型转换失败。原因EF Core 生成的 SQL 会把字符串参数直接传给数据库如果字段类型是 INT而传入的值是空字符串或非数字内容Sqlserver 无法完成隐式转换。解决在进仓储层之前做类型校验不要依赖数据库兜底。特别是查询接口前端传pageIndexabc时要保证模型验证先把这道门拦住。5.2 JWT 密钥太短导致启动即报错现象项目启动时抛出IDX10720: Unable to create KeyedHashAlgorithm异常。原因JWT 配置里的SecretKey长度小于 32 字节HMAC-SHA256 的密钥长度必须达标。把 SecretKey 设为 16 字节也不行那是 MD5 的强度。解决用一段至少 32 字符的随机字符串做密钥。建议用 PowerShell 生成在密钥管理上把它放到环境变量或用户机密里别直接写进 appsettings.json 提交到代码仓库。5.3 EF Core 更新操作覆盖了未被修改的字段现象用户在编辑界面只改了 Email保存后 UserName 变成了空值。原因控制器里_context.Users.Update(user)将状态一次性标为 ModifiedSQL 生成的 UPDATE 语句包含所有列而 DTO 没有接收 UserName 字段导致空值覆盖。解决先按 ID 重新从数据库加载实体再只更新 DTO 里明确出现的字段。这个方案会多查一次数据库但能避免脏写。5.4 Sqlserver 事务日志暴涨现象数据库运行一段时间后日志文件膨胀到几十 GB磁盘被耗尽。原因数据库处于简单恢复模式还好完整恢复模式下超过一定规模日志不会自动截断。系统频繁的增删改操作都会写日志日志文件只增不减。解决定期做差异备份备份之后日志点会被截断。把数据库恢复到简单模式是一种即时止血方案适合测试环境生产交接时我也会在恢复模型和日志备份策略上留下清晰的备注避免后来的人对着膨胀日志无从下手。5.5 JWT 中间件不生效接口全部匿名可访问现象加了[Authorize]特性后未登录仍能正常调用接口也不报错。原因Program.cs 里中间件注册顺序错误。很多教程只写 AddAuthentication 然后忘了 UseAuthentication或把 UseAuthentication 写在 MapControllers 之后JWT 的 Bearer Handler 从未被执行。解决检查中间件注册顺序UseAuthentication 必须出现在 UseRouting 之后、MapControllers 之前。我一般会在 Program.cs 里加注释标明顺序。调试时看响应头里有没有WWW-Authenticate: Bearer没有就是没套上认证中间件。排查边界问题连不上是批次里最常见也最没头绪的一条。但是调试链路走通后发布与部署才是工程闭环的最后一公里。6. 发布与自检用 Postman 走通认证流程再上 IIS 部署的收尾细节6.1 Postman 里建立环境变量完整走一遍认证CRUD调试接口时在 Postman 里新建环境变量把请求地址和 Token 串起来。登录成功后在 Tests 脚本里自动写入 Tokenconst json pm.response.json(); pm.collectionVariables.set(token, json.token);这样后续的增删改查请求只需在 Authorization 标签里选择 Bearer Token 类型值填{{token}}即可。如果 Token 过期401 响应会提醒重新登录。这条链路从头跑到尾大约 3 分钟能确认接口层没有问题。我习惯给 Postman 收藏集加一个环境文件夹分成本地、测试、线上三套环境环境切换时只需在右上角下拉切换不用改任何一个 URL。生产环境的环境变量里绝不放密钥只放连接字符串和内存不落盘的临时凭据。6.2 发布到 IISruntime 版本和宿主机权限发布配置用框架依赖模式省体积但目标服务器必须安装对应的 ASP.NET Core Runtime 和 Hosting Bundle。很多部署翻车都发生在这一步服务器只装了 .NET Runtime 没有装 Hosting BundleIIS 反向代理标志没装上请求进到 IIS 就变成 502。发布流程是这样的VS 里右键项目发布目标选“文件夹”用 Release 配置生成。把发布出的文件拷到服务器上在 IIS 里新建应用程序池.NET CLR 版本选“无托管代码”。站点物理路径指到发布文件夹绑定端口后重启站点。6.3 验证与收尾习惯部署完成后用 Postman 跑一条全链路脚本登录拿 Token、创建用户、查询用户、更新用户、删除用户。确认 Token 过期后接口正确返回 401再检查 Sqlserver 里的 Users 表数据与数据库操作后的预期一致。从那以后我每次交付 .NET6 WebAPI 项目都强制走一遍这个流程本机跑通、Postman 自动化验证、IIS 空应用程序池部署、日志告警检查。这套资源从建表到 JWT 再到 CRUD 就是一个完整闭环关键是每个环节的配置都有据可查。把坑提前踩完省下来的时间足够你多看几个需求文档。希望这篇笔记帮到你落地这套链路。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。