SpringSecurity中文文档(体系结构).md

体系结构(Architecture)

本节讨论基于 Servlet 的应用程序中 Spring Security 的高级体系结构。我们将在参考文献的身份验证、授权和防止利用部分中构建这种高层次的理解。

过滤器的综述 (A Review of Filters)

Spring Security 的 Servlet 支持基于 Servlet 过滤器,所以首先看一下过滤器的作用是有帮助的。下图显示了单个 HTTP 请求的处理程序的典型分层。

filterchain

*Figure 1. FilterChain*

客户机向应用程序发送一个请求,容器创建一个 FilterChain,其中包含 Filter 实例和 Servlet,它们应该根据请求 URI 的路径处理 HttpServletRequest。在 Spring MVC 应用程序中,Servlet 是 DispatcherServlet 的一个实例。一个 Servlet 最多只能处理一个 HttpServletRequest 和 HttpServletResponse:

  • 防止下游过滤器实例或 Servlet 被调用。在这种情况下,Filter 通常会写入 HttpServletResponse。
  • 修改下游过滤器实例和Servlet使用的HttpServletRequest或HttpServletResponse。

过滤器Filter的能力来自于传递给它的过滤器链FilterChain

FilterChain Usage Example

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {// do something before the rest of the applicationchain.doFilter(request, response); // invoke the rest of the application// do something after the rest of the application
}

由于 Filter 只影响下游 Filter 实例和 Servlet,因此调用每个 Filter 的顺序非常重要。

DelegatingFilterProxy

Spring 提供了一个名为 DelegatingFilterProxy的 Filter 实现,它允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间建立桥梁。Servlet 容器允许使用自己的标准注册 Filter 实例,但是它不知道 Spring 定义的 Bean。您可以通过标准的Servlet容器机制注册DelegatingFilterProxy,但是将所有工作委托给实现Filter的Spring Bean。

这是一张图,展示了DelegatingFilterProxy如何适应过滤器实例和过滤器链。

delegatingfilterproxy

Figure 2. DelegatingFilterProxy

DelegatingFilterProxy 从 ApplicationContext 中查找 Bean Filter0,然后调用 Bean Filter0。下面的列表展示了 DelegatingFilterProxy 的伪代码:

DelegatingFilterProxy Pseudo Code

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {Filter delegate = getFilterBean(someBeanName);  // 1delegate.doFilter(request, response);  // 2
}
  1. 延迟获取作为Spring Bean注册的Filter。例如,在DelegatingFilterProxy中,delegate是Bean Filter0的一个实例。(Lazily get Filter that was registered as a Spring Bean. For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0.)
  2. 将工作委托给 Spring Bean。

DelegatingFilterProxy的另一个优点是它允许懒加载Filter bean实例。这一点很重要,因为容器需要在启动之前注册Filter实例。然而,Spring通常使用ContextLoaderListener来加载Spring Beans,这在需要注册Filter实例之后才会完成。

FilterChainProxy

SpringSecurity 的 Servlet 支持包含在 FilterChainProxy 中。FilterChainProxy 是 Spring Security 提供的一个特殊的 Filter,它允许通过 SecurityFilterChain委托许多 Filter 实例。因为 FilterChainProxy 是一个 Bean,所以它通常包装在一个 DelegatingFilterProxy 中。

下图显示了 FilterChainProxy 的角色。

filterchainproxy

Figure 3. FilterChainProxy

SecurityFilterChain

FilterChainProxy使用 SecurityFilterChain来确定应该为当前请求调用哪些 Spring 安全筛选器实例。

下图显示了 SecurityFilterChain 的角色。

securityfilterchain

Figure 4. SecurityFilterChain

SecurityFilterChain 中的 Security Filters 通常是 Bean,但是它们是用 FilterChainProxy 注册的,而不是用 DelegatingFilterProxy.注册的。FilterChainProxy 提供了直接在 Servlet 容器中注册或使用 DelegatingFilterProxy 的多项优势。首先,它为Spring Security的所有Servlet支持提供了一个起点。因此,如果您尝试调试Spring Security的Servlet支持,在FilterChainProxy中添加一个断点是一个很好的开始。其次,由于FilterChainProxy是Spring Security使用的核心,它可以执行一些被认为非可选的任务。例如,它清除SecurityContext以避免内存泄漏。它还应用Spring Security的HttpFirewall来保护应用程序免受某些类型的攻击。

此外,它在确定何时应该调用 SecurityFilterChain 方面提供了更大的灵活性。在 Servlet 容器中,过滤器实例仅基于 URL 进行调用。然而,通过使用RequestMatcher接口,FilterChainProxy可以根据HttpServletRequest中的任何内容来确定调用。

下图显示了多个 SecurityFilterChain 实例:

multi securityfilterchain

*Figure 5. Multiple SecurityFilterChain*

在Multiple SecurityFilterChain图中,FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只调用第一个匹配的 SecurityFilterChain。如果请求的URL是/api/messages/,它首先与/api/** 的SecurityFilterChain0模式匹配,因此只有SecurityFilterChain0被调用,即使它也与SecurityFilterChainn匹配。如果请求的URL是/messages/,它不与/api/** 的SecurityFilterChain0模式匹配,所以FilterChainProxy继续尝试每个SecurityFilterChain。假设没有其他SecurityFilterChain实例匹配,SecurityFilterChainn将被调用。

请注意,SecurityFilterChain0 只配置了三个安全 Filter 实例。然而,SecurityFilterChainn 配置了四个安全 Filter 实例。重要的是要注意每个 SecurityFilterChain 可以是唯一的,并且可以独立配置。实际上,如果应用程序希望 Spring Security 忽略某些请求,SecurityFilterChain 可能甚至没有一个安全 Filter 实例。

Security Filters

Security Filters 通过 SecurityFilterChainAPI 插入到 FilterChainProxy 中。这些过滤器可以用于许多不同的目的,比如身份验证、授权、漏洞保护等等。过滤器按照特定的顺序执行,以确保在正确的时间调用它们,例如,执行身份验证的 Filter 应该在执行授权的 Filter 之前调用。通常不需要知道 Spring 安全过滤器的顺序。但是,有时了解排序是有益的,如果您想了解它们,可以检查 FilterOrderRegistration代码。

为了举例说明上面的段落,让我们考虑以下安全配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(Customizer.withDefaults()).authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()).httpBasic(Customizer.withDefaults()).formLogin(Customizer.withDefaults());return http.build();}}

上述配置将导致下列Filter顺序:

FilterAdded by
CsrfFilterHttpSecurity#csrf
UsernamePasswordAuthenticationFilterHttpSecurity#formLogin
BasicAuthenticationFilterHttpSecurity#httpBasic
AuthorizationFilterHttpSecurity#authorizeHttpRequests
  1. (首先,调用 CsrfFilter 来防止 CSRF 攻击。)First, the CsrfFilter is invoked to protect against CSRF attacks.
  2. (其次,调用身份验证过滤器来对请求进行身份验证。)Second, the authentication filters are invoked to authenticate the request.
  3. (第三,调用 AuthorizationFilter 对请求进行授权。)Third, the AuthorizationFilter is invoked to authorize the request.

可能还有上面没有列出的其他 Filter 实例。如果希望查看为特定请求调用的筛选器列表,可以打印它们print them。

Printing the Security Filters

通常,查看为特定请求调用的 security Filter列表是有用的。例如,您希望确保添加的Filter位于 security Filter列表中。过滤器列表在应用程序启动时以 INFO 级别打印,因此您可以在控制台输出中看到如下内容,例如:

2023-06-14T08:55:22.321-03:00  INFO 76975 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]

这将很好地了解为each filter chain配置的安全过滤器。

但这还不是全部,您还可以将应用程序配置为为每个请求打印每个过滤器的调用。这有助于查看您添加的筛选器是否被针对特定请求调用,或者检查异常来自何处。为此,可以将应用程序配置为记录安全事件log the security events。

向过滤器链添加自定义过滤器(Adding a Custom Filter to the Filter Chain)

大多数情况下,默认的安全过滤器就足以保护您的应用程序。然而,有时您可能希望向安全过滤器链中添加一个自定义过滤器。

例如,假设您希望添加一个 Filter,它获取租户 ID 标头,并检查当前用户是否可以访问该租户。前面的描述已经给了我们一个关于在哪里添加过滤器的线索,因为我们还需要知道当前用户,所以我们需要在认证过滤器之后添加它。

首先,让我们创建 Filter:

import java.io.IOException;import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;import org.springframework.security.access.AccessDeniedException;public class TenantFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;String tenantId = request.getHeader("X-Tenant-Id"); (1)boolean hasAccess = isUserAllowed(tenantId); (2)if (hasAccess) {filterChain.doFilter(request, response); (3)return;}throw new AccessDeniedException("Access denied"); (4)}}

上面的示例代码执行以下操作:

  1. 从请求头中获取租户 ID。
  2. 检查当前用户是否有访问租户 ID 的权限。
  3. 如果用户具有访问权限,则调用链中的其余过滤器。
  4. 如果用户没有访问权限,则抛出 AccessDeniedException

您可以选择从OncePerRequestFilter扩展,而不是实现Filter接口。OncePerRequestFilter是一个基类,用于只每个请求调用一次的过滤器,并提供了一个带有HttpServletRequest和HttpServletResponse参数的doFilterInternal方法。

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// ....addFilterBefore(new TenantFilter(), AuthorizationFilter.class); // 1return http.build();
}
  1. 使用 HttpSecurity # addFilterBefore 在 AuthorizationFilter 之前添加 TenantFilter。

通过在 AuthorizationFilter 之前添加过滤器,我们可以确保在身份验证过滤器之后调用 TenantFilter。您还可以使用 HttpSecurity # addFilterAfter 在特定筛选器之后添加筛选器,或者使用 HttpSecurity # addFilterAt 在筛选器链中的特定筛选器位置添加筛选器。

就是这样,现在将在过滤器链中调用 TenantFilter,并检查当前用户是否有访问租户 ID 的权限。

在将过滤器声明为 Spring bean 时要小心,可以使用@Component 对其进行注释,也可以在配置中将其声明为 bean,因为 Spring Boot 会自动将其注册到嵌入式容器中。这可能导致过滤器被调用两次,一次由容器调用,一次由 Spring Security 以不同的顺序调用。

例如,如果你仍然想把你的过滤器声明为一个 Spring bean 来利用依赖注入,并避免重复调用,你可以通过声明一个 FilterregistrationBean bean 并把它的启用属性设置为 false 来告诉 Spring Boot 不要在容器中注册它:

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);registration.setEnabled(false);return registration;
}

Handling Security Exceptions

ExceptionTranslationFilter 允许将 AccessDeniedExceptionAuthenticationException转换为 HTTP 响应。

ExceptionTranslationFilter 作为安全过滤器之一被插入到 FilterChainProxy 中。

下面的图像显示了 ExceptionTranslationFilter 与其他组件的关系:

exceptiontranslationfilter

  1. 首先,ExceptionTransationFilter 调用 FilterChain.doFilter (request,response)来调用应用程序的其余部分。
  2. 如果用户未经身份验证,或者是 AuthenticationException,则启动身份验证。
    • SecurityContextHolder 被清除。
    • 保存 HttpServletRequest,以便在身份验证成功后可以使用它重播原始请求。
    • AuthenticationEntryPoint 用于从客户端请求凭据。例如,它可能重定向到登录页面或发送 WWW-Authenticate 标头。
  3. 否则,如果它是一个 AccessDeniedException,那么访问被拒绝。将调用 AccessDeniedHandler 来处理访问被拒绝。

如果应用程序不引发 AccessDeniedException 或 AuthenticationException,则 ExceptionTransationFilter 不执行任何操作。

ExceptionTranslationFilter pseudocode

try {filterChain.doFilter(request, response); // 1
} catch (AccessDeniedException | AuthenticationException ex) {if (!authenticated || ex instanceof AuthenticationException) {startAuthentication(); // 2} else {accessDenied(); // 3}
}
  1. 如 A Review of Filters 中所述,调用 FilterChain.doFilter (request,response)等同于调用应用程序的其余部分。这意味着,如果应用程序的另一部分(FilterSecurityInterceptor 或方法安全性)抛出 AuthenticationException 或 AccessDeniedException,将在这里捕获并处理它。
  2. 如果用户未经身份验证,或者是 AuthenticationException,则启动身份验证。
  3. 否则,访问被拒绝

Saving Requests Between Authentication

如 Handling Security Exceptions所示,当一个请求没有认证且请求的资源需要认证时,就需要保存对该认证资源的请求,以便在认证成功后重新请求。在 Spring Security 中,这是通过使用 RequestCache 实现来保存 HttpServletRequest 完成的。

RequestCache

HttpServletRequest 保存在 RequestCache中。当用户成功进行身份验证时,将使用 RequestCache 重播原始请求。

RequestCacheAwareFilter 在用户验证之后使用 RequestCache 获取保存的 HttpServletRequest,而 RequestCacheAwareFilter在检测到 AuthenticationException 之后,在将用户重定向到登录端点之前,使用 RequestCache 保存 HttpServletRequest。

默认情况下,使用 HttpSessionRequestCache。下面的代码演示如何自定义 RequestCache 实现,该实现用于检查 HttpSession 是否存在已保存的请求(如果存在名为 Continue 的参数)。

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {HttpSessionRequestCache requestCache = new HttpSessionRequestCache();requestCache.setMatchingRequestParameterName("continue");http// ....requestCache((cache) -> cache.requestCache(requestCache));return http.build();
}
阻止保存请求(Prevent the Request From Being Saved)

有许多原因可能导致您不想在会话中存储用户的未经身份验证的请求。您可能希望将该存储卸载到用户的浏览器上,或将其存储在数据库中。或者您可能想关闭这个功能,因为您总是想将用户重定向到主页,而不是他们在登录之前试图访问的页面。

为此,可以使用 NullRequestCache实现。

Prevent the Request From Being Saved

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {RequestCache nullRequestCache = new NullRequestCache();http// ....requestCache((cache) -> cache.requestCache(nullRequestCache));return http.build();
}

RequestCacheAwareFilter

RequestCacheAwareFilter 使用 RequestCache 重放原始请求。

Logging

SpringSecurity 在 DEBUG 和 TRACE 级别提供所有安全相关事件的全面日志记录。这在调试应用程序时非常有用,因为对于安全措施,Spring Security 不会向响应主体添加请求被拒绝的任何细节。如果遇到401或403错误,很可能会找到一条日志消息,帮助您了解正在发生的情况。

让我们考虑一个示例,其中用户试图向启用了 CSRF 保护但没有启用 CSRF 令牌的资源发出 POST 请求。如果没有日志,用户将看到一个403错误,没有解释为什么请求被拒绝。但是,如果为 Spring Security 启用日志记录,您将看到如下日志消息:

2023-06-14T09:44:25.797-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /hello
2023-06-14T09:44:25.797-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/15)
2023-06-14T09:44:25.798-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/15)
2023-06-14T09:44:25.800-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/15)
2023-06-14T09:44:25.801-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/15)
2023-06-14T09:44:25.802-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (5/15)
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost:8080/hello
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.s.w.access.AccessDeniedHandlerImpl   : Responding with 403 status code
2023-06-14T09:44:25.814-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]

很明显,CSRF 令牌丢失了,这就是请求被拒绝的原因。

若要将应用程序配置为记录所有安全事件,可以向应用程序添加以下内容:

-- application.properties in Spring Boot
logging.level.org.springframework.security=TRACE
-- logback.xml
<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><!-- ... --></appender><!-- ... --><logger name="org.springframework.security" level="trace" additivity="false"><appender-ref ref="Console" /></logger>
</configuration>

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

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

相关文章

实现WebSocket聊天室功能

实现WebSocket聊天室功能 什么是WebSocket&#xff1f;WebSocket的工作原理服务器端实现客户端实现 在现代Web开发中&#xff0c;实时通信已经变得越来越重要。传统的HTTP协议由于其无状态和单向通信的特点&#xff0c;无法很好地满足实时通信的需求。而WebSocket协议则应运而生…

【讨论C++继承】

讨论C继承 继承定义继承方式和访问限定符 基类和派生类的赋值转换继承中的作用域派生类的默认成员函数继承和友元继承和静态成员菱形继承虚拟继承 继承是面向对象程序设计中&#xff0c;使代码可以复用的重要手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展。 继…

【ONLYOFFICE】| 桌面编辑器从0-1使用初体验

目录 一. &#x1f981; 写在前面二. &#x1f981; 在线使用感受2.1 创建 ONLYOFFICE 账号2.2 编辑pdf文档2.3 pdf直接创建表格 三. &#x1f981; 写在最后 一. &#x1f981; 写在前面 所谓桌面编辑器就是一种用于编辑文本、图像、视频等多种自媒体的软件工具&#xff0c;具…

算法训练营day24--93.复原IP地址 +78.子集 +90.子集II

一、93.复原IP地址 题目链接&#xff1a;https://leetcode.cn/problems/restore-ip-addresses/ 文章讲解&#xff1a;https://programmercarl.com/0093.%E5%A4%8D%E5%8E%9FIP%E5%9C%B0%E5%9D%80.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1fA4y1o715 1.1 初…

d3dcompiler_47.dll缺失怎么修复?d3dcompiler_47.dll修复使用说明

d3dcompiler_47.dll是一个重要的系统文件&#xff0c;属于MicrosoftWindows操作系统中Direct3D的一部分&#xff0c;它主要负责处理在Windows上运行的应用程序和游戏中的3D图形编程。这个DLL文件是“DirectX”的一项组成部分&#xff0c;DirectX是一套核心技术&#xff0c;用于…

13-Django项目--文件上传

目录 前端展示 路由: 数据库字段: 函数视图: 前端展示 {% extends "index/index.html" %}{% block content %}<div class"container"><input type"button" id"btnAdd" value"上传荣耀" class"btn btn-succ…

Oracle 集群的守护进程

ohas&#xff1a;主要用于守护cluster ware进程&#xff0c;在单节点建立集群的时候&#xff0c;没有crs&#xff0c;只有ohas、cluster ware GPnP&#xff1a;管理clusterware的配置信息&#xff0c;放在本地磁盘上 crs&#xff1a;管理clusterware中的资源&#xff0c;数据库…

成功解决ES高亮内容引起的字段显示不一致问题

在处理搜索引擎&#xff08;如Elasticsearch&#xff09;结果时&#xff0c;常见需求之一是对用户搜索的关键词进行高亮显示&#xff0c;这有助于用户快速识别搜索结果为何与其查询相关。但在实际应用中&#xff0c;如果处理不当&#xff0c;直接使用高亮片段可能会导致原始数据…

A股站不稳3000点让人稀罕不已啊

今天的A股&#xff0c;让人稀罕不已&#xff0c;你知道是为什么吗&#xff1f;盘面出现2个重要信号&#xff0c;一起来看看&#xff1a; 1、今天两市冲了下3000点&#xff0c;第一个主题炒作的热点终于出现了&#xff0c;税改方向的行情发酵&#xff0c;并带动着其他改革相关方…

echarts的折线图实现部分虚线部分实线

场景&#xff1a; 折线图一般都是实线为准&#xff0c;但是由于最后一个数据是预测。所以想要实现最后一段为虚线。 效果图&#xff1a; 具体实现&#xff1a; series:[{name: "销售总金额",type: "line",smooth: true,barWidth: 10,stack: Total,itemSty…

Ubuntu下反弹shell的思考

目录 Ubuntu的命令执行环境 bash (Bourne Again SHell): sh (Bourne SHell): dash (Debian Almquist SHell): 它们之间的关系&#xff1a; 可能遇到的问题 一、脚本权限问题 二、命令执行环境(shell解释器)问题 如何解决&#xff1f; 1.修改/bin/sh软连接的指向为bas…

ESP32CAM物联网教学01

ESP32CAM物联网教学01 拍照 视频 这么小的一个开发板都带上摄像头了&#xff0c;能拍照&#xff1f;能视频吗&#xff1f;现在就跟着我做起来。 初识ESP32CAM 我们到淘宝搜索“ESP32Cam”&#xff0c;就能买到这样一块开发板。 ESP32Cam是双核处理器&#xff0c;提供WIFI和…

Cyuyanzhong的内存函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、memcpy函数的使用与模拟实现二、memmove函数的使用和模拟实现三、memset函数与memcmp函数的使用&#xff08;一&#xff09;、memset函数&#xff08;内存块…

Linux shell编程学习笔记59: ps 获取系统进程信息,类似于Windows系统中的tasklist 命令

0 前言 系统进程信息是电脑网络信息安全检查中的一块重要内容&#xff0c;对于使用Linux和基于Linux作为操作系统的电脑来说&#xff0c;可以使用ps命令。 1 ps命令 的功能、格式和选项说明 1.1 ps命令 的功能 Linux 中的ps&#xff08;意为&#xff1a;process status&…

Chrome导出cookie的实战教程

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

leetcode刷题:vector刷题

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;leetcode刷题 1.只出现一次的数字 这道题很简单&#xff0c;我们只需要遍历一次数组即可通过异或运算实现。(一个数与自身异或结果为0&#xff0c;任何数与0异或还是它本身) class Solut…

WPF UI 3D 基本概念 点线三角面 相机对象 材质对象与贴图 3D地球 光源 变形处理 动作交互 辅助交互插件 系列三

WPF UI交互专题 平面图形 Path Drawing 绘图 渐变 Brush 矩阵 Transform 变形 阴影效果 模糊效果 自定义灰度去色效果 系列二-CSDN博客 1软件中的3D基本概念 WPF 中 3D 功能的设计初衷并非提供功能齐全的游戏开发平台。 WPF 中的 3D 图形内容封装在 Viewport3D 元素中&#x…

WPF自定义模板--Button

属性&#xff1a; TemplateBinding&#xff1a;用于在ControlTemplate中绑定到控件的属性&#xff0c;例如Background、BorderBrush等。TargetType&#xff1a;指定该模板应用于哪种控件类型。在这个例子中&#xff0c;是Button。 标准的控件模板代码&#xff1a; <Style…

借助 Aspose.Words,在 C# 中将 Word 转换为 Excel

有时我们会遇到需要将 Word 文档&#xff08;DOC 或 DOCX&#xff09;转换为 Excel 文档的任务。例如&#xff0c;这对于数据分析和报告很有用&#xff0c;或者如果您收到了任何文本数据并想将其转换为表格格式&#xff08;XLS 或 XLSX&#xff09;以便进一步工作。在本文中&am…

IAR工程目录移动报错(改变文件目录结构)

刚开始用IAR&#xff0c;记录一下。 工作中使用华大单片机&#xff0c;例程的文件目录结构太复杂了想精简一点。 1.如果原本的C文件相对工程文件&#xff08;.eww文件&#xff09;路径变化了&#xff0c;需要先打开工程&#xff0c;再将所有的.c文件右键Add添加进工程&#xf…