首页 > 其他分享 >.net core web api授权、鉴权、API保护

.net core web api授权、鉴权、API保护

时间:2024-10-21 11:22:18浏览次数:9  
标签:core 公钥 false web token API var new public

前言

本文整理asp.net core web API的授权、鉴权以及注册验证、API保护一系列常用技术手段。

本文所有的实现代码可以参考:
https://gitee.com/xiaoqingyao/web-app-identity.git

用户管理

授权和鉴权的前提是要有一个用户管理模块,.net 提供一个现有的Identity组件,帮我们完成了大部分功能。

学习使用可以参考asp.net core使用identity+jwt保护你的webapi(一)——identity基础配置 - xhznl - 博客园

JWT服务配置及获取JwtToken

完成用户注册、登录后,就可以在登录之后为用户颁发JwtToken,完成了token颁发和接口请求时的身份验证

asp.net core使用identity+jwt保护你的webapi(二)——获取jwt token - xhznl - 博客园

弥补Jwt的先天缺钱——RefreshToken

Jwt一旦颁发,基本不可控,在过期时间内一直有效。但是如果设置有效时间过短,又会导致用户频繁登录。

refresh token就可以很好的弥补jwt的缺陷。在refresh token机制下,我们可以把token的有效期设置的短一些,比如30分钟,而refresh token的有效期可以很长;因为refresh token会持久化到数据库中,它是完全可控的。

详细操作可以参考:asp.net core使用identity+jwt保护你的webapi(三)——refresh token - xhznl - 博客园

以用户修改密码为例,演示如何获取Jwt中的信息

新增一个中间件

public class TokenMiddleware
{
    private readonly RequestDelegate _next;

    public TokenMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 从请求头中获取 token
        if (context.Request.Headers.TryGetValue("Authorization", out var token))
        {
            // 将 token 存入 HttpContext.Items
            context.Items["Token"] = token.ToString().Replace("Bearer ", ""); // 去除 "Bearer " 前缀,如果有的话
        }
        // 调用管道中的下一个中间件
        await _next(context);
    }
}

封装一个HttpContextWrapper用于获取JwtToken

public interface IHttpContextWrapper
{
    string? GetToken();
}
public class HttpContextWrapper : IHttpContextWrapper
{
    private readonly Microsoft.AspNetCore.Http.HttpContext? _httpContent;

    public HttpContextWrapper(IHttpContextAccessor httpContextAccessor)
    {
        _httpContent = httpContextAccessor.HttpContext;
    }
    public string? GetToken()
    {
        if (_httpContent != null && _httpContent.Request.Headers.TryGetValue("Authorization", out var token))
        {
            return token.ToString().Replace("Bearer ", "");

        }
        return null;
    }
}

builder.Services.AddScoped<IHttpContextWrapper, HttpContextWrapper>(); // 注册自定义 HttpContextWrapper

在controller中使用

/// <summary>
/// 修改密码
/// </summary>
/// <param name="modifyPasswordRequest"></param>
/// <returns></returns>
[HttpPost("ModifyPassword")]
public async Task<IActionResult> ModifyPassword(ModifyPasswordRequest modifyPasswordRequest)
{
    var token = _httpContextWrapper.GetToken();
    var result = await _userService.ModifyPassword(modifyPasswordRequest, token);
    if (!result.Success)
    {
        return BadRequest(new FailedResponse()
        {
            Errors = result.Errors
        });
    }
    return Ok();
}

业务代码中校验并获取token信息的关键代码

private ClaimsPrincipal? GetClaimsPrincipalByRsaToken(string token)
{


    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateIssuerSigningKey = true,
        //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.SecurityKey)),//使用对称加密
        IssuerSigningKey = _securityKey,//使用公钥解签
        ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Aes128CbcHmacSha256 },//使用公钥解签
        TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abc111111111111111111111111111111cba")),//  token解密密钥 jwe解密
        ClockSkew = TimeSpan.Zero,
        ValidateLifetime = false
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    // 验证 JWT
    try
    {
        var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken);
        return principal;
    }
    catch (SecurityTokenExpiredException)
    {
        Console.WriteLine("JWT has expired.");
    }
    catch (SecurityTokenInvalidSignatureException)
    {
        Console.WriteLine("JWT signature is invalid.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("JWT validation failed: " + ex.Message);
    }
    return null;
}

详细代码在前面给到的gitee代码中。

修改密码后用户重新登录

根据拿到的token信息找到对应的RefreshToken进行无效掉,这样token过期后就无法再通过refresh token进行token刷新了。

var storedRefreshToken =
    await _appDbContext.RefreshTokens.SingleOrDefaultAsync(x => x.Token == refreshToken);
if (storedRefreshToken == null)
{
    // 无效的refresh_token...
    return new TokenResult()
    {
        Errors = new[] { "3: Invalid request!" },
    };
}
storedRefreshToken.Used = true;
await _appDbContext.SaveChangesAsync();

修改密码后用户立马重新登录

修改密码后,token还是没有过期的,这时候如何控制用户立马登录呢?

有很多技术手段比如修改jwt令牌,增加redis缓存校验等。我认为最好的办法就是前端配合把本地存储的jwttoken删除即可。

jwt升级:使用非对称加密进行Jwt签名和验签

公司内的多个业务项目都会使用该token,因此,为了让每个项目都可以进行身份认证,就需要将密钥分发给所有项目,这就产生了较大的风险。因此,使用非对称加密来计算签名,是一个更加合理地选择:我们使用私钥进行签名,然后只需要将公钥暴露出去用于验签,即可验证token是有效的(没有被篡改)。

新增RsaKeyManager用于导出公钥私钥

public class RsaKeyManager
{
    public void GenerateAndSaveRsaKeys(IWebHostEnvironment env)
    {
        var dir = Path.Combine(env.ContentRootPath, "Rsa");
        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }
        if (File.Exists(Path.Combine(dir, "key.private.json"))
            && File.Exists(Path.Combine(dir, "key.public.json")))
        {
            return;
        }

        using (RSA rsa = RSA.Create(2048)) // 创建2048位的RSA密钥
        {
            // 导出公钥
            RSAParameters publicKeyParameters = rsa.ExportParameters(false);// 导出私钥
            RSAParameters privateKeyParameters = rsa.ExportParameters(true);
        }

        RSAParameters privateKey, publicKey;

        using (var rsa = RSA.Create(2048))
        {
            // 导出私钥
            privateKey = rsa.ExportParameters(true);
            // 导出公钥
            publicKey = rsa.ExportParameters(false);
        }
        File.WriteAllText(Path.Combine(dir, "key.public.json"), JsonConvert.SerializeObject(publicKey));
        File.WriteAllText(Path.Combine(dir, "key.private.json"), JsonConvert.SerializeObject(privateKey));
    }
}

在program中修改

var keyManager = new RsaKeyManager();
keyManager.GenerateAndSaveRsaKeys(builder.Environment);
var rsaSecurityPrivateKeyString = File.ReadAllText(Path.Combine(builder.Environment.ContentRootPath, "Rsa", "key.private.json"));
var rsaSecurityPublicKeyString = File.ReadAllText(Path.Combine(builder.Environment.ContentRootPath, "Rsa", "key.public.json"));
RsaSecurityKey rsaSecurityPrivateKey = new(Newtonsoft.Json.JsonConvert.DeserializeObject<RSAParameters>(rsaSecurityPrivateKeyString));
RsaSecurityKey rsaSecurityPublicKey = new(Newtonsoft.Json.JsonConvert.DeserializeObject<RSAParameters>(rsaSecurityPublicKeyString));

//使用私钥加签
builder.Services.AddSingleton(sp => new SigningCredentials(rsaSecurityPrivateKey, SecurityAlgorithms.RsaSha256));

builder.Services.AddSingleton(rsaSecurityPublicKey);
var tokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateIssuerSigningKey = true,
        //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.SecurityKey)),//使用对称加密
        IssuerSigningKey = rsaSecurityPublicKey,//使用公钥解签
        ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 },//使用公钥解签

        ClockSkew = TimeSpan.Zero,
    };

builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => { options.TokenValidationParameters = tokenValidationParameters; });

在业务代码中解密jwttoken

/// <summary>
/// 获取非对称加密token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
private ClaimsPrincipal? GetClaimsPrincipalByRsaToken(string token)
{
    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateIssuerSigningKey = true,
        //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.SecurityKey)),//使用对称加密
        IssuerSigningKey = _securityKey,//使用公钥解签
        ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 },//使用公钥解签

        ClockSkew = TimeSpan.Zero,
        ValidateLifetime = false
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    // 验证 JWT
 var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken);
        return principal;

}

 

详细代码参考前面gitee

jwt升级:JWE

加密的时候添加

var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new[]
    {
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")),
        new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString())
    }),
    IssuedAt = DateTime.UtcNow,
    NotBefore = DateTime.UtcNow,
    Expires = DateTime.UtcNow.Add(_jwtSettings.ExpiresIn),
    SigningCredentials = _signingCredentials//非对称加密
    ,
    EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abc111111111111111111111111111111cba")), JwtConstants.DirectKeyUseAlg, SecurityAlgorithms.Aes128CbcHmacSha256)
};

解密的时候添加

var tokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = false,
    ValidateAudience = false,
    ValidateIssuerSigningKey = true,
    //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.SecurityKey)),//使用对称加密
    IssuerSigningKey = _securityKey,//使用公钥解签
    ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Aes128CbcHmacSha256 },//使用公钥解签
    TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abc111111111111111111111111111111cba")),//  token解密密钥 jwe解密
    ClockSkew = TimeSpan.Zero,
    ValidateLifetime = false
};

在program中同步修改解密配置

var tokenValidationParameters = new TokenValidationParameters
{
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateIssuerSigningKey = true,
                //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.SecurityKey)),//使用对称加密
                IssuerSigningKey = rsaSecurityPublicKey,//使用公钥解签
                ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Aes128CbcHmacSha256 },//使用公钥解签
                                                                                                                 
                TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abc111111111111111111111111111111cba")),// token解密密钥 jwe解密
                ClockSkew = TimeSpan.Zero,
};

参考:【ASP.NET Core 认证】JwtBearer认证 - .Neterr - 博客园

标签:core,公钥,false,web,token,API,var,new,public
From: https://www.cnblogs.com/chenxizhaolu/p/18489096

相关文章

  • PointWeb: Enhancing Local Neighborhood Features for Point Cloud Processing——点
    此内容是论文总结,重点看思路!!文章概要本文研究如何有效聚合局部特征,提高点云数据的识别性能,提出了一种新的处理点云的方法PointWeb,旨在从局部邻域中提取上下文特征。与之前的方法不同,PointWeb通过密集连接局部邻域中的每个点,从而基于该区域的特性来调整每个点的特征。主要创......
  • EF Core进行增删改查
    1.使用nuge添加引用(程序资源管理器)2.program.csusingFluentAssertions.Common;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.Extensions.Configuration;usingNetModelCore02.Models;varbuilder=WebApplication.CreateBuilder(args);//Addservicesto......
  • .NET Core SqlSugar
    概念:1.官方文档:https://www.donet5.com/Home/Doc?typeId=11802.在vsstudio中导包SqlSugarCore创建模型类:1.vsstudio2022中选择项目2.选择6.03.projram.csusingSqlSugar;varbuilder=WebApplication.CreateBuilder(args);//Addservicestothecontain......
  • Qt编写的modbus模拟器/支持网络和串口以及websocket/支持网络rtu
    一、使用说明1.1设备模拟-Com第一步,填写要模拟的设备地址,0表示自动处理,也就是收到什么地址就应答什么地址。第二步,填写对应的串口号和波特率。第三步,单击打开串口,成功后会变成关闭串口字样。单击清空数据会将左侧打印栏的信息清空。右侧一堆微调框用于模拟对应设备多个寄......
  • webAPI中的键盘事件以及线程
    一、常用键盘事件1.键盘事件键盘事件触发条件onkeyup某个键盘按键松开时触发onkeydown某个键盘按键按下时触发onkeypress某个键盘按键按下时触发,但是不识别功能键,比如ctrl、shift和箭头等注意:如果使用document.addEventListener,则不用带ononkeypress和前面2个的区别的......
  • C#/.NET/.NET Core技术前沿周刊 | 第 10 期(2024年10.14-10.20)
    前言C#/.NET/.NETCore技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NETCore领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿、推荐或自荐优质文章、项目、学习资源等。每......
  • 金蝶云星空——关于Webapi保存接口同时自动审核
    问题期望在调用金蝶Webapi接口的新增单据的时候,同时完成提交、审核操作解决方案webapi保存接口有个参数IsAutoSubmitAndAudit,用来在保存时自动提交和审核,传入保存JSON数据时传入此参数设置为true即可实现自动提交审核。说明:为了减少接口调用,简化接口使用,但在实践中很多单......
  • ARM CORELINK是什么?
    ARMCoreLink是ARM公司开发的一系列系统IP(知识产权)产品,用于连接和管理片上系统(SoC)中的各个组件。它是ARM系统设计生态系统的重要组成部分,旨在帮助芯片设计者更快速、更高效地开发复杂的SoC设计。让我为您详细介绍一下ARMCoreLink:主要功能:互连:提供高性能、低延迟的......
  • 初学javaweb遇到的问题
    1.环境配置问题我得知要创建一个javaweb项目,需要下载安装idea专业版,于是我搜索网络资源,找到了使用专业版的办法。并根据教程配置tomcat,jdk,meaven等项目基础环境配置。2.项目结构理解我在使用Maven创建项目时,对pom.xml文件的配置感到困惑。特别是依赖管理部分,很多依赖的版......
  • wlanapi.dll错误提示:全面解析修复方法与快速解决策略
    在使用Windows操作系统时,用户可能会遇到各种系统文件错误,其中wlanapi.dll错误便是较为常见的一种。wlanapi.dll是Windows操作系统中负责无线网络连接相关操作的重要动态链接库文件。当此文件出现缺失、损坏或版本不兼容等问题时,系统通常会弹出错误提示,影响无线网络功能的正常使......