网络安全实验——安全通信软件safechat的设计
网络安全实验——安全通信软件safechat的设计
仅供参考,请勿直接抄袭,抄袭者后果自负。
仓库地址:
后端地址:https://github.com/yijunquan-afk/safechat-server
前端地址: https://github.com/yijunquan-afk/safechat-client
CosUpload.java中的COS设置,需要自己配
1 设计要求
结合所学安全机制设计实现一个简单的安全通信软件,包含机密性,消息认证等基本功能。并考虑其中涉及的密钥分配方式与机密性算法等相关问题的解决.实现方法不限,使用机制不限。
要求:
1、 独立完成
2、 具有完整的流程设计,报文格式等相关分析。
3、 具备自圆其说的安全性设计思考
2 设计分工
3 设计原理
SHA-2
SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发[3],由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
RSA
RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA 就是他们三人姓氏开头字母拼在一起组成的。
对极大整数做因数分解的难度决定了 RSA 算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA 算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用 RSA 加密的信息的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的 RSA 钥匙才可能被强力方式破解。到2020年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被破解的。
WebSocket协议
WebSocket是双向的,在客户端-服务器通信的场景中使用的全双工协议,与HTTP不同,它以ws://或wss://开头。它是一个有状态协议,这意味着客户端和服务器之间的连接将保持活动状态,直到被任何一方(客户端或服务器)终止。在通过客户端和服务器中的任何一方关闭连接之后,连接将从两端终止。
以客户端-服务器通信为例,每当启动客户端和服务器之间的连接时,客户端-服务器进行握手随后创建一个新的连接,该连接将保持活动状态,直到被他们中的任何一方终止。建立连接并保持活动状态后,客户端和服务器将使用相同的连接通道进行通信,直到连接终止。
新建的连接被称为WebSocket。一旦通信链接建立和连接打开后,消息交换将以双向模式进行,客户端-服务器之间的连接会持续存在。如果其中任何一方(客户端服务器)宕掉或主动关闭连接,则双方均将关闭连接。套接字的工作方式与HTTP的工作方式略有不同,状态代码101表示WebSocket中的交换协议。

JWT
JWT就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密,签名等相关处理。
基于JWT认证
首先,前端通过Wb表单将自己的用戶名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。
2、后端核对用戶名和密码成功后,将用戶的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同11.Zzz.xx的字符串。token head.payload.signature
3、后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage.上,退出登录时前端删除保存的JWT即可。
4、前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
5、后端检查JWT是否存在,如存在验证JWT的有效性。检查签名是否正确,检查Token是否过期,检查Token的接收方是否是自己(可选)
JWT结构
jwt生成的字符串包含有三部分
1、 jwt头信息部分header:标头通常由两部分组成:令牌的类型(即JWT所使用的签名算法,例如HMAC、SHA256或RSA。它会使用Base64编码组成JWT结构的第一部分。
2、 在效载荷Payload:令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用戶)和其他数据的声明。同样的,它会使用Ba$64编码组成JWT结构的第二部分
3、 签名哈希Signature:header和payload都是结果Base64编码过的,中间用.隔开,第三部分就是前面两部分合起来做签名,密钥绝对自己保管好,签名值同样做Base64编码拼接在JWT后面。(签名并编码)
AES
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),又称Rijndael加密法(荷兰语发音: [ˈrɛindaːl],音似英文的“Rhine doll”),是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。现在,高级加密标准已然成为对称密钥加密中最流行的算法之一。
严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中两者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度均可以是128,192或256比特。加密过程中使用的密钥是由Rijndael密钥生成方案产生。
大多数AES计算是在一个特别的有限域完成的。
AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵的“列数(Row number)”可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:
① AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
② SubBytes—透过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
③ ShiftRows—将矩阵中的每个横列进行循环式移位。
④ MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey取代。
4 整体设计方案
网络协议
本次设计中,我使用了HTTP协议处理一般的网络请求:如登录、注册、好友列表获取、个人信息获取、头像更新等功能。
而好友之间点对点的通信,为了持续快速地沟通,我是用WebSocket协议来处理信息发送请求。
客户端技术选型
客户端负责的是与用户进行交互,因此在实用之外还需要考虑到界面美观整洁,以给用户带来良好的使用体验。因此,前端选择使用 vue + AntDesign 组件库进行界面构建。另一方面,由于需要建立 WebSocket 连接,发送 WebSocket 请求,因此需要引入 WebSocket 相关功能的实现。这里使用的是 socket.io 这一 NodeJS 第三方模块。

服务端技术选型
对于服务端,采用了 Java + SpringBoot 为大框架来进行服务端的开发。数据库采用的是经典的关系型数据库 MySql。同时为了建立 WebSocket 连接,处理 WebSocket 请求,选择了 socket.io 的一个 Java 移植版本 netty-socketio。netty-socketio是一个开源的Socket.io服务器端的一个java的实现,它基于Netty框架,可用于服务端推送消息给客户端。

整体功能说明
本系统主要包含六个大的功能模块:登陆注册、用户信息获取、信息发送、好友列表显示、头像上传以及退出系统。其中信息发送是本次课程设计最重要的部分,是安全通信的主要体现。

5 安全加密部分代码说明
整体设计

HTTP加密
Token产生
private static String sign(String userId,String password){Algorithm algorithm = Algorithm.HMAC256(password);String token = JWT.create().withClaim(CLAIM_USERID_NAME,userId).withExpiresAt(new Date(System.currentTimeMillis()+EXPIRED_TIME/2)).sign(algorithm);return token;
}/**
* 生成一个登录token
* @param userId
* @param password
* @return
*/
public static String loginSign(String userId,String password){String token = sign(userId,password);cache.putToken(token,token);return token;
}
每次登录产生Token,并存储在前端的localStorage中,每次发送HTTP的POST和GET请求时加在HTTP Header中的Authorization位。(解决XSS和XSRF问题)
Token认证
后端接收HTTP请求时需要认证Token。
如此做可以认证发送HTTP请求的用户身份,适用于所有HTTP请求
/*** 验证客户端传来token是否有效* 验证逻辑顺序如下:* 1. token是否为空* 2. token中账号是否存在* 3. 根据token中账号从数据库中获取真实密码等用户信息,并验证用户信息是否有效*/public static void verifyToken(String clientToken, stu.software.chatroom.common.CommonService commonService){if(!StringUtils.hasText(clientToken)){//token为空throw new RuntimeException("无登录令牌!");}//从客户端登录令牌中获取当前用户账号String userId = JWT.decode(clientToken).getClaim(CLAIM_USERID_NAME).asString();if(!StringUtils.hasText(userId)){//token中账号不存在throw new RuntimeException("登录令牌失效!");}//取出缓存中的登录令牌String cacheToken = cache.getToken(clientToken);if(!StringUtils.hasText(cacheToken)){//缓存中没有登录令牌throw new RuntimeException("登录令牌失效!");}User user = commonService.getUserById(userId);if(user==null){//用户不存在throw new RuntimeException("用户不存在!");}//验证Token有效性try{Algorithm algorithm = Algorithm.HMAC256(user.getU_pwd());JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(CLAIM_USERID_NAME,userId).build();//构建验证器jwtVerifier.verify(cacheToken);}catch(TokenExpiredException e){//令牌过期,刷新令牌String newToken = sign(userId,user.getU_pwd());cache.putToken(clientToken,newToken);}catch(Exception e){e.printStackTrace();//令牌验证未通过throw new RuntimeException("令牌错误!请登录。");}
注册密码加密
使用SHA256加密注册时用户使用的密码,数据库中存的是密文,这样可防止数据库被攻击导致密码泄露。
/***
* 利用Apache的工具类实现SHA-256加密
* @return str 加密后的报文
*/
public static String getSHA256Str(String str) {MessageDigest messageDigest;String encodeSir = str;try {messageDigest = MessageDigest.getInstance("SHA-256");byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));encodeSir = Hex.encodeHexString(hash);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return encodeSir;
}/**
* 通过该方法将密码加密(实际上并没有)
*/
public static String encodePwd(String u_pwd) {// 密码通过此方法解密并再加密return getSHA256Str(u_pwd);
}
登录密码加密
登录时,前端输入明文密码,使用SHA256加密该密码以后,再加数据发送到后端。后端根据该加密后的密码与数据库比对,从而验证用户身份。
此做法避免了前端请求数据被拦截导致密码泄露。
import { sha256 } from 'js-sha256';/*** 加密方法*/
export function PASSWORD(str) {let encodedStr = str;encodedStr = sha256(encodedStr);return encodedStr;
}
const login = () => {post("/user/login", {u_name: u_name.value,u_pwd: PASSWORD(u_pwd.value),}).then((res) => {tip.success(res.message);let token = res.data;setLocalToken(token);router.push({ name: "Room", query: { usr: u_name.value } });}).catch((err) => {tip.error("账号密码错误!");});
};
密钥分配——使用Keytool
参考教程 https://blog.csdn.net/m0_59579040/article/details/124811147
keytool 是个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性以及认证服务。它还允许用户储存他们的通信对等者的公钥(以证书形式)。
在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。
通过如下步骤可以产生证书和公钥
keytool -genkeypair -storetype PKCS12 -alias yjq - -keyalg RSA -keysize 1024 -dname "CN=xxx, OU=xxx, O=xxx, L=xx, ST=xx, C=CN" -keystore D:\mygit\大三下笔记\网安课设\safechat-server\src\main\resources\keys-and-certs\yjq.keystore -keypass 123456 -storepass 123456 -validity 36500 -v
产生二进制文件yjq.keystore,以上部分可由脚本生成。
经过KeyStore的相关操作生成公钥、证书和私钥

当用户需要公钥和私钥时,只需要调用相关方法即可。
public static void genKeyPair(String name) throws Exception { //以 PKCS12 规格,创建 KeyStore KeyStore keyStore = KeyStore.getInstance("PKCS12"); path = "keys-and-certs/" + name + ".keystore"; //载入 jks 和该 jks 的密码 到 KeyStore 内 keyStore.load(new FileInputStream(new ClassPathResource("keys-and-certs/yjq.keystore").getFile()), "123456".toCharArray()); // 要获取 key,需要提供 KeyStore 的别名 和该 KeyStore 的密码 // 获取 keyStore 内所有别名 alias Enumeration<String> aliases = keyStore.aliases(); String alias = null; alias = aliases.nextElement(); char[] keyPassword = "123456".toCharArray(); keyPairString.clear(); //私钥 privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword); keyPairString.put("PR", new String(Base64.getEncoder().encode(privateKey.getEncoded()))); //证书 Certificate certificate = keyStore.getCertificate(alias); //公钥 publicKey = certificate.getPublicKey(); keyPairString.put("PU", new String(Base64.getEncoder().encode(publicKey.getEncoded()))); }
使用公钥加密保证消息认证和机密性
参考教程https://blog.csdn.net/m0_59579040/article/details/124811147.
A和B进行通信,首先使用A的私钥对报文M进行加密——数字签名;然后A用B的公钥对上述结果进行加密——保证了保密性。
B收到消息后,用B的私钥解密,再用A的公钥验证签名。

这里我使用RSA作为加密算法、SHA1WithRSA作为签名算法,签名和加密的操作实现在类RSAUtils.java中。
签名
/**
* 私钥签名
* @param content 字符串
* @param priKey 私钥
* @return
* @throws Exception
*/
public static byte[] sign(String content, PrivateKey priKey) throws Exception { Signature signature = Signature.getInstance(SIGALG); signature.initSign(priKey); signature.update(content.getBytes()); return signature.sign();
} /**
* 公钥验证签名
* @param content 字符串
* @param sign 签名
* @param pubKey 公钥
* @return 身份是否真实
* @throws Exception
*/
public static boolean verify(String content, byte[] sign, PublicKey pubKey) throws Exception { Signature signature = Signature.getInstance(SIGALG); signature.initVerify(pubKey); signature.update(content.getBytes()); return signature.verify(sign);
}
加密解密
/**
* RSA公钥加密
*
* @param content 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(String content, String publicKey) throws Exception { //base64编码的公钥 byte[] decoded = Base64.getMimeDecoder().decode(publicKey); RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(KEYALG).generatePublic(new X509EncodedKeySpec(decoded)); System.out.println(pubKey.getAlgorithm()); //RSA加密 Cipher cipher = Cipher.getInstance(KEYALG); cipher.init(Cipher.ENCRYPT_MODE, pubKey); String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(content.getBytes("UTF-8"))); return outStr;
} /**
* RSA私钥解密
*
* @param content 加密字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(String content, String privateKey) throws Exception { //64位解码加密后的字符串 byte[] inputByte = Base64.getMimeDecoder().decode(content); // //base64编码的私钥 byte[] decoded = Base64.getMimeDecoder().decode(privateKey); RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded)); //RSA解密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, priKey); String outStr = new String(cipher.doFinal(inputByte)); return outStr;
}
使用AES加密消息
因为公钥加密的消息认证比较费时间,所以当两个用户建立消息通信时由一方产生会话密钥,使用公钥加密来传送会话密钥并认证身份。身份认证完成后,使用该会话密钥加密消息,其中使用对称加密技术AES加密消息。
消息报文格式如下:
1、 id:报文标识id;
2、 time:报文发送时间
3、 content:报文内容(加密)
4、 type:报文类型:会话密钥消息/公钥消息
5、 sender_name:发送者
6、 receiver_name:接收者
7、 sign:发送者签名。
加密过程如下:
public final class AESUtils{ private static final String ALGORITHM = "AES"; public static String genAesSecret(){ try { KeyGenerator kg = KeyGenerator.getInstance("AES"); //下面调用方法的参数决定了生成密钥的长度,可以修改为128, 192或256 kg.init(256); SecretKey sk = kg.generateKey(); byte[] b = sk.getEncoded(); String secret = Base64.encodeBase64String(b); return secret; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("没有此算法"); } } /** * 根据密钥对指定的明文plainText进行加密. * * @param plainBytes 明文 * @param keyBytes 密码 * @return 加密后的密文. * @since 0.0.8 */ public static byte[] encrypt(byte[] plainBytes, byte[] keyBytes) { try { SecretKey secretKey = getSecretKey(keyBytes); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(plainBytes); } catch (Exception e) { throw new RuntimeException(e); } } /** * 根据密钥对指定的密文 cipherBytes 进行解密. * * @param cipherBytes 加密密文 * @param keyBytes 秘钥 * @return 解密后的明文. * @since 0.0.8 */ public static byte[] decrypt(byte[] cipherBytes, byte[] keyBytes) { try { SecretKey secretKey = getSecretKey(keyBytes); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(cipherBytes); } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取加密 key * @param keySeed seed * @return 结果 * @since 0.0.8 */ private static SecretKey getSecretKey(byte[] keySeed) { try { // 避免 linux 系统出现随机的问题 SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(keySeed); KeyGenerator generator = KeyGenerator.getInstance("AES"); generator.init(secureRandom); return generator.generateKey(); } catch (Exception e) { throw new RuntimeException(e); } }
}
服务端加密
结合RSA与AES的加密如下:
先用公钥加密RSA发送对称加密使用的会话密钥,然后再用会话密钥进行AES对称加密通信。
// 监听客户端发送消息
socketIOServer.addEventListener(Constants.EVENT_MESSAGE_TO_SERVER, String.class, (client, data, ackSender) -> { String sender_name = getParamsByClient(client, "u_name"); ObjectMapper mapper = new ObjectMapper(); Message message = mapper.readValue(data, Message.class); String receiver_name = message.getReceiver_name(); if (message.getType().equals(Constants.MASTER_MESSAGE)) { //使用公钥加密传送会话密钥 if (AesKey.equals("")) { log.info("用户" + sender_name + "生成会话密钥"); AesKey = AESUtils.genAesSecret(); message.setContent(AesKey); log.info("用户" + sender_name + "使用用户" + sender_name + "的私钥对会话密钥进行签名"); String sign = new String(RSAUtils.sign(message.getContent(), RSAUtils.getPrivateKey()), "ISO-8859-1"); message.setSign(sign); String result = RSAUtils.encrypt(message.getContent(), publicKeyStringMap.get(receiver_name)); log.info("使用用户" + receiver_name + "的公钥对会话密钥进行加密:" + result); message.setContent(result); sendMessageToFriend(message.getReceiver_name(), message); } else { return; } } else { //使用会话密钥发送消息 byte[] bytes = AESUtils.encrypt(message.getContent().getBytes(), AesKey.getBytes()); String encrypt = new String(bytes, "ISO-8859-1"); log.info("用户" + sender_name + "使用会话密钥加密消息"); message.setContent(encrypt); sendMessageToFriend(message.getReceiver_name(), message); }
});
//
//GBK, GB2312,UTF-8等一些编码方式为多字节或者可变长编码,原来的字节数组就被改变了,再转回原来的byte[]数组就会发生错误了。
//ISO-8859-1通常叫做Latin-1,Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,其中 0~127的字符与ASCII码相同,
// 它是单字节的编码方式,在来回切换时不会出现错误。 // 监听客户端接收消息
socketIOServer.addEventListener("receive_triger", String.class, (client, data, ackSender) -> { ObjectMapper mapper = new ObjectMapper(); Message message = mapper.readValue(data, Message.class); String sender_name = message.getSender_name(); String receiver_name = message.getReceiver_name(); if (message.getType().equals(Constants.MASTER_MESSAGE)) { log.info("收到来自" + sender_name + "发送给" + message.getReceiver_name() + "的消息: " + message.getContent()); String result = RSAUtils.decrypt(message.getContent(), RSAUtils.getKeyPair().get("PR")); log.info("用户" + receiver_name + "使用用户" + receiver_name + "的私钥对消息进行解密:"); message.setContent(result); log.info("用户" + receiver_name + "使用用户" + sender_name + "的公钥对消息进行验证签名"); Boolean sign = (RSAUtils.verify(message.getContent(), message.getSign().getBytes("ISO-8859-1"), publicKeyMap.get(sender_name))); if (sign) { log.info("签名验证成功!身份无误"); } else { throw new Exception("签名错误!"); } receiveMessageFromFriend(message.getReceiver_name(), message); } else { log.info("收到来自" + sender_name + "发送给" + message.getReceiver_name() + "的消息: " + message.getContent()); String text = new String(AESUtils.decrypt(message.getContent().getBytes("ISO-8859-1"), AesKey.getBytes()), "UTF-8"); log.info("用户" + receiver_name + "使用会话密钥进行解密"); message.setContent(text); receiveMessageFromFriend(message.getReceiver_name(), message); }
});
6 演示
登录

进入主页面
可以看到好友列表

同时获取本地密钥库中的公私钥并将其加入公钥库

选择好友进行私聊
选择好友进行私聊,进入聊天界面。


发送消息
在输入框中输入消息,点击发送,接收者和发送者的聊天框都会出现相应的消息。此消息是经过后端AES对称加密解密得到的。

相关文章:
网络安全实验——安全通信软件safechat的设计
网络安全实验——安全通信软件safechat的设计 仅供参考,请勿直接抄袭,抄袭者后果自负。 仓库地址: 后端地址:https://github.com/yijunquan-afk/safechat-server 前端地址: https://github.com/yijunquan-afk/safec…...
【MySQL】MySQL的事务
目录 概念 什么是事务? 理解事务 事务操作 事务的特性 事务的隔离级别 事务的隔离级别-操作 概念 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查 询、更新和删除数据。 不同的存储引擎提供…...
Java分布式事务(七)
文章目录🔥Seata提供XA模式实现分布式事务_业务说明🔥Seata提供XA模式实现分布式事务_下载启动Seata服务🔥Seata提供XA模式实现分布式事务_转账功能实现上🔥Seata提供XA模式实现分布式事务_转账功能实现下🔥Seata提供X…...
二十八、实战演练之定义用户类模型、迁移用户模型类
1. Django默认用户模型类 (1)Django认证系统中提供了用户模型类User保存用户的数据。 User对象是认证系统的核心。 (2)Django认证系统用户模型类位置 django.contrib.auth.models.User(3)父类AbstractUs…...
Java Virtual Machine的结构 3
1 Run-Time Data Areas 1.1 The pc Register 1.2 Java Virtual Machine Stacks 1.3 Heap 1.4 Method Area JVM方法区是在JVM所有线程中共享的内存区域,在编程语言中方法区是用于存储编译的代码、在操作系统进程中方法区是用于存储文本段,在JVM中方法…...
linux ubuntu22 安装neo4j
环境:neo4j 5 ubuntu22 openjdk-17 neo4j 5 对 jre 版本要求是 17 及以上,且最好是 openjdk,使用比较新的 ubuntu 系统安装比较好, centos7 因为没有维护,yum 找不到 openjdk-17了。 官方的 debian 系列安装教程&a…...
模型实战(7)之YOLOv8推理+训练自己的数据集详解
YOLOv8推理+训练自己的数据集详解 最近刚出的yolov8模型确实很赞啊,亲测同样的数据集用v5和v8两个模型训练+预测,结果显示v8在检测精度和准确度上明显强于v5。下边给出yolov8的效果对比图: 关于v8的结构原理在此不做赘述,随便搜一下到处都是。1.环境搭建 进入github进行git…...
火车进出栈问题 题解
来源 卡特兰数 个人评价(一句话描述对这个题的情感) …~%?..,# *☆&℃$︿★? 1 题面 一列火车n节车厢,依次编号为1,2,3,…,n。每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种。 输入…...
Unity学习日记12(导航走路相关、动作完成度返回参数)
目录 动作的曲线与函数 创建遮罩 导航走路 设置导航网格权重 动作的曲线与函数 执行动作,根据动作完成度返回参数。 函数,在代码内执行同名函数即可调用。在执行关键帧时调用。 创建遮罩 绿色为可效用位置 将其运用到Animator上的遮罩,可…...
基于bearpi的智能小车--Qt上位机设计
基于bearpi的智能小车--Qt上位机设计 前言一、界面原型1.主界面2.网络配置子窗口模块二、设计步骤1.界面原型设计2.控件添加信号槽3.源码解析3.1.网络链接核心代码3.2.网络设置子界面3.3.小车控制核心代码总结前言 最近入手了两块小熊派开发板,借智能小车案例,进行鸿蒙设备学…...
汇编语言与微机原理(1)基础知识
前言(1)本人使用的是王爽老师的汇编语言第四版和学校发的微机原理教材配合学习。(2)推荐视频教程通俗易懂的汇编语言(王爽老师的书);贺老师C站账号网址;(3)文…...
ASEMI代理瑞萨TW8825-LA1-CR汽车芯片
编辑-Z TW8825-LA1-CR在单个封装中集成了创建多用途车载LCD显示系统所需的许多功能。它集成了高质量的2D梳状NTSC/PAL/SECAM视频解码器、三重高速RGB ADC、高质量缩放器、多功能OSD和高性能MCU。TW8825-LA1-CR其图像视频处理能力包括任意缩放、全景缩放、图像镜像、图像调整和…...
什么是 .com 域名?含义和用途又是什么?
随着网络的发展,网络上出现了各种不同后缀的域名,这些域名的后缀各有不同的含义,也有不同的用途。今天,我们就一起来探讨一下 .com 后缀的域名知识。 .com 域名是一种最常见的顶级域名,它是由美国国家网络信息中心&…...
VueX快速入门(适合后端,无脑入门!!!)
文章目录前言State和Mutations基础简化gettersMutationsActions(异步)Module总结前言 作为一个没啥前端基础(就是那种跳过js直接学vue的那种。。。)的后端选手。按照自己的思路总结了一下对VueX的理解。大佬勿喷qAq。 首先我们需要…...
前列腺癌论文笔记
名词解释 MRF: 磁共振指纹打印技术( MR Fingerprinting)是近几年发展起来的最新磁共振技术,以一种全新的方法对数据进行采集、后处理和实现可视化。 MRF使用一种伪随机采集方法,取代了过去为获得个体感兴趣的参数特征而使用重复系列数据的采集方法&…...
Python+Yolov5道路障碍物识别
PythonYolov5道路障碍物识别如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助!前言这篇博客针对<<PythonYolov5道路障碍物识别>>编写代码,代码整洁,规则,易读。 学习与…...
全新升级,EasyV 3D高德地图组件全新上线
当我们打开任意一个可视化搭建工具或者搜索数据可视化等关键词,我们会发现「地图」是可视化领域中非常重要的一种形式,对于许多可视化应用场景都具有非常重要的意义,那对于EasyV,地图又意味着什么呢?EasyV作为数字孪生…...
从管理到变革,优秀管理者的进阶之路
作为一位管理者,了解自身需求、企业需求和用户需求是非常重要的。然而,仅仅满足这些需求是不够的。我们还需要进行系统化的思考,以了解我们可以为他人提供什么价值,以及在企业中扮演什么样的角色。只有清晰的自我定位,…...
安装Anaconda3
安装Anaconda3 下载安装文件 可以去官网下载 https://repo.anaconda.com/archive/根据自己的操作系统选择合适的Anaconda版本 我选择的是Anaconda3-2021.05-Linux-x86_64.sh的版本 方法一:可以下载到本地然后在上传到虚拟机 方法二:在终端输入以下…...
HTTPS,SSL(对称加密和非对称加密详解)
上一篇博客(HTTP详解_徐憨憨!的博客-CSDN博客)详细讲解了关于HTTP的知识,了解到HTTP协议下的数据传输是一种明文传输,既然是明文传输,可能导致在传输过程中出现一些被篡改的情况,此时就需要对所…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...
jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
Netty自定义协议解析
目录 自定义协议设计 实现消息解码器 实现消息编码器 自定义消息对象 配置ChannelPipeline Netty提供了强大的编解码器抽象基类,这些基类能够帮助开发者快速实现自定义协议的解析。 自定义协议设计 在实现自定义协议解析之前,需要明确协议的具体格式。例如,一个简单的…...
vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能
vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能 查看官网:https://vxetable.cn 效果 代码 通过 checkbox-config.isShift 启用批量选中,启用后按住快捷键和鼠标批量选取 <template><div><vxe-grid v-bind"gri…...
