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.Tokens、System.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. 【Linux】Linux没有网络,可能的解决方法

    [root@localhost etc]# cd /etc/sysconfig/network-scripts/ [root@localhost network-scripts]# ll 修改此文件中 ...

  2. 【Warrior刷题笔记】力扣169. 多数元素 【排序 || 哈希 || 随机算法 || 摩尔投票法】详细注释 不断优化 极致压榨

    题目 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/majority-element/ 注意,该题在LC中被标注为easy,所以我们更多应该关 ...

  3. .NET 云原生架构师训练营(责任链模式)--学习笔记

    目录 责任链模式 源码 责任链模式 职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了 何时使用:在处理 ...

  4. 怎样在idea添加log日志 以及log4j2配置文件解读

    网上找了很多篇文章,就数这篇比较全,从下载到配置都有讲到,解决从0开始接触java日志文件添加的各位同学.参考文章:https://www.cnblogs.com/hong-fithing/p/769 ...

  5. APP自动化,怎样让应用不重置?

    noReset =True产生的背景: 在编写APP自动化代码时,除了登录用例需要填写账号和密码外,其余很多用例都是需要先登录再操作的,如果每一个用例都从头开始到具体的操作,这样将会耗费很多时间,此时 ...

  6. centos7-collabora-office(在线文档编辑)

    1.wget https://www.collaboraoffice.com/repos/CollaboraOnline/CODE-centos7/repodata/repomd.xml.key &a ...

  7. Bash 取字符串的最后 N 个字符 - ${str:0-N:LENGTH}

    Bash 取字符串的最后 N 个字符: ${str:0-N:LENGTH} or ${str:0-N} https://tldp.org/LDP/abs/html/string-manipulatio ...

  8. Pandas系列(十八)- 多级索引

    多级索引 多级索引(也称层次化索引)是pandas的重要功能,可以在Series.DataFrame对象上拥有2个以及2个以上的索引.实质上,单级索引对应Index对象,多级索引对应MultiInde ...

  9. 《剑指offer》面试题35. 复杂链表的复制

    问题描述 请实现 copyRandomList 函数,复制一个复杂链表.在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null. ...

  10. Golang 记录

    Golang 笔记 1. hello Golang 新建项目,推荐GoLand工具 GOPATH目录:D:\go\awesomeProject 三个标准目录:bin,pkg,src MAIN目录:D: ...