Spring-Cloud-Gateway-实现XSS、SQL注入拦截

XSS和SQL注入是Web应用中常见计算机安全漏洞,文章主要分享通过Spring Cloud Gateway 全局过滤器对XSS和SQL注入进行安全防范。
写这篇文章也是因为项目在经过安全组进行安全巡检时发现项目存储该漏洞后进行系统整改,本文的运行结果是经过安全组验证通过。

使用版本

  • spring-cloud-dependencies Hoxton.SR7
  • spring-boot-dependencies 2.2.9.RELEASE
  • spring-cloud-gateway 2.2.4.RELEASE

核心技术点

1. AddRequestParameterGatewayFilterFactory 获取get请求参数并添加参数然后重构get请求

public GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {URI uri = exchange.getRequest().getURI();StringBuilder query = new StringBuilder();//获取请求url携带的参数,?号后面参数体,类似cl=3&tn=baidutop10&fr=top1000&wd=31 String originalQuery = uri.getRawQuery();if (StringUtils.hasText(originalQuery)) {query.append(originalQuery);if (originalQuery.charAt(originalQuery.length() - 1) != '&') {query.append('&');}}String value = ServerWebExchangeUtils.expand(exchange, config.getValue());query.append(config.getName());query.append('=');query.append(value);try {//重构请求uriURI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri();ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();return chain.filter(exchange.mutate().request(request).build());} catch (RuntimeException var9) {throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");}}public String toString() {return GatewayToStringStyler.filterToStringCreator(AddRequestParameterGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();}};}

2. [Spring Cloud Gateway中RequestBody只能获取一次的问题解决方案](https://blog.csdn.net/dear_little_bear/article/details/105319657)
3. Spring Gateway GlobalFilter

技术实现

  1. 创建Filter 实现GlobalFilter, Ordered
@Slf4j
@Component
public class SqLinjectionFilter implements GlobalFilter, Ordered {@SneakyThrows@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){// grab configuration from Config objectlog.debug("----自定义防XSS攻击网关全局过滤器生效----");ServerHttpRequest serverHttpRequest = exchange.getRequest();HttpMethod method = serverHttpRequest.getMethod();String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);URI uri = exchange.getRequest().getURI();Boolean postFlag = (method == HttpMethod.POST || method == HttpMethod.PUT) &&(MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType));//过滤get请求if (method == HttpMethod.GET) {String rawQuery = uri.getRawQuery();if (StringUtils.isBlank(rawQuery)){return chain.filter(exchange);}log.debug("原请求参数为:{}", rawQuery);// 执行XSS清理rawQuery = XssCleanRuleUtils.xssGetClean(rawQuery);log.debug("修改后参数为:{}", rawQuery);//	如果存在sql注入,直接拦截请求if (rawQuery.contains("forbid")) {log.error("请求【" + uri.getRawPath() + uri.getRawQuery() + "】参数中包含不允许sql的关键词, 请求拒绝");return setUnauthorizedResponse(exchange);}try {//重新构造get requestURI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(rawQuery).build(true).toUri();ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();return chain.filter(exchange.mutate().request(request).build());} catch (Exception e) {log.error("get请求清理xss攻击异常", e);throw new IllegalStateException("Invalid URI query: \"" + rawQuery + "\"");}}//post请求时,如果是文件上传之类的请求,不修改请求消息体else if (postFlag){return DataBufferUtils.join(serverHttpRequest.getBody()).flatMap(d -> Mono.just(Optional.of(d))).defaultIfEmpty(Optional.empty()).flatMap(optional -> {// 取出body中的参数String bodyString = "";if (optional.isPresent()) {byte[] oldBytes = new byte[optional.get().readableByteCount()];optional.get().read(oldBytes);bodyString = new String(oldBytes, StandardCharsets.UTF_8);}HttpHeaders httpHeaders = serverHttpRequest.getHeaders();// 执行XSS清理log.debug("{} - [{}:{}] XSS处理前参数:{}", method, uri.getPath(), bodyString);bodyString = XssCleanRuleUtils.xssPostClean(bodyString);log.info("{} - [{}:{}] XSS处理后参数:{}", method, uri.getPath(), bodyString);//	如果存在sql注入,直接拦截请求if (bodyString.contains("forbid")) {log.error("{} - [{}:{}] 参数:{}, 包含不允许sql的关键词,请求拒绝", method, uri.getPath(), bodyString);return setUnauthorizedResponse(exchange);}ServerHttpRequest newRequest = serverHttpRequest.mutate().uri(uri).build();// 重新构造bodybyte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);DataBuffer bodyDataBuffer = toDataBuffer(newBytes);Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);// 重新构造headerHttpHeaders headers = new HttpHeaders();headers.putAll(httpHeaders);// 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度int length = newBytes.length;headers.remove(HttpHeaders.CONTENT_LENGTH);headers.setContentLength(length);headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf8");// 重写ServerHttpRequestDecorator,修改了body和header,重写getBody和getHeaders方法newRequest = new ServerHttpRequestDecorator(newRequest) {@Overridepublic Flux<DataBuffer> getBody() {return bodyFlux;}@Overridepublic HttpHeaders getHeaders() {return headers;}};return chain.filter(exchange.mutate().request(newRequest).build());});} else {return chain.filter(exchange);}}// 自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}/*** 设置403拦截状态*/private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange) {return WebfluxResponseUtil.responseFailed(exchange, HttpStatus.FORBIDDEN.value(),"request is forbidden, SQL keywords are not allowed in the parameters.");}/*** 字节数组转DataBuffer** @param bytes 字节数组* @return DataBuffer*/private DataBuffer toDataBuffer(byte[] bytes) {NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);buffer.write(bytes);return buffer;}}
  1. 定义xss注入、sql注入工具类
@Slf4j
public class XssCleanRuleUtils {private final static Pattern[] scriptPatterns = {Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)};private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";private static Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);//整体都忽略大小写/*** GET请求参数过滤* @param value* @return*/public static String xssGetClean(String value) throws UnsupportedEncodingException {//过滤xss字符集if (value != null) {value = value.replaceAll("\0|\n|\r", "");for (Pattern pattern : scriptPatterns) {value = pattern.matcher(value).replaceAll("");}value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");}//sql关键字检查return cleanGetSqlKeyWords(value);}public static String xssPostClean(String value) {//过滤xss字符集if (value != null) {value = value.replaceAll("\0|\n|\r", "");for (Pattern pattern : scriptPatterns) {value = pattern.matcher(value).replaceAll("");}value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");}//sql关键字检查return cleanPostSqlKeyWords(value);}/*** 解析参数SQL关键字* @param value* @return*/private static String cleanGetSqlKeyWords(String value) throws UnsupportedEncodingException {//参数需要url编码//这里需要将参数转换为小写来处理//不改变原值//value示例 order=asc&pageNum=1&pageSize=100&parentId=0String lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();//获取到请求中所有参数值-取每个key=value组合第一个等号后面的值boolean isContains = Stream.of(lowerValue.split("\\&")).map(kp -> kp.substring(kp.indexOf("=") + 1)).parallel().anyMatch(param -> {if (sqlPattern.matcher(param).find()){log.error("参数中包含不允许sql的关键词");return true;}return false;});return isContains ? "forbid" : value;}/*** 解析参数SQL关键字* @param value* @return*/private static String cleanPostSqlKeyWords(String value){JSONObject json = JSONObject.parseObject(value);Map<String, Object> map = json;Map<String, Object> mapjson = new HashMap<>();for (Map.Entry<String, Object> entry : map.entrySet()) {String value1 =  entry.getValue().toString();//这里需要将参数转换为小写来处理-不改变原值String lowerValue = value1.toLowerCase();if (sqlPattern.matcher(lowerValue).find()){log.error("参数中包含不允许sql的关键词");value1 = "forbid";mapjson.put(entry.getKey(),value1);break;} else {mapjson.put(entry.getKey(),entry.getValue());}}return JSONObject.toJSONString(mapjson);}

踩坑过程

  1. sql注入过滤规则
    网上大多数sql注入拦截规则都是使用一个sql关键字匹配,
//定义sql注入关键字
String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|%|chr|mid|master|truncate|" +"char|declare|sitename|net user|xp_cmdshell|;|or|+|,|like'|and|exec|execute|insert|create|drop|" +"table|from|grant|use|group_concat|column_name|" +"information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|" +"chr|mid|master|truncate|char|declare|or|;|--|,|like|//|/|%|#";
//过滤规则
for (String bad : badStrs) {if (value1.equalsIgnoreCase(bad)) {value1 = "forbid";mapjson.put(entry.getKey(),value1);break;} else {mapjson.put(entry.getKey(),entry.getValue());}}}

最初我们也是使用改方式,但是关键字匹配方式实在太容易误杀正常业务,且容易漏,比如

select/*/1from/*/tt 

这样形式的参数就无法过滤。
最后我们还是采取sql正则匹配的方式(见代码),已和安全工程师完成联调,能够挡住安全工程师的注入测试案例,对业务也完成回归测试,基本不影响现有业务正常运行。
2. get请求拦截过程不要对源参数进行url编码,否则应用可能出现不必要的错误

get请求参数中的中文字符以及一些特色字符请求到服务器会自动编码,在对xss注入过滤过程需要进行url编码才能进行过滤规则的验证,在起初我们是在源参数上进行编码,但是一些正常请求中携带+号这样的特色符号的请求在处理过程会被过滤掉,该问题排查了许久才发现的,因此建议不要改变源请求参数的编码格式

 //参数需要url编码//这里需要将参数转换为小写来处理//不改变原值//value示例 order=asc&pageNum=1&pageSize=100&parentId=0String lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();
  1. RequestBody只能获取一次的问题
    代码参考了网上对于RequestBody只能获取一次的问题解决的方案,在spring-cloud-gateway 2.2.4.RELEASE验证有效

优化

拦截器在实际生产运行过程存在一些列问题:

  1. 对xss字符集的转换会导致会第三方平台接入的接口出现一些列问题,尤其是需要参数签名验签的接口,因为参数的变化导致验签不成功
  2. 对于第三方平台(尤其时强势的第三方),我们往往无法要求第三方按照我们的参数规则传递参数,这类的接口会包含sql注入的关键字
  3. 在请求重构过程,可能会改变参数的结构,会导致验签失败
  4. 对post请求,虽然目前前后端大多交互都是通过Json,但如有特殊请求参数可能是非Json格式参数,需要多改类型参数进行兼容

因此,在实现XSS、SQL注入拦截基础上进行优化,移除xss字符集转换且不改变请求参数,增加白名单机制,具体实现如下:

@Slf4j
@Component
@ConfigurationProperties(prefix = "gateway.security.ignore")
@RefreshScope
public class SqLinjectionFilter implements GlobalFilter, Ordered {private String[] sqlinjectionHttpUrls = new String[0];@SneakyThrows@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){// grab configuration from Config objectlog.debug("----自定义防sql注入网关全局过滤器生效----");ServerHttpRequest serverHttpRequest = exchange.getRequest();HttpMethod method = serverHttpRequest.getMethod();String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);URI uri = exchange.getRequest().getURI();//1.动态刷新 sql注入的过滤的路径String path = serverHttpRequest.getURI().getRawPath();String matchUrls[] = this.getSqlinjectionHttpUrls();if( AuthUtils.isMatchPath(path, matchUrls)){log.error("请求【{}】在sql注入过滤白名单中,直接放行", path);return chain.filter(exchange);}Boolean postFlag = (method == HttpMethod.POST || method == HttpMethod.PUT) &&(MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType));//过滤get请求if (method == HttpMethod.GET) {String rawQuery = uri.getRawQuery();if (StringUtils.isBlank(rawQuery)){return chain.filter(exchange);}log.debug("请求参数为:{}", rawQuery);// 执行sql注入校验清理boolean chkRet = SqLinjectionRuleUtils.getRequestSqlKeyWordsCheck(rawQuery);//	如果存在sql注入,直接拦截请求if (chkRet) {log.error("请求【" + uri.getRawPath() + uri.getRawQuery() + "】参数中包含不允许sql的关键词, 请求拒绝");return setUnauthorizedResponse(exchange);}//透传参数,不对参数做任何处理return chain.filter(exchange);}//post请求时,如果是文件上传之类的请求,不修改请求消息体else if (postFlag){return DataBufferUtils.join(serverHttpRequest.getBody()).flatMap(d -> Mono.just(Optional.of(d))).defaultIfEmpty(Optional.empty()).flatMap(optional -> {// 取出body中的参数String bodyString = "";if (optional.isPresent()) {byte[] oldBytes = new byte[optional.get().readableByteCount()];optional.get().read(oldBytes);bodyString = new String(oldBytes, StandardCharsets.UTF_8);}HttpHeaders httpHeaders = serverHttpRequest.getHeaders();// 执行XSS清理log.debug("{} - [{}] 请求参数:{}", method, uri.getPath(), bodyString);if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) {//如果MediaType是json才执行json方式验证chkRet = SqLinjectionRuleUtils.postRequestSqlKeyWordsCheck(bodyString);} else {//form表单方式,需要走get请求chkRet = SqLinjectionRuleUtils.getRequestSqlKeyWordsCheck(bodyString);}//	如果存在sql注入,直接拦截请求if (chkRet) {log.error("{} - [{}] 参数:{}, 包含不允许sql的关键词,请求拒绝", method, uri.getPath(), bodyString);return setUnauthorizedResponse(exchange);}ServerHttpRequest newRequest = serverHttpRequest.mutate().uri(uri).build();// 重新构造bodybyte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);DataBuffer bodyDataBuffer = toDataBuffer(newBytes);Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);// 重新构造headerHttpHeaders headers = new HttpHeaders();headers.putAll(httpHeaders);// 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度int length = newBytes.length;headers.remove(HttpHeaders.CONTENT_LENGTH);headers.setContentLength(length);headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf8");// 重写ServerHttpRequestDecorator,修改了body和header,重写getBody和getHeaders方法newRequest = new ServerHttpRequestDecorator(newRequest) {@Overridepublic Flux<DataBuffer> getBody() {return bodyFlux;}@Overridepublic HttpHeaders getHeaders() {return headers;}};return chain.filter(exchange.mutate().request(newRequest).build());});} else {return chain.filter(exchange);}}// 自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}/*** 设置403拦截状态*/private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange) {return WebfluxResponseUtil.responseFailed(exchange, HttpStatus.FORBIDDEN.value(),"request is forbidden, SQL keywords are not allowed in the parameters.");}/*** 字节数组转DataBuffer** @param bytes 字节数组* @return DataBuffer*/private DataBuffer toDataBuffer(byte[] bytes) {NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);buffer.write(bytes);return buffer;}public String[] getSqlinjectionHttpUrls() {return sqlinjectionHttpUrls;}public void setSqlinjectionHttpUrls(String[] sqlinjectionHttpUrls) {this.sqlinjectionHttpUrls = sqlinjectionHttpUrls;}
}@Slf4j
public class SqLinjectionRuleUtils {private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";private static Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);//整体都忽略大小写/*** get请求sql注入校验* @param value* @return*/public static boolean getRequestSqlKeyWordsCheck(String value) throws UnsupportedEncodingException {//参数需要url编码//这里需要将参数转换为小写来处理//不改变原值//value示例 order=asc&pageNum=1&pageSize=100&parentId=0String lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();//获取到请求中所有参数值-取每个key=value组合第一个等号后面的值return Stream.of(lowerValue.split("\\&")).map(kp -> kp.substring(kp.indexOf("=") + 1)).parallel().anyMatch(param -> {if (sqlPattern.matcher(param).find()){log.error("参数中包含不允许sql的关键词");return true;}return false;});}/*** post请求sql注入校验* @param value* @return*/public static boolean postRequestSqlKeyWordsCheck(String value){Object jsonObj = JSON.parse(value);if (jsonObj instanceof JSONObject) {JSONObject json = (JSONObject) jsonObj;Map<String, Object> map = json;//对post请求参数值进行sql注入检验return map.entrySet().stream().parallel().anyMatch(entry -> {//这里需要将参数转换为小写来处理String lowerValue = Optional.ofNullable(entry.getValue()).map(Object::toString).map(String::toLowerCase).orElse("");if (sqlPattern.matcher(lowerValue).find()){log.error("参数[{}]中包含不允许sql的关键词", lowerValue);return true;}return false;});} else {JSONArray json = (JSONArray) jsonObj;List<Object> list = json;//对post请求参数值进行sql注入检验return list.stream().parallel().anyMatch(obj -> {//这里需要将参数转换为小写来处理String lowerValue = Optional.ofNullable(obj).map(Object::toString).map(String::toLowerCase).orElse("");if (sqlPattern.matcher(lowerValue).find()){log.error("参数[{}]中包含不允许sql的关键词", lowerValue);return true;}return false;});}}

ps:网关全局拦截影响应用所有请求,拦截规则和对请求类型的兼容还需要根据项目线上实际情况进行调整。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/849063.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

玩物科技:引领物联网时代的创新先锋

在深圳这座充满活力和创新精神的城市&#xff0c;有一家年轻而充满潜力的公司正在悄然改变我们的日常生活。深圳市玩物科技有限公司自2017年成立以来&#xff0c;凭借其卓越的技术和创新理念&#xff0c;逐渐成为物联网时代的先锋力量。 玩物科技的愿景与使命 玩物科技的核心…

【vue3响应式原理】

# 源码结构 源码位置是在packages文件件内&#xff0c;实际上源码主要分为两部分&#xff0c;编译器和运行时环境 1. 编译器 compiler-core 核心编译逻辑compiler-dom 针对浏览器平台编译逻辑compiler-sfc 针对单文件组件编译逻辑compiler-ssr 针对服务端渲染编译逻辑 2. 运行时…

使用kafka tools工具连接带有用户名密码的kafka

使用kafka tools工具连接带有用户名密码的kafka 创建kafka连接&#xff0c;配置zookeeper 在Security选择Type类型为SASL Plaintext 在Advanced页面添加如下图红框框住的内容 在JAAS_Config加上如下配置 需要加的配置&#xff1a; org.apache.kafka.common.security.plain.Pla…

企业数字化转型的主要方面有哪些?

本人研究企业数字化转型10余年&#xff0c;为企业软件选型、数字化提供咨询服务&#xff01;目前重点研究低代码数字化转型玩法&#xff0c;力争为各行各业探索出一条最具性价比的数字化方式。 关于“企业数字化转型包括哪些方面”这个问题&#xff0c;咱先来看个例子哈~ 比如…

用负载绿原酸的纳米复合水凝胶调节巨噬细胞表型以加速伤口愈合

引用信息 文 章&#xff1a;Modulating macrophage phenotype for accelerated wound healing with chlorogenic acid-loaded nanocomposite hydrogel. 期 刊&#xff1a;Journal of Controlled Release&#xff08;影响因子&#xff1a;10.8&#xff09; 发表时间&a…

基于pytoch卷积神经网络水质图像分类实战

具体怎么学习pytorch&#xff0c;看b站刘二大人的视频。 完整代码&#xff1a; import numpy as np import os from PIL import Image import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data…

resultType的类型错误

resultType的类型错误&#xff0c;不能是List而应该是对应的返回Bean对象的类型&#xff0c;VO 这里是引用 org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: Error querying database. Cause: java.lang…

opencv进阶 ——(十二)基于三角剖分实现人脸对齐

三角剖分概念 三角剖分&#xff08;Triangulation&#xff09;是一种将多边形或曲面分解为一系列互不相交的三角形的技术&#xff0c;它是计算几何、计算机图形学、地理信息系统、工程和科学计算中的一个基本概念。通过三角剖分&#xff0c;复杂的形状可以被简化为基本的三角…

病理级Polymer酶标二抗IHC试剂盒上线!

免疫组织化学 Immunohistochemistry,lHC 是利用抗体与抗原特异性识别原理&#xff0c;对组织样本中的抗原进行定位/定性分析的实验技术。组织切片保留了样品的解剖学结构特征&#xff0c;从而可以高分辨率地显现蛋白在细胞&#xff0c;甚至细胞器中的定位。基于以上特性&…

生物相容性CY5.5-D-甘露糖细胞生物学研究

随着生物医学研究的深入发展&#xff0c;荧光标记技术在细胞生物学中的应用日益广泛。其中&#xff0c;CY5.5-D-甘露糖作为一种新型的荧光标记物&#xff0c;不仅继承了CY5.5荧光染料的光学性能&#xff0c;还结合了D-甘露糖的生物学特性&#xff0c;因此在细胞成像、药物研发等…

DBus 在Qt和C++中的使用Demo

一、DBus DBus&#xff08;D-Bus&#xff09;是一种跨进程通信机制&#xff0c;是一种消息总线系统。DBus提供了一种在应用程序之间进行通信和交互的方式&#xff0c;可以在不同的进程之间传递消息&#xff0c;并提供了一套API供开发者使用。 二、Qt中使用 功能&#xff1a;先获…

Apple - Image I/O Programming Guide

翻译自&#xff1a;Image I/O Programming Guide&#xff08;更新时间&#xff1a;2016-09-13 https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/ImageIOGuide/imageio_intro/ikpg_intro.html#//apple_ref/doc/uid/TP40005462 文章目录 …

orbslam2代码解读(1):数据预处理过程

写orbslam2代码解读文章的初衷 首先最近陆陆续续花了一两周时间学习视觉slam&#xff0c;因为之前主要是做激光slam&#xff0c;有一定基础所以学的也比较快&#xff0c;也是看完了视觉14讲的后端后直接看orbslam2的课&#xff0c;看的cvlife的课&#xff08;课里大部分是代码…

jenkins的简单使用

2.1.简介 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 2.4.Jenkins安装 1.下载安装包jenkins.war&#xff1b; 2.在安装…

笔记 | 软件工程04:软件项目管理

1 软件项目及其特点 1.1 什么是项目 1.2 项目特点 1.3 影响项目成功的因素 1.4 什么是软件项目 针对软件这一特定产品和服务的项目努力开展“软件开发活动",&#xff08;理解&#xff1a;软件项目是一种活动&#xff09; 1.5 软件项目的特点 1.6 军用软件项目的特点 2 …

一、搭建 Vue3 Admin 项目:从无到有的精彩历程

在前端开发的领域中&#xff0c;Vue3 展现出了强大的魅力&#xff0c;而搭建一个功能丰富的 Vue3 Admin 项目更是充满挑战与乐趣。今天&#xff0c;我将和大家分享我搭建 Vue3 Admin 项目的详细过程&#xff0c;其中用到了一系列重要的依赖包。 首先 让我们开启这个旅程。在确…

怎么用电脑把图片转换二维码?图片在线生成二维码的步骤内容

现在很多人会通过二维码来存储物品的信息图片&#xff0c;其他人可以通过扫描二维码的方式来查看对应的图片内容&#xff0c;那么当我们需要将一批图片每个单独生成二维码&#xff0c;该如何操作能够快速将图片转换二维码呢&#xff1f; 今天&#xff0c;小编来分享给大家一个…

CNN卷积神经网络

一、概述 卷积神经网络&#xff08;CNN&#xff09;是深度学习领域的重要算法&#xff0c;特别适用于处理具有网格结构的数据&#xff0c;比如说图像和音频。它起源于二十世纪80至90年代&#xff0c;但真正得到快速发展和应用是在二十一世纪&#xff0c;随着深度学习理论的兴起…

【ai】phc:安装issac环境且fix libstdc++.so 版本报错

Pycharm远程连接服务器(2023-11-9) 大神分享了pycharm远程连接ubuntu工作站的方法。 https://github.com/ZhengyiLuo/PHC 给出的操作同样适用: 参考 Pycharm远程连接服务器(2023-11-9) :前提是一样的 PHC的要求:isaac 创建 conda activate isaac

前端js 元素拖拽案例

js原生元素拖拽案例 下面是一个简单的使用原生 JavaScript 实现元素拖拽的代码示例&#xff1a; <!DOCTYPE html> <html> <head><style>.draggable {width: 100px;height: 100px;background-color: red;position: absolute;cursor: move;}</style&…