温故知新,signalR、RSA加密、ConcurrentQueue队列
这是一个用户密码异步加解密的Demo,包含了RSA非对称加密,ConcurrentQueue线程安全队列使用,signalR实时推送加解密信息等内容。
说在前面的话
距离上次更新已然快过去一年了,这中间日子里进入了非常繁忙的项目迭代开发中,时至今日终于有空停下来写一写之前的博客计划,续更后的第一篇,温故知新,用一个Demo介绍技术点的落地实操,如有不同意见评论区留下你的想法,Of course ,如果你杠精就是你对。
依照惯例,源代码在文末,需要自取~
不同解决方案?
直接看看执行效果,看完之后,你是否会与我选择同样的技术方案呢?
实现效果
一个用户列表,可以展示所有用户的信息,需要对其中的密码进行加密,加密使用非对称加密,点击加密按钮以及解密按钮,实时地可以看到加密和解密的数据。
乍一看看可太简单,RSA的非对称加密,网上直接ctrl C V一套已有的就完事,就是要解决实时性的问题。
这里我选择了SignlR做实时推送,然后为了可以看到效果与性能考虑,使用了ConcurrentQueue线程安全队列,控制加解密的速度。
好了砖头抛出来了,看各位大佬骚操作
拉代码看代码
如果你不晓得这几个技术点该如何加入你的框架中,或者知道一些概念,但是没用过,下文适合你食用!
RSA加解密
非对称加密的使用现在已然太多示例,提供的项目源代码中,专门提供了一个可以直接跑的Demo,拉下来F5,调试一下完事,贴心的为你提供了Web API接口与测试页面。
QueueDemo 作为WebApi启动, RSAProcessing.MVC 作为前端页面启动
一下就是核心 RSA加密处理程序的核心代码,Ctrl C V之后, 使用 RSAProcessing.GenerateKeys(out string publicKey, out string pricateKey);
即可生成公钥和秘钥,加密解密使用方式同上。
/// <summary>
/// RSA加密处理程序
/// </summary>
public static class RSAProcessing
{
/// <summary>
/// 生成RSA密钥对
/// </summary>
/// <param name="publicKey"></param>
/// <param name="privateKey"></param>
public static void GenerateKeys(out string publicKey, out string privateKey)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
publicKey = rsa.ToXmlString(false);
privateKey = rsa.ToXmlString(true);
}
}
/// <summary>
/// 使用公钥加密文本
/// </summary>
/// <param name="plainText"></param>
/// <param name="publicKey"></param>
/// <returns></returns>
public static string Encrypt(string plainText, string publicKey)
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(publicKey);
byte[] encryptedBytes = rsa.Encrypt(plainBytes, false);
return Convert.ToBase64String(encryptedBytes);
}
}
/// <summary>
/// 使用私钥解密文本
/// </summary>
/// <param name="encryptedText"></param>
/// <param name="privateKey"></param>
/// <returns></returns>
public static string Decrypt(string encryptedText, string privateKey)
{
byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(privateKey);
byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, false);
return Encoding.UTF8.GetString(decryptedBytes);
}
}
......
SignlR的实时推送
SignlR是什么?这个不用我去百度,ChatGPT可以给你一个简洁有效的答案。
看到这里,可能你会有和我一样感受,这个作为实时通讯,是不是我直接做一个仿QQ和微信的聊天工具来,做大做强? 看看 GPT的回答:
可以看到ChatGPT可以给予我们绝大部分答案,但是这里给大家补充一下:
- 通讯方式的选择取决于浏览器版本以及服务端和客户端能力范围内的最佳通讯方式,通常是WebSocket > Server-Sent Events > Long Poling
- SignalR不仅仅可在线聊天的通讯软件,做事件推送也非常好用,例如直播或者视频的观看人数统计等等
SignlR的.net 6代码实现
使用之前,一定先去看看微软的官方Demo!
https://learn.microsoft.com/zh-cn/aspnet/core/signalr/introduction?view=aspnetcore-6.0
以上是简易的两个Web应用的架构图,5102的WebApi作为服务端提供数据,MVC 应用作为客户端接受数据,以及发送SignalR连接,因为他们是两个Web应用,所属的web域不同,客户端请求服务端,需要服务端配置允许跨域请求。
注册服务
在5102的服务端中注册SignalR ,并且配置允许跨域
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("http://localhost:5067")
.AllowCredentials(); // 允许包含凭据;
});
});
builder.Services.AddSignalR();
...注册其他的服务...
var app = builder.Build();
app.UseCors();
app.MapHub<QueueHub>("/queueHub"); // 配置路由
配置消息处理中心
在持久性连接的基础上,SignalR提供了一个更高层次的抽象层:Hub,基于javascript的灵活性和C#的动态特性,Hub是一个至关重要的开发模式,它消弭了客户端和服务端这两个独立的物理环境之间的界限。 在Web环境中最通用的使用模式允许我们透明地在客户端和服务端之间进行方法调用。
简单的说,就是双向RPC,即可以直接从客户端调用服务器端的方法,同时服务端也可以调用客户端的方法。
using Microsoft.AspNetCore.SignalR;
namespace QueueDemo.Core
{
/// <summary>
/// 队列的signalR总线
/// </summary>
public class QueueHub : Hub
{
/// <summary>
/// 加入连接的事件
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
GlobalUserInfo.Clients = Clients;
await base.OnConnectedAsync();
}
/// <summary>
/// signalR推送加密信息
/// </summary>
/// <param name="userId">用户id</param>
/// <param name="message">加密数据</param>
/// <returns></returns>
public async Task SendEncryptDequeue(int userId, string message)
{
await GlobalUserInfo.Clients.All.SendAsync("ReceiveEncrypt", userId, message);
}
/// <summary>
/// signalR推送解密信息
/// </summary>
/// <param name="userId">用户id</param>
/// <param name="message">解密数据</param>
/// <returns></returns>
public async Task SendDecryptDequeue(int userId, string message)
{
await GlobalUserInfo.Clients.All.SendAsync("ReceiveDecrypt", userId, message);
}
}
}
客户端JS配置
客户端主要需要做的就是,与SignalR服务端建立连接,接受服务端推送过来的数据。
<script>
let queueHost = 'http://localhost:5102';
// 创建signalR连接
var connection = new signalR.HubConnectionBuilder().withUrl(queueHost + "/queueHub").build();
// 接收到 ReceiveEncrypt 的消息
connection.on("ReceiveEncrypt", function (userId, message) {
console.log(userId);
console.log(message);
// 使用特定 id 来定位并修改文本内容
$('#en_' + userId).text(message);
});
// 接收到 ReceiveDecrypt 的消息
connection.on("ReceiveDecrypt", function (userId, message) {
console.log(userId);
console.log(message);
// 使用特定 id 来定位并修改文本内容
$('#de_' + userId).text(message);
});
// 连接成功
connection.start().then(function () {
console.log("Connection Success")
}).catch(function (err) {
return console.error(err.toString());
});
</script>
ConcurrentQueue队列连接客户端与服务端
SignalR与QueueHub的连接已然搞定,就是如何触发推送加解密信息。
这里使用的方案是ConcurrentQueue队列
,将所有的用户信息推送到加密队列(&解密队列)中,出队一个UserInfo,就加密(&解密)一个用户信息,随后利用SignalR推送一个加密解密信息。
再说到队列,大家熟知都是RabbitMQ ,Kafka , RocketMQ,然而在实战中,急着要用一个队列,如果此时用上RabbitMQ,那么还需要额外部署一个应用,开防火墙等等,这一套搞下来,加上走流程快的话一周过去了,此时用一个内存队列就是最合适的,用线程加内存队列可以做一个低配版的rabbitmq,先实现业务需求,再后期去升级。
初始化队列
依上述所言,队列为了简单易用,在StartUp
中创建两个线程去跑。
// 创建并启动后台任务
UserQueueHandler ledgerQueue = new(new QueueHub());
Task task = Task.Run(() => ledgerQueue.DeProcessQueue(builder.Services, GlobalUserQueue.DecryptCancelToken.Token));
Task task2 = Task.Run(() => ledgerQueue.EnProcessQueue(builder.Services, GlobalUserQueue.EncryptCancelToken.Token));
两个全局的静态变量存储队列的配置,并且创建两个中断循环的开关。
/// <summary>
/// 全局用户队列初始化
/// </summary>
public static class GlobalUserQueue
{
/// <summary>
/// 解密队列 退出循环开关
/// </summary>
public static CancellationTokenSource DecryptCancelToken = new();
/// <summary>
/// 加密队列 退出循环开关
/// </summary>
public static CancellationTokenSource EncryptCancelToken = new();
/// <summary>
/// 解密队列
/// </summary>
public static ConcurrentQueue<DecryptRequest> DecryptQueue = new();
/// <summary>
/// 加密队列
/// </summary>
public static ConcurrentQueue<EncryptRequest> EncryptQueue = new();
}
初始化用户队列处理程序
/// <summary>
/// 用户队列处理程序
/// </summary>
public class UserQueueHandler
{
private QueueHub _queueHub;
/// <summary>
/// 注入队列总线
/// </summary>
/// <param name="queueHub"></param>
public UserQueueHandler(QueueHub queueHub)
{
_queueHub = queueHub;
}
/// <summary>
/// 启动解密队列
/// </summary>
/// <param name="services">注册服务</param>
/// <param name="cancellationToken">退出Token控制器</param>
/// <returns></returns>
public async Task DeProcessQueue(IServiceCollection services, CancellationToken cancellationToken)
{
try
{
var serviceProvider = services.BuildServiceProvider();
// Rsa加解密服务
var rsaService = serviceProvider.GetRequiredService<IRSAService>();
await Console.Out.WriteLineAsync($"Decrypt ProcessQueue Start! ");
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
if (cancellationToken.IsCancellationRequested)
{
break;
}
// 解密队列出队
if (GlobalUserQueue.DecryptQueue.TryDequeue(out DecryptRequest deRequest))
{
await Console.Out.WriteLineAsync($"DeProcessQueue UserIndexId -- {deRequest.UserIndex} -- {JsonConvert.SerializeObject(deRequest)} ");
try
{
// 解密
var deScryptRsp = rsaService.Decrypt(deRequest);
if (deScryptRsp != null)
{
var userInfo = GlobalUserInfo.UserInfos.First(x => x.Index == deScryptRsp.UserIndex);
userInfo.DecryptedPwd = deScryptRsp.DecryptedPwd;
// 推送解密信息到前端
await _queueHub.SendDecryptDequeue(userInfo.UserId, userInfo.DecryptedPwd);
await Console.Out.WriteLineAsync($"DeProcessQueue Success! UserId--{userInfo.UserId} UserIndex--{deScryptRsp.UserIndex} ");
}
await Task.Delay(1000);
}
catch (Exception ex)
{
await Console.Out.WriteLineAsync($"DeProcessQueue Error --{JsonConvert.SerializeObject(ex)} ");
}
}
else
{
// 队列中无数量 则休眠10秒
await Task.Delay(10000);
}
}
}
catch (Exception ex)
{
await Console.Out.WriteLineAsync(ex.Message);
throw;
}
}
}
这个方法就是触发推送加解密的信息,连接客户端和服务端的核心。
- ConcurrentQueue出队使用的是
GlobalUserQueue.DecryptQueue.TryDequeue(out DecryptRequest deRequest)
- 利用
serviceProvider.GetRequiredService<IRSAService>();
获取RSA解密服务,然后再调用解密方法。 - 拿到解密之后的信息之后,使用queueHub的方法
await _queueHub.SendDecryptDequeue(userInfo.UserId, userInfo.DecryptedPwd);
推送解密数据 - 推送数据主要是SignalR的方法
await GlobalUserInfo.Clients.All.SendAsync("ReceiveDecrypt", userId, message);
总结
稍微总结一下,
- Demo集成了RSA加密、SignalR推送、内存版的队列。
- 讲解了一下SignalR的用法以及注意事项
- 内存版的队列在Web应用中的优势
- 有更好的更快速的解决方案评论区留下信息
项目拉取下来,在解决方案设置中,同时启动两个项目即可。
源代码仓库 https://github.com/OrzCoCo-Y/QueueDemo
参考资料
【微软文档】 https://learn.microsoft.com/zh-cn/aspnet/core/signalr/introduction?view=aspnetcore-6.0
【ChatGPT】
温故知新,signalR、RSA加密、ConcurrentQueue队列的更多相关文章
- “不给力啊,老湿!”:RSA加密与破解
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 加密和解密是自古就有技术了.经常看到侦探电影的桥段,勇敢又机智的主角,拿着一长串毫 ...
- .NET 对接JAVA 使用Modulus,Exponent RSA 加密
最近有一个工作是需要把数据用RSA发送给Java 虽然一开始标准公钥 net和Java RSA填充的一些算法不一样 但是后来这个坑也补的差不多了 具体可以参考 http://www.cnblogs. ...
- Android数据加密之Rsa加密
前言: 最近无意中和同事交流数据安全传输的问题,想起自己曾经使用过的Rsa非对称加密算法,闲下来总结一下. 其他几种加密方式: Android数据加密之Rsa加密 Android数据加密之Aes加密 ...
- 兼容javascript和C#的RSA加密解密算法,对web提交的数据进行加密传输
Web应用中往往涉及到敏感的数据,由于HTTP协议以明文的形式与服务器进行交互,因此可以通过截获请求的数据包进行分析来盗取有用的信息.虽然https可以对传输的数据进行加密,但是必须要申请证书(一般都 ...
- RSA加密例子和中途遇到的问题
在进行RSA加密例子 package test; import java.io.IOException; import java.security.Key; import java.security. ...
- iOS中RSA加密详解
先贴出代码的地址,做个说明,因为RSA加密在iOS的代码比较少,网上开源的也很少,最多的才8个星星.使用过程中发现有错误.然后我做了修正,和另一个库进行了整合,然后将其支持CocoaPod. http ...
- iOS动态部署之RSA加密传输Patch补丁
概要:这一篇博客主要说明下iOS客户端动态部署方案中,patch(补丁)是如何比较安全的加载到客户端中. 在整个过程中,需要使用RSA来加密(你可以选择其它的非对称加密算法),MD5来做校验(同样,你 ...
- Java使用RSA加密解密及签名校验
该工具类中用到了BASE64,需要借助第三方类库:javabase64-1.3.1.jar注意:RSA加密明文最大长度117字节,解密要求密文最大长度为128字节,所以在加密和解密的过程中需要分块进行 ...
- 基于OpenSLL的RSA加密应用(非算法)
基于OpenSLL的RSA加密应用(非算法) iOS开发中的小伙伴应该是经常用der和p12进行加密解密,而且在通常加密不止一种加密算法,还可以加点儿盐吧~本文章主要阐述的是在iOS中基于openSL ...
- 通过ios实现RSA加密和解密
在加密和解密中,我们需要了解的知识有什么事openssl:RSA加密算法的基本原理:如何通过openssl生成最后我们需要的der和p12文件. 废话不多说,直接写步骤: 第一步:openssl来生成 ...
随机推荐
- 【CTF】系统调用号查询表
32位 #ifndef _ASM_X86_UNISTD_32_H #define _ASM_X86_UNISTD_32_H 1 #define __NR_restart_syscall 0 #defi ...
- 正则表达式、datetime
1.正则表达式就是用来匹配字符串的 2.常用\d表示一个数字,\w表示数字或者字母,'.'表示任意字符 3.如果要匹配边长的字符串,使用*表示任意个字符,+表示至少一个字符,?表示0个或者1个字符,{ ...
- PMD插件:你必须掌握的代码质量工具!
当今的软件开发需要使用许多不同的工具和技术来确保代码质量和稳定性.PMD是一个流行的静态代码分析工具,可以帮助开发者在编译代码之前发现潜在的问题.在本文中,我们将讨论如何在Gradle中使用PMD,并 ...
- 生成df的几种方法
法一: pd.DataFrame( [ (第一行),(第二行),(第三行)] ) df = pd.DataFrame([('bird', 389.0), ('bird', 24.0), ('mamma ...
- Redis 数据类型 Set
Redis 数据类型 Set(集合) Redis 常用命令,思维导图 >>> Redis 的 Set 是 String 类型的无序集合.集合成员是唯一的,这就意味着集合中不能出现重复 ...
- MySQL InnoDB Architecture 简要介绍
MySQL InnoDB 存储引擎整体架构图: 一.内存存储结构 1.Buffer Pool buffer pool 是主内存中的一块儿存储区域,用于存储访问的表及索引数据.这样从内存中直接访问获取使 ...
- 华为云 OpenTiny 跨端、跨框架企业级开源组件库项目落地实践直播即将开启!
大家好,我是 Kagol,公众号:前端开源星球. "你们这个产品怎么只能在电脑上适配呀?我想在手机上看都不行,太麻烦了!!" "你们这个产品看起来太简单了,我想要@@功能 ...
- ts、typescript、enum、枚举、ts 获取枚举对应的类型、获取 enum 的 key 和 value
ts.typescript.enum.枚举.ts 获取枚举对应的类型 // 假设我一个枚举 enum ENUM_TYPE { ALL = 'all', SOME = 'some', LITTLE = ...
- 命令行编译和执行java代码
虽然现在IDE很强大又很智能,但是平常随意写点练手的代码的时候,直接在命令行中使用vim和java命令更为方便快捷,可以做到无鼠标纯键盘的操作. 首先保证将java相关指令添加到了环境变量中: 1.编 ...
- Laf Assistant:云开发从未如此爽快!
原文链接:https://forum.laf.run/d/67 工欲善其事,必先利其器.在编写代码时,IDE 也是我们不可或缺的.它可以让我们更高效地完成代码编写,提高开发效率.因此,IDE 是我们编 ...