基于.NetCore开发博客项目 StarBlog - (27) 使用JWT保护接口

这篇具有很好参考价值的文章主要介绍了基于.NetCore开发博客项目 StarBlog - (27) 使用JWT保护接口。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

这是StarBlog系列在2023年的第二篇更新😂

这几个月都在忙,更新变得很不勤快,但是拖着不更新我的心里更慌,很久没写,要开头就变得很难😑

说回正题,之前的文章里,我们已经把博客关键的接口都开发完成了,但还少了一个最关键的「认证授权」,少了这东西,网站就跟筛子一样,谁都可以来添加和删除数据,乱套了~

关于「认证授权」的知识,会比较复杂,要学习这块的话,建议分几步:

  • 基础概念
  • AspNetCore 的 Identity 框架
  • 其他框架,如 IdentityServer

关于基础概念可以看看我之前写的这篇: Asp-Net-Core学习笔记:身份认证入门

PS:Identity 框架的还没写好😂

为了避免当复读机,本文就不涉及太多概念的东西了,建议先看完上面那篇再来开始使用JWT~

JWT

前面介绍文章的CRUD接口时,涉及到修改的接口,都加了 [Authorize] 特性,表示需要登录才能访问,本文就以最简单的方式来实现这个登录认证功能。

在 AspNetCore 中,使用 JWT 的工作流程大概如下:

  • JWT就是一个Base64编码的字符串,分为 head/payload/sign 三个部分(sign签名是使用特定秘钥生成的,别人无法伪造,所以就算修改了payload部分的信息,后端校验也不会通过)
  • 用户登录时,后端可以在里面存一些类似用户ID、邮箱、手机号之类的数据,然后把这串东西返回给前端存储,注意不要把不能被客户端知道的信息放在里面(也可以对payload进行加密)
  • 之后调用需要登录的接口时,都要带上这个JWT(一般是放在 HTTP Header 里面)
  • 这串东西只有后端能解析,后端拿到之后就知道用户的身份了

JWT 还有其他一些特性,比如说是没有状态的,这就很符合我们用的 RESTFul 接口了,不像传统使用 session 和 cookies 那样,原版 JWT 只要签发之后,在有效期结束前就不能取消,用户也没法注销,为了避免泄露 JWT token 导致安全问题,一般过期时间都设置得比较短。(这个不能取消的问题,也是可以通过曲线救国解决的,不过不在本文的讨论范围哈)

初步接触 JWT

OK,说了那么多,还是开始来写代码吧

生成 JWT

要生成的话很简单,不需要什么额外的配置,几行代码就搞定了

public LoginToken GenerateLoginToken(User user) {
  var claims = new List<Claim> {
    new(JwtRegisteredClaimNames.Sub, user.Id), // User.Identity.Name
    new(JwtRegisteredClaimNames.GivenName, user.Name),
    new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // JWT ID
  };
  var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("jwt key"));
  var signCredential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
  var jwtToken = new JwtSecurityToken(
    issuer: "jwt issuer 签发者",
    audience: "jwt audience 接受者",
    claims: claims,
    expires: DateTime.Now.AddDays(7),
    signingCredentials: signCredential);

  return new LoginToken {
    Token = new JwtSecurityTokenHandler().WriteToken(jwtToken),
    Expiration = TimeZoneInfo.ConvertTimeFromUtc(jwtToken.ValidTo, TimeZoneInfo.Local)
  };
}

最开始的 claims 就是前面说的后端往JWT里面存的数据

"The set of claims associated with a given entity can be thought of as a key. The particular claims define the shape of that key; much like a physical key is used to open a lock in a door. In this way, claims are used to gain access to resources." from MSDN

Claim 的构造方法可以接收 keyvalue 参数,都是字符串

对于 key ,.Net 提供了一些常量,在 JwtRegisteredClaimNamesClaimTypes 类里边,这俩的区别就是后者是老的,一般在Windows体系下使用,比如说同样是 Name 这个 key

  • ClaimTypes.Name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
  • JwtRegisteredClaimNames.Name = "name"

我们是在 JWT 里面设置 Claim,用 JwtRegisteredClaimNames 就好了

参考:https://stackoverflow.com/questions/50012155/jwt-claim-names

从 JWT 中读取信息

也就是读取放在里面的各个 Claim

在正确配置 Authentication 服务和 JwtBearer 之后,已登录的客户端请求过来,后端可以在 Controller 里面拿到 JWT 数据

像这样

var name = HttpContext.User.FindFirst(JwtRegisteredClaimNames.Name)?.Value;

还可以用 System.Security.Claims.PrincipalExtensions 的扩展方法 FindFirstValue 直接拿到字符串值。

吐槽:如果对应的 Claim 不存在的话,这个扩展方法返回的值是 null,但不知道为啥,他源码用的是 string 作为返回值类型,而不是 string? ,真是令人遗憾

使用 JWT 保护接口

了解 JWT 的使用方式之后,终于可以把 JWT 应用到博客项目中了~

配置JWT参数

为了避免硬编码,我们把 JWT 需要的 Issuer, Audience, Key 三个参数写在配置里面

形式如下

"Auth": {
  "Jwt": {
    "Issuer": "starblog",
    "Audience": "starblog-admin-ui",
    "Key": "F2REaFzQ6xA9k77EUDLf9EnjK5H2wUot"
  }
}

接着需要定义一个类来方便映射配置。

StarBlog.Web/Models/Config 下添加 Auth.cs

public class Auth {
  public Jwt Jwt { get; set; }
}

public class Jwt {
  public string Issuer { get; set; }
  public string Audience { get; set; }
  public string Key { get; set; }
}

注册一下

builder.Services.Configure<Auth>(configuration.GetSection(nameof(Auth)));

配置 Authentication 服务

这部分代码比较多,写成扩展方法,避免 Program.cs 文件代码太多

添加 StarBlog.Web/Extensions/ConfigureAuth.cs 文件

public static class ConfigureAuth {
  public static void AddAuth(this IServiceCollection services, IConfiguration configuration) {
    services.AddScoped<AuthService>();
    services.AddAuthentication(options => {
      options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
      options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
      .AddJwtBearer(options => {
        var authSetting = configuration.GetSection(nameof(Auth)).Get<Auth>();
        options.TokenValidationParameters = new TokenValidationParameters {
          ValidateAudience = true,
          ValidateLifetime = true,
          ValidateIssuer = true,
          ValidateIssuerSigningKey = true,
          ValidIssuer = authSetting.Jwt.Issuer,
          ValidAudience = authSetting.Jwt.Audience,
          IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSetting.Jwt.Key)),
          ClockSkew = TimeSpan.Zero
        };
      });
  }
}

然后在 Program.cs 里,需要使用这个扩展方法来注册服务

builder.Services.AddAuth(builder.Configuration);

还得配置一下中间件,这个顺序很重要,需要使用身份认证保护的接口或资源,必须放到这俩 Auth... 中间件的后面。

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// ...
app.MapControllerRoute(...);
app.Run();

封装登录逻辑

还是那句话,为了方便使用balabala……

新建 StarBlog.Web/Services/AuthService.cs 文件

public class AuthService {
  private readonly Auth _auth;
  private readonly IBaseRepository<User> _userRepo;

  public AuthService(IOptions<Auth> options, IBaseRepository<User> userRepo) {
    _auth = options.Value;
    _userRepo = userRepo;
  }

  public LoginToken GenerateLoginToken(User user) {
    var claims = new List<Claim> {
      new(JwtRegisteredClaimNames.Sub, user.Id), // User.Identity.Name
      new(JwtRegisteredClaimNames.GivenName, user.Name),
      new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // JWT ID
    };
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_auth.Jwt.Key));
    var signCredential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    var jwtToken = new JwtSecurityToken(
      issuer: _auth.Jwt.Issuer,
      audience: _auth.Jwt.Audience,
      claims: claims,
      expires: DateTime.Now.AddDays(7),
      signingCredentials: signCredential);

    return new LoginToken {
      Token = new JwtSecurityTokenHandler().WriteToken(jwtToken),
      Expiration = TimeZoneInfo.ConvertTimeFromUtc(jwtToken.ValidTo, TimeZoneInfo.Local)
    };
  }
}

因为篇幅关系,只把关键的生成 JWT 代码贴出来,还有一些获取用户信息啥的代码,还不是最终版本,接下来随时会修改,而且也比较简单,就没有放出来~

再来写个登录接口

添加 StarBlog.Web/Apis/AuthController.cs 文件

[ApiController]
[Route("Api/[controller]")]
[ApiExplorerSettings(GroupName = ApiGroups.Auth)]
public class AuthController : ControllerBase {
  private readonly AuthService _authService;

  public AuthController(AuthService authService) {
    _authService = authService;
  }

  /// <summary>
  /// 登录
  /// </summary>
  [HttpPost]
  [ProducesResponseType(typeof(ApiResponse<LoginToken>), StatusCodes.Status200OK)]
  public async Task<ApiResponse> Login(LoginUser loginUser) {
    var user = await _authService.GetUserByName(loginUser.Username);
    if (user == null) return ApiResponse.Unauthorized("用户名不存在");
    if (loginUser.Password != user.Password) return ApiResponse.Unauthorized("用户名或密码错误");
    return ApiResponse.Ok(_authService.GenerateLoginToken(user));
  }
}

之后我们请求这个接口,如果用户名和密码正确的话,就可以拿到 JWT token 和过期时间

{
  "statusCode": 200,
  "successful": true,
  "message": "Ok",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR123I6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRlYWxpIiwibmFC1kYJ9.DaJEmBAVdXks8MOedVee4xxrB-RvUSg2wIJGc30HGkk",
    "expiration": "2023-05-04T22:29:04+08:00"
  },
  "errorData": null
}

接下来,请求添加了 [Authorize] 的接口时,需要在 HTTP header 里面加上:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR123I6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRlYWxpIiwibmFC1kYJ9.DaJEmBAVdXks8MOedVee4xxrB-RvUSg2wIJGc30HGkk

配置swagger支持

加了 [Authorize] 之后,在swagger里就没法调试接口了,得用 postman 之类的工具,添加 HTTP header

不过swagger这么好用的工具肯定不会那么蠢,它是可以配置支持 JWT 的

添加 nuget 包 Swashbuckle.AspNetCore.Filters

然后编辑 StarBlog.Web/Extensions/ConfigureSwagger.cs 来配置一下(上一篇关于swagger的还没忘记吧?)

AddSwaggerGen 里面,添加配置代码

var security = new OpenApiSecurityScheme {
  Description = "JWT模式授权,请输入 \"Bearer {Token}\" 进行身份验证",
  Name = "Authorization",
  In = ParameterLocation.Header,
  Type = SecuritySchemeType.ApiKey
};
options.AddSecurityDefinition("oauth2", security);
options.AddSecurityRequirement(new OpenApiSecurityRequirement {{security, new List<string>()}});
options.OperationFilter<AddResponseHeadersFilter>();
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.OperationFilter<SecurityRequirementsOperationFilter>();

搞定。这样swagger页面右上角就多了个锁头图标,点击就可以输入 JWT token

基于.NetCore开发博客项目 StarBlog - (27) 使用JWT保护接口

不过有一点不方便的是,每个接口分组都要输入一次,切换了就得重新输入了…

但至少不用postman了~文章来源地址https://www.toymoban.com/news/detail-432793.html

参考资料

  • https://stackoverflow.com/questions/50012155/jwt-claim-names
  • https://stackoverflow.com/questions/47923652/what-is-the-best-practice-for-fetching-user-data-after-validating-jwt-in-net-co
  • https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters

系列文章

  • 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客?
  • 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目
  • 基于.NetCore开发博客项目 StarBlog - (3) 模型设计
  • 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入
  • 基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目
  • 基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表
  • 基于.NetCore开发博客项目 StarBlog - (7) 页面开发之文章详情页面
  • 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示
  • 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入
  • 基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流
  • 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计
  • 基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译
  • 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能
  • 基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能
  • 基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片
  • 基于.NetCore开发博客项目 StarBlog - (16) 一些新功能 (监控/统计/配置/初始化)
  • 基于.NetCore开发博客项目 StarBlog - (17) 自动下载文章里的外部图片
  • 基于.NetCore开发博客项目 StarBlog - (18) 实现本地Typora文章打包上传
  • 基于.NetCore开发博客项目 StarBlog - (19) Markdown渲染方案探索
  • 基于.NetCore开发博客项目 StarBlog - (20) 图片显示优化
  • 基于.NetCore开发博客项目 StarBlog - (21) 开始开发RESTFul接口
  • 基于.NetCore开发博客项目 StarBlog - (22) 开发博客文章相关接口
  • 基于.NetCore开发博客项目 StarBlog - (23) 文章列表接口分页、过滤、搜索、排序
  • 基于.NetCore开发博客项目 StarBlog - (24) 统一接口数据返回格式
  • 基于.NetCore开发博客项目 StarBlog - (25) 图片接口与文件上传
  • 基于.NetCore开发博客项目 StarBlog - (26) 集成Swagger接口文档
  • 基于.NetCore开发博客项目 StarBlog - (27) 使用JWT保护接口

到了这里,关于基于.NetCore开发博客项目 StarBlog - (27) 使用JWT保护接口的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

    本文介绍从零到一搭建基于netcore6.0版本的 webapi接口应用 包括swagger接口管理文档 jwt接口安全认证 aop接口调用轨迹日志 ef映射mysql 使用codefirst模式交互数据库 首先新建一个webapi应用 此次默认配置HTTPS默认是勾选的 此处没用到暂时不进行勾选 可以删除这2个默认的文件 首先我

    2024年02月04日
    浏览(50)
  • 基于Spring实现博客项目

    访问地址: 用户登录 代码获取 :基于Spring实现博客项目: Spring项目写博客项目  需求评审,需求分析 项目设计(接口设计,DB设计等,比较大的需求,需要设计流程图,用例图,UML, model中的字段) 开发+自测 提测(提交测试) 验收:预发布环境/预生产环境部署测试(开发,测试,产品) 上线

    2024年02月11日
    浏览(56)
  • Node.js博客项目开发思路笔记

    开发一个博客系统,具备博客基本功能 只开发 server 端,不关心前端 首页、作者页、博客详情页 登陆页 管理中心、新建页、编辑页 数据如何存储 博客 id title content createtime author 1 标题 1 内容 1 1111112 zhangsan 2 标题 2 内容 2 1111111 lisi 用户 id username password realname 1 zhangsan 123 张三

    2024年02月12日
    浏览(42)
  • [Vue]之Jwt的入门和Jwt工具类的使用及Jwt集成spa项目

             JWT,全称为 JSON Web Token,是一种用于在 网络应用之间传递信息的标准方法 。它是 基于 JSON 格式定义的一种简洁且自包含的方式 ,可以安全地在 用户和服务之间传输声明信息 ①简洁性 : JWT 的格式简洁且体积小,可以方便地在网络请求的头部、参数或者正文中传

    2024年02月08日
    浏览(38)
  • 如何在springboot项目中使用JWT

    JWT的全称为:JSON Web Token ,它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。 JWT由三部分组成,它们之间用圆点(.)连接。这三部分分别是: Header.Payload.Signature Header是一个描述JWT元数据的JS

    2024年02月08日
    浏览(49)
  • Python Flask框架-开发简单博客-项目布局、应用设置

    作者:Eason_LYC 悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。 一个人的价值,只在于他所拥有的。所以可以不学无术,但不能一无所有! 技术领域:WEB安全、网络攻防 关注WEB安全、网络攻防。我的专栏文章知识点全面细致,逻辑清晰、结合实战,让你在学习路

    2024年02月02日
    浏览(45)
  • SpringBoot项目使用JWT令牌进行权限校验

    要在Spring Boot中进行JWT校验,你需要遵循以下步骤: 在你的pom.xml文件中添加以下依赖: 创建一个名为JwtUtil的工具类,用于生成和解析JWT令牌。 在你的Controller中,使用JwtUtil工具类来验证请求头中的JWT令牌。 现在,当用户访问/secure端点时,系统会检查请求头中的JWT令牌是否

    2024年01月23日
    浏览(56)
  • 基于 Spring Boot 博客系统开发(三)

    本系统是简易的个人博客系统开发,为了更加熟练地掌握 SprIng Boot 框架及相关技术的使用。🌿🌿🌿 基于 Spring Boot 博客系统开发(二)👈👈 在Thymeleaf中,如果你想要抽取公共页面(例如,头部、底部、导航栏等),可以通过定义和使用片段(fragments)和包含(includes)来

    2024年04月28日
    浏览(36)
  • 基于NodeJs+Express+MySQL 实现的个人博客完整项目

    目录 一、创建项目并初始化 项目结构 二、安装项目所需要的包 三、创建所需要的数据库表 表 user 用于存放账户密码 表 notepad 用于存放文章数据 表 leaving 用于存放留言板的数据 三、编写app.js文件 1、导入所有需要的包 2、创建web服务器 3、创建db文件夹,在文件夹里面创建

    2024年02月08日
    浏览(49)
  • 基于SqlSugar的开发框架循序渐进介绍(27)-- 基于MongoDB的数据库操作整合

    SqlSugar的开发框架本身主要是基于常规关系型数据库设计的框架,支持多种数据库类型的接入,如SqlServer、MySQL、Oracle、PostgreSQL、SQLite等数据库,非关系型数据库的MongoDB数据库也可以作为扩展整合到开发框架里面,通过基类的继承关系很好的封装了相关的基础操作功能,极大

    2023年04月13日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包