JWT

JSON Web Token 经过数字签名后,无法伪造,一个能够在各方之间安全的传输JSON对象的开放标准(RFC 7519

创建项目和解决方案

dotnet new webapi -n SampleApi
cd SampleApi
dotnet new sln -n SampleApp
dotnet sln add .\SampleApi.csproj

引用包

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

该包已经依赖Microsoft.IdentityModel.TokensSystem.IdentityModel.Tokens.Jwt,该包由Azure AD 团队提供,所以不在aspnetcore6 运行时中。

  • 或直接修改jwtaspnetcore.csproj,引用包
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
  • appsettings.json

"Authentication": {
"JwtBearer": {
"Issuer": "http://api.sampleapi.com",
"Audience": "SampleApi",
"SecurityKey": "SecurityKey23456"
}
}
  • Issuer:令牌的颁发者。一般就写成域名,实际可任意
  • Audience 颁发给谁。一般写成项目名,实际可任意
  • SecurityKey:签名验证的KEY;至少 128bit ,即16个英文字符以上,实际可任意英文字符

定义一个JwtSettings


public class JwtSettings
{
public JwtSettings(byte[] key, string issuer, string audience)
{
Key = key;
Issuer = issuer;
Audience = audience;
} /// <summary>
///令牌的颁发者
/// </summary>
public string Issuer { get; } /// <summary>
/// 颁发给谁
/// </summary>
public string Audience { get; } public byte[] Key { get; } public TokenValidationParameters TokenValidationParameters => new TokenValidationParameters
{
//验证Issuer和Audience
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
//是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
ValidateLifetime = true,
ValidIssuer = Issuer,
ValidAudience = Audience,
IssuerSigningKey = new SymmetricSecurityKey(Key)
}; public static JwtSettings FromConfiguration(IConfiguration configuration)
{
var issuser = configuration["Authentication:JwtBearer:Issuer"] ?? "default_issuer";
var auidence = configuration["Authentication:JwtBearer:Audience"] ?? "default_auidence";
var securityKey = configuration["Authentication:JwtBearer:SecurityKey"] ?? "default_securitykey"; byte[] key = Encoding.ASCII.GetBytes(securityKey); return new JwtSettings(key, issuser, auidence);
}
}

中间件Middleware引用

        app.UseAuthentication();//认证
app.UseAuthorization();//授权

定义JWT扩展方法服务注入

    public static IServiceCollection AddJwt(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IStorageUserService, StorageUserService>(); var jwtSettings = JwtSettings.FromConfiguration(configuration);
services.AddSingleton(jwtSettings); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => options.TokenValidationParameters = jwtSettings.TokenValidationParameters); return services;
}

引用服务

services.AddJwt(Configuration);

定义一个数据库的实体类,数据库访问 为模拟数据

public class SysUser
{
public int Id { get; set; }
public string UserName { get; set; }
}
public interface IStorageUserService
{
/// <summary>
/// 根据登录验证用户
/// </summary>
/// <param name="loginInfo"></param>
/// <returns></returns>
Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo);
}
public class StorageUserService : IStorageUserService
{
public async Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo)
{
return await Task.FromResult(
new SysUser
{
Id = new Random().Next(10000),
UserName = loginInfo.UserName
}
);
}
}

AuthController登录GenerateToken

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SampleApi.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; namespace SampleApi.Auth; /// <summary>
/// 登录认证个人信息
/// </summary>
[ApiController]
[Route("/api/[controller]/[action]")]
[AllowAnonymous]
public class AuthController : ControllerBase
{
private readonly IStorageUserService _userService;
private readonly JwtSettings _jwtSettings; public AuthController(JwtSettings jwtSettings, IStorageUserService userService)
{
_jwtSettings = jwtSettings;
_userService = userService;
} /// <summary>
/// 登录,生成访问Toekn
/// </summary>
/// <param name="loginInfo"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> GenerateToken(LoginInfo loginInfo)
{
SysUser user = await _userService.CheckPasswordAsync(loginInfo);
if (user == null)
{
return Ok(new
{
Status = false,
Message = "账号或密码错误"
});
} var claims = new List<Claim>(); claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserName)); var key = new SymmetricSecurityKey(_jwtSettings.Key);
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: creds
);
return Ok(new
{
Status = true,
Token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
}

aspnetcore6默认集成了swagger,直接运行项目,实际上为模拟数据库请求,所以点击登录接口即可。

{
"status": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijc4NjciLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNjQzMDMyNzA1LCJpc3MiOiJodHRwOi8vYXBpLnNhbXBsZWFwaS5jb20iLCJhdWQiOiJTYW1wbGVBcGkifQ.Rl8XAt2u0aZRxEJw2mVUnV6S9WzQ65qUYjqXDTneCxE"
}

当使用Swagger测试时,增加,可配置全局请求头。增加一个扩展方法。

services.AddSwagger(Configuration);
  public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(options =>
{
try
{
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml"), true);
}
catch (Exception ex)
{
Log.Warning(ex.Message);
}
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "SampleApp - HTTP API",
Version = "v1",
Description = "The SampleApp Microservice HTTP API. This is a Data-Driven/CRUD microservice sample"
}); options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
},
Array.Empty<string>()
}
});
options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
Name = "Authorization", //jwt默认的参数名称
In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey
}); });
services.AddEndpointsApiExplorer(); return services; }

获取当前用户信息

    /// <summary>
/// 编码Token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
public CurrentUser DecodeToken(string token)
{
var jwtTokenHandler = new JwtSecurityTokenHandler(); if (jwtTokenHandler.CanReadToken(token))
{
JwtPayload jwtPayload = new JwtSecurityTokenHandler().ReadJwtToken(token).Payload;
string? userIdOrNull = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.NameIdentifier)?.Value;
string? UserName = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.Name)?.Value;
CurrentUser currentUser = new CurrentUser
{
UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
UserName = UserName
};
return currentUser;
}
return null;
}

根据请求头获取用户信息

IStorageUserService增加接口,StorageUserService的实现,创建一个CurrentUser类

public class StorageUserService : IStorageUserService
{
private readonly IHttpContextAccessor _contextAccessor; public StorageUserService(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
} public async Task<CurrentUser> GetUserByRequestContext()
{
var user = _contextAccessor.HttpContext.User; string? userIdOrNull = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
string? UserName = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value; CurrentUser currentUser = new CurrentUser
{
IsAuthenticated = user.Identity.IsAuthenticated,
UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
UserName = UserName
};
return await Task.FromResult(currentUser);
}
} public class CurrentUser
{
/// <summary>
/// 是否登录
/// </summary>
public bool IsAuthenticated { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int? UserId { get; set; }
/// <summary>
/// 用户名
/// </summary>
public string? UserName { get; set; }
}
public interface IStorageUserService
{
/// <summary>
/// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
/// </summary>
/// <returns></returns>
Task<CurrentUser> GetUserByRequestContext();
}

AuthController调用服务

    /// <summary>
/// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<CurrentUser> GetUserByRequestContext()
{
return await _userService.GetUserByRequestContext();
}

在swagger右上角,点击Authorize,header的参数结构: "Authorization: Bearer+空格+ {token}"

开源地址

SampleApp/SampleApi at master · luoyunchong/SampleApp (github.com)

.NET +JWT

JSON Web Token Libraries - jwt.io 可以看到,.NET有6个类库实现了JWT。

有二个常用的。

  1. 微软 Azure团队的实现:AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet: IdentityModel extensions for .Net (github.com)
  2. jwt-dotnet/jwt: Jwt.Net, a JWT (JSON Web Token) implementation for .NET (github.com)

JWT+ASP.NET Core集成方案的更多相关文章

  1. asp.net core 集成JWT(一)

    [什么是JWT] JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT ...

  2. asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限

    [前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...

  3. ABP官方文档翻译 6.2.1 ASP.NET Core集成

    ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Acti ...

  4. asp.net core 集成 log4net 日志框架

    asp.net core 集成 log4net 日志框架 Intro 在 asp.net core 中有些日志我们可能想输出到数据库或文件或elasticsearch等,如果不自己去实现一个 Logg ...

  5. [Abp 源码分析]十七、ASP.NET Core 集成

    0. 简介 整个 Abp 框架最为核心的除了 Abp 库之外,其次就是 Abp.AspNetCore 库了.虽然 Abp 本身是可以用于控制台程序的,不过那样的话 Abp 就基本没什么用,还是需要集合 ...

  6. Asp.Net Core 集成 Hangfire 配置使用 Redis 存储

    Hangfire 官方支持 MSSQL 与 Redis(Hangfire.Pro.Redis) 两种 ,由于我的数据库是 MYSQL ,粗略查询了一下文档,现在对 .NET Core 支持的并不够好, ...

  7. asp.net core集成MongoDB

    0.目录 整体架构目录:ASP.NET Core分布式项目实战-目录 一.前言及MongoDB的介绍 最近在整合自己的框架,顺便把MongoDBD的最简单CRUD重构一下作为组件化集成到asp.net ...

  8. asp.net core集成CAP(分布式事务总线)

    一.前言 感谢杨晓东大佬为社区贡献的CAP开源项目,传送门在此:.NET Core 事件总线,分布式事务解决方案:CAP 以及 如何在你的项目中集成 CAP[手把手视频教程],之前也在工作中遇到分布式 ...

  9. asp.net core 集成 Prometheus

    asp.net core 集成 prometheus Intro Prometheus 是一个开源的现代化,云原生的系统监控框架,并且可以轻松的集成 PushGateway, AlertManager ...

随机推荐

  1. Docker | dockerfile 文件编写

    dockerfile 的作用 dockerfile 作用就是制作镜像,保持开发,测试,生产环境的一致性. 直接将容器制作为镜像 制作新的镜像 # 把容器按照自己的需求个性完之后,就可以创建自己的镜像的 ...

  2. frontend-maven-plugin插件问题解决

    1.插件介绍 frontend-maven-plugin为项目本地下载/安装Node和NPM,运行npm install命令 . 它适用于Windows,OS X和Linux. 这个插件也可以下载No ...

  3. CAS学习笔记二:CAS单点登录流程

    背景 由于公司项目甲方众多,各甲方为了统一登录用户体系实现单点登录(SSO)开始要求各乙方项目对接其搭建的CAS单点登录服务,有段时间对CAS的流程很迷,各厂商还有基于CAS进行二次开发的情况,所以对 ...

  4. MySQL约束和数据类型

    约束条件 约束条件就是在给字段加一些约束,使该字段存储的值更加符合我们的预期. 常用约束条件如下: UNSIGNED :无符号,值从0开始,无负数 ZEROFILL:零填充,当数据的显示长度不够的时候 ...

  5. 细谈 Java 匿名内部类 【分别 使用 接口 和 抽象类实现】

    1.前言 匿名内部类是什么东西? 没有名字的内部类就是匿名内部类. 什么场景使用? 匿名内部类适合创建那种只需要一次使用的类. 这是个很有用的东西,可想而知,如果不使用匿名内部类,哪些只需要使用一次的 ...

  6. 记一次oom问题排查

    大家好,我是大彬~ 今天给大家分享最近出现的OOM问题. 上周五早上,测试同学反馈测试环境的子系统服务一直超时,请求没有响应. 收到这个问题之后,我有点纳闷,最近这个系统也没有改动代码逻辑,怎么会突然 ...

  7. Jetpack—LiveData组件的缺陷以及应对策略 转至元数据结尾

    一.前言 为了解决Android-App开发以来一直存在的架构设计混乱的问题,谷歌推出了Jetpack-MVVM的全家桶解决方案.作为整个解决方案的核心-LiveData,以其生命周期安全,内存安全等 ...

  8. Solon Web 开发,十一、国际化

    Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...

  9. 在DigitalOcean vps中安装vnstat监控流量,浏览器打开php代码。。。

    由于DigitalOcean中没有发现可以观察已用流量的功能,有想知道自己的流量使用情况,所以安装了vnstat. 安装过程十分简单,见百度经验,官方主页等. 1.安装完vnstat后,直接命令vns ...

  10. promise的队列,宏任务,微任务,同步任务

    // promise里面有一个特别的任务,就是微任务 // 同步任务>微任务>宏任务 setTimeout(() => { console.log("setTimeout& ...