spring-security原理與應用系列:requestMatchers和authorizeRequests

目錄

簡單示例

WebSecurityConfig

requestMatchers

???????requestMatchers

???????antMatchers

???????chainRequestMatchers

???????setMatchers

???????requestMatcher

???????WebSecurity

???????performBuild

securityFilterChainBuilder對象

???????init

???????addSecurityFilterChainBuilder

???????securityFilterChainBuilder.build

???????build

???????doBuild

???????httpSecurity.performBuild

???????FilterChainProxy

???????doFilterInternal

???????getFilters

???????matches

???????OrRequestMatcher.matches

authorizeRequests

???????authorizeRequests

???????ExpressionUrlAuthorizationConfigurer

???????AbstractInterceptUrlConfigurer

???????FilterSecurityInterceptor

???????invoke

???????beforeInvocation

總結


? ? ? ?本篇文章我們來研究spring-security框架里http.requestMatchers和http.authorizeRequests的作用。只要使用了spring-security框架,那么,開發人員就免不了要對這兩個配置項的配置了。

簡單示例

? ? ? ?首先,新建一個config包用于存放spring-security通用配置;然后,新建一個WebSecurityConfig類,使其繼承WebSecurityConfigurerAdapter。

? ? ? ?然后,給WebSecutiryConfig類中加上@EnableWebSecurity 注解后,這樣便會自動被 Spring發現并注冊。

???????WebSecurityConfig

@EnableWebSecurity

class WebSecurityConfig extends WebSecurityConfigurerAdapter {

??@Override

??protected void configure(HttpSecurity http) throws Exception {

????http.requestMatchers()

.antMatchers("web/oauth/authorize", "/oauth/**", "/admin/**")

??????.and().authorizeRequests()

??????.antMatchers("/admin/**").hasRole("ADMIN")

.anyRequest().authenticated()

??????... ...

}

? ? ? ?在這里,首先調用httpSecurity的requestMatchers()方法,緊接著調用antMatchers()方法,并輸入web/oauth/authorize,?/oauth/**, /admin/**三個通配符路徑,表示當前配置只允許滿足這三個路徑的請求通過;

? ? ? ?然后,調用authorizeRequests()方法,再調用antMatchers()方法,并輸入/admin/**通配符路徑,緊接著調用hasRole()方法,并輸入ADMIN參數,表示對于滿足/admin/**路徑的請求,需要登錄用戶有ADMIN的角色;

? ? ? ?最后,調用anyRequest()方法,緊接著調用authenticated()方法,表示/admin/**之外的所有請求,需要登錄用戶認證后才能訪問。

requestMatchers

? ? ? ? 在簡單示例里,點擊requestMatchers()方法,如下所示:

???????requestMatchers

public RequestMatcherConfigurer requestMatchers() {

return requestMatcherConfigurer;

}

? ? ? ?在這里,返回配置器RequestMatcherConfigurer對象。?

? ? ? ?在簡單示例里,點擊requestMatchers()后面的antMatchers方法,如下所示:

???????antMatchers

public abstract class AbstractRequestMatcherRegistry<C> {

... ...

public C antMatchers(String... antPatterns) {

??return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));

}

? ? ? ? 點擊chainRequestMatchers()方法,如下所示:

???????chainRequestMatchers

@Override

protected RequestMatcherConfigurer chainRequestMatchers(List<RequestMatcher> requestMatchers) {

setMatchers(requestMatchers);

return this;

}

? ? ? ? 在這里,點擊setMatchers()方法,如下所示:

???????setMatchers

private void setMatchers(List<? extends RequestMatcher> requestMatchers) {

this.matchers.addAll(requestMatchers);

requestMatcher(new OrRequestMatcher(this.matchers));

}

? ? ? ?在這里,將配置的請求地址轉換為OrRequestMatcher對象。然后調用requestMatcher()方法進行后續的處理。

? ? ? ?點擊requestMatcher()方法,如下所示:

???????requestMatcher

public final class HttpSecurity extends?AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>?implements SecurityBuilder<DefaultSecurityFilterChain>,

HttpSecurityBuilder<HttpSecurity> {

????private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;

... ...

public HttpSecurity requestMatcher(RequestMatcher requestMatcher) {

??this.requestMatcher = requestMatcher;

??return this;

}

? ? ? ?在這里,將配置的請求地址OrRequestMatcher對象保存到HttpSecurity對象的requestMatcher 屬性對象里。

? ? ? ?從前面的文章里,我們知道spring-security框架最終會創建一個FilterChainProxy類的過濾器對象,接下來我們探究一下該FilterChainProxy過濾器如何引用HttpSecurity對象的requestMatcher 屬性對象。

? ? ? ?從前面的文章里,我們也了解到過濾器FilterChainProxy對象,是在WebSecurity類里創建的。

???????WebSecurity

? ? ? ?點擊performBuild()方法,如下所示:

???????performBuild

protected Filter performBuild() throws Exception {

????int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();

????List<SecurityFilterChain> securityFilterChains = new ArrayList(chainSize);

????... ...

????var3 = this.securityFilterChainBuilders.iterator();

????while(var3.hasNext()) {

????????SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next();

????????securityFilterChains.add(securityFilterChainBuilder.build());

????}

????FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);

????if (this.httpFirewall != null) {

????????filterChainProxy.setFirewall(this.httpFirewall);

????}

????filterChainProxy.afterPropertiesSet();

????Filter result = filterChainProxy;

????this.postBuildAction.run();

????return (Filter)result;

}

? ? ? ?在這里,FilterChainProxy實例的構造參數是securityFilterChainBuilder.build()。

? ? ? ?接下來我們探究兩個問題:

  1. securityFilterChainBuilder對象來自哪里?
  2. securityFilterChainBuilder.build()方法跟HttpSecurity對象的requestMatcher 屬性對象有什么關系?
  3. 過濾器FilterChainProxy對象如何使用requestMatcher 屬性?

securityFilterChainBuilder對象

? ? ? ?回顧前面的文章,我們知道在WebSecurityConfigurerAdapter 類里會創建HttpSecurity對象,找到WebSecurityConfigurerAdapter 類的init()方法,如下所示:

???????init

public abstract class WebSecurityConfigurerAdapter implements?WebSecurityConfigurer<WebSecurity> {

????... ...

public void init(final WebSecurity web) throws Exception {

final HttpSecurity http = getHttp();

web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {

FilterSecurityInterceptor securityInterceptor = http

.getSharedObject(FilterSecurityInterceptor.class);

web.securityInterceptor(securityInterceptor);

});

}

? ? ? ? 在這里,通過getHttp()方法創建了HttpSecurity對象,然后調用web.addSecurityFilterChainBuilder(http)方法,如下所示:

???????addSecurityFilterChainBuilder

public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {

this.securityFilterChainBuilders.add(securityFilterChainBuilder);

return this;

}

? ? ? ?在這里,將HttpSecurity對象加入到WebSecurity 對象的屬性securityFilterChainBuilders里。

? ? ? ?接下來, 我們探究securityFilterChainBuilder.build()方法跟HttpSecurity對象的requestMatcher 屬性對象有什么關系?

???????securityFilterChainBuilder.build

? ? ? ?在WebSecurity類的performBuild()方法,點擊securityFilterChainBuilder.build(),如下所示:

???????build

public final O build() throws Exception {

if (this.building.compareAndSet(false, true)) {

this.object = doBuild();

return this.object;

}

throw new AlreadyBuiltException("This object has already been built");

}

? ? ? ? 點擊doBuild()方法,如下所示:

???????doBuild

@Override

protected final O doBuild() throws Exception {

synchronized (configurers) {

buildState = BuildState.INITIALIZING;

beforeInit();

init();

buildState = BuildState.CONFIGURING;

beforeConfigure();

configure();

buildState = BuildState.BUILDING;

O result = performBuild();

buildState = BuildState.BUILT;

return result;

}

}

? ? ? ? 點擊performBuild()方法,如下所示:

???????httpSecurity.performBuild

@Override

protected DefaultSecurityFilterChain performBuild() {

filters.sort(comparator);

return new DefaultSecurityFilterChain(requestMatcher, filters);

}

? ? ? ?在這里,會創建DefaultSecurityFilterChain對象,該對象的第一個構造參數就是httpSecurity對象的屬性requestMatcher對象了。

? ? ? ?接下來,我們探究過濾器FilterChainProxy對象如何使用requestMatcher屬性?

???????FilterChainProxy

? ? ? ?點擊FilterChainProxy 類的doFilter()方法,如下所示:

public class FilterChainProxy extends GenericFilterBean {

????... ...

@Override

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;

if (clearContext) {

try {

request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

doFilterInternal(request, response, chain);

}?finally {

SecurityContextHolder.clearContext();

request.removeAttribute(FILTER_APPLIED);

}

}?else {

doFilterInternal(request, response, chain);

}

}

? ? ? ? 點擊doFilterInternal()方法,如下所示:

???????doFilterInternal

private void doFilterInternal(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

FirewalledRequest fwRequest = firewall

.getFirewalledRequest((HttpServletRequest) request);

HttpServletResponse fwResponse = firewall

.getFirewalledResponse((HttpServletResponse) response);

List<Filter> filters = getFilters(fwRequest);

if (filters == null || filters.size() == 0) {

fwRequest.reset();

chain.doFilter(fwRequest, fwResponse);

return;

}

VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);

vfc.doFilter(fwRequest, fwResponse);

}

? ? ? ? 點擊getFilters()方法,如下所示:

???????getFilters

private List<Filter> getFilters(HttpServletRequest request) {

for (SecurityFilterChain chain : filterChains) {

if (chain.matches(request)) {

return chain.getFilters();

}

}

return null;

}

? ? ? ? 點擊chain.matches()方法,如下所示:

???????matches

public boolean matches(HttpServletRequest request) {

return requestMatcher.matches(request);

}

? ? ? ? 點擊requestMatcher.matches()方法,如下所示:

???????OrRequestMatcher.matches

public boolean matches(HttpServletRequest request) {

for (RequestMatcher matcher : requestMatchers) {

if (logger.isDebugEnabled()) {

logger.debug("Trying to match using " + matcher);

}

if (matcher.matches(request)) {

logger.debug("matched");

return true;

}

}

logger.debug("No matches found");

return false;

}

? ? ? ? 至此,我們總算把spring-security框架的http.requestMatchers研究透徹了。

? ? ? ? 接下來,我們探究spring-security框架的http.authorizeRequests的作用。

authorizeRequests

? ? ? ?在簡單示例里,點擊authorizeRequests()方法,如下所示:

???????authorizeRequests

public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()?throws Exception {

ApplicationContext context = getContext();

return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry();

}

? ? ? ? 在這里,使用的是配置器ExpressionUrlAuthorizationConfigurer對象。點擊ExpressionUrlAuthorizationConfigurer類,如下所示:

???????ExpressionUrlAuthorizationConfigurer

public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>

extends?AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {

... ...

private final ExpressionInterceptUrlRegistry REGISTRY;

????... ...

? ? ? ? 在這里,找不到configure(H http)方法,點擊繼承類AbstractInterceptUrlConfigurer,如下所示:

???????AbstractInterceptUrlConfigurer

abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>>

extends AbstractHttpConfigurer<C, H> {

private Boolean filterSecurityInterceptorOncePerRequest;

private AccessDecisionManager accessDecisionManager;

@Override

public void configure(H http) throws Exception {

FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);

if (metadataSource == null) {

return;

}

FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(

http, metadataSource, http.getSharedObject(AuthenticationManager.class));

if (filterSecurityInterceptorOncePerRequest != null) {

securityInterceptor

.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);

}

securityInterceptor = postProcess(securityInterceptor);

http.addFilter(securityInterceptor);

http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);

}

? ? ? ? 在這里,創建了過濾器FilterSecurityInterceptor 對象。

? ? ? ?點擊FilterSecurityInterceptor 類,如下所示:

???????FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements?Filter {

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

FilterInvocation fi = new FilterInvocation(request, response, chain);

invoke(fi);

}

? ? ? ? 點擊invoke()方法,如下所示:

???????invoke

public void invoke(FilterInvocation fi) throws IOException, ServletException {

if ((fi.getRequest() != null)?&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)

&& observeOncePerRequest) {

??... ...

}?else {

if (fi.getRequest() != null && observeOncePerRequest) {

fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);

}

InterceptorStatusToken token = super.beforeInvocation(fi);

try {

fi.getChain().doFilter(fi.getRequest(), fi.getResponse());

}?finally {

super.finallyInvocation(token);

}

super.afterInvocation(token, null);

}

}

? ? ? ? 點擊beforeInvocation()方法,如下所示:

???????beforeInvocation

protected InterceptorStatusToken beforeInvocation(Object object) {

??boolean debug = this.logger.isDebugEnabled();

??if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {

??????... ...

??} else {

??????Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

??????if (attributes != null && !attributes.isEmpty()) {

??????????... ...

??????????Authentication authenticated = this.authenticateIfRequired();

??????????try {

??????????????this.accessDecisionManager.decide(authenticated, object, attributes);

??????????} catch (AccessDeniedException var7) {

??????????????... ...

??????????}

??????????... ...

??}

}

? ? ? ?在這里,通過this.obtainSecurityMetadataSource().getAttributes(object)獲取配置的權限信息,通過this.accessDecisionManager.decide進行授權決策。

總結

? ? ? ?http.requestMatchers()是一個請求過濾器,決定是否要應用安全配置。對于不需要進行安全控制的URL,可以不要配置在requestMatchers里,這樣可以避免走到下一個環節。

? ? ? ?http.authorizeRequests()是一個授權配置器,決定如何應用安全配置。用于配置 URL 的訪問權限控制規則。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/86897.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/86897.shtml
英文地址,請注明出處:http://en.pswp.cn/web/86897.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Bessel位勢方程求解步驟

問題 考慮偏微分方程&#xff08;PDE&#xff09;&#xff1a; ? Δ u u f , x ∈ R n , -\Delta u u f, \quad x \in \mathbb{R}^n, ?Δuuf,x∈Rn, 其中 f ∈ L 2 ( R n ) f \in L^2(\mathbb{R}^n) f∈L2(Rn)。這是一個線性橢圓型方程&#xff0c;稱為 Bessel 位勢方…

if __name__ == ‘__main__‘:

基本概念 if __name__ __main__: 是一個條件判斷語句&#xff0c;用于確定當前模塊是作為主程序運行&#xff0c;還是被其他模塊導入。 __name__ 變量 __name__ 是Python的一個內置變量&#xff0c;表示當前模塊的名稱當一個模塊被直接運行時&#xff0c;__name__ 的值會被…

淺談Apache HttpClient的相關配置和使用

Apache HttpClient是由Apache軟件基金會維護的一款開源HTTP客戶端庫&#xff0c;對比最基礎的 HttpURLConnection 而言,它的優勢時支持連接池管理&#xff0c;攔截器&#xff08;Interceptor&#xff09;機制&#xff0c;同步/異步請求支持等能力。 在使用這個組件時&#xff…

【Teensy】在ArduinoIDE中配置Teensy4.1

1.文件——首選項 在其他開發板管理器地址這里添加&#xff1a; https://www.pjrc.com/teensy/package_teensy_index.json 點擊確定&#xff01; 2.安裝Teensy(for Arduino IDE…) 按照圖中1&#xff0c;2&#xff0c;3操作&#xff01;可以選擇上一個版本&#xff08;不使用最…

企業自建云概念解讀|私有云、專有云、混合云、分布式云、企業云

隨著云計算技術逐漸成熟&#xff0c;越來越多的企業開始在本地數據中心自行搭建云平臺&#xff0c;滿足數據合規、業務性能與連續性、節約成本等多方面的需求。不過&#xff0c;面對多種多樣的自建云產品&#xff0c;不少用戶會有類似的疑問&#xff1a;自建云等于私有云嗎&…

反彈 Shell 升級為全交互終端的兩種高效方法

目錄 ?? 升級反彈 Shell 為全交互終端:兩種高效方法 ??? 方法 1:利用 Python pty.spawn 創建偽終端 ?? 操作步驟

Hyper-YOLO: When Visual Object Detection Meets Hypergraph Computation論文精讀(逐段解析)

Hyper-YOLO: When Visual Object Detection Meets Hypergraph Computation論文精讀&#xff08;逐段解析&#xff09; 論文地址&#xff1a;https://arxiv.org/abs/2408.04804 CVPR 2024 Yifan Feng, Jiangang Huang, Shaoyi Du, Senior Member, IEEE, Shihui Ying, Jun-Hai Y…

Windows 下配置多個 GitHub 賬號的 SSH Key

Windows 下配置多個 GitHub 賬號的 SSH Key 假設你有以下兩個 SSH key 文件&#xff1a; 第一個賬號&#xff1a;id_rsa&#xff08;默認&#xff09;第二個賬號&#xff1a;id_rsa_github ? 步驟&#xff1a;在 Windows 上配置多個 GitHub 賬號 SSH Key 1?? 打開 SSH 配…

技術選型:時序數據庫(三)

IoTDB vs InfluxDB vs TDengine 時序數據庫橫評對比。 從 架構設計、性能、功能、生態、適用場景 等維度&#xff0c;對三款時序數據庫進行深度對比&#xff0c;助您精準選型。 一、核心架構對比 數據庫 存儲模型 數據模型 擴展性 Apache IoTDB 分層存儲&#xff08;TsFi…

電子電路原理第十九章(非線性運算放大器電路的應用)

單片集成運算放大器價格便宜、用途廣泛且性能可靠。它們不僅可以用于線性電路,如電壓放大器、電流源和有源濾波器,而且可以用于非線性電路,如比較器、波形生成器和有源二極管電路。非線性運放電路的輸出通常與輸入信號的波形不同,這是因為運放在輸入周期的某個時間段內達到…

FPGA實現CameraLink視頻解碼轉SDI輸出,基于LVDS+GTX架構,提供2套工程源碼和技術支持

目錄 1、前言工程概述免責聲明 2、CameraLink協議理論學習3、相關方案推薦我已有的所有工程源碼總目錄----方便你快速找到自己喜歡的項目FPGA實現CameraLink視頻編解碼方案本博已有的 SDI 編解碼方案 4、工程詳細設計方案工程設計原理框圖輸入CameraLink相機LVDS視頻解碼模塊LV…

戶外人像要怎么拍 ?

前言&#xff1a; ” 接上篇&#xff0c;培養你的眼力 - 攝影構圖&#xff0c;本文是整理自《美國紐約攝影學院 攝影教材》&#xff0c;第三單元 - 第9課 - 自然光&#xff0c;課后習題及解答。“ 1. 正面光產生無深淺反差的平面感覺。 理解這題&#xff0c;首先得明白什么是…

華為云Flexus+DeepSeek征文 | 華為云 ModelArts Studio 賦能高情商AI聊天助手:用技術構建有溫度的智能對話體驗

前言 華為云 ModelArts Studio 是基于 ModelArts 構建的一站式大模型即服務平臺&#xff08;MaaS&#xff09;&#xff0c;可通過與開源 Agent 框架 Dify.AI 結合來開發對接 AI 聊天助手。 在打造 “高情商” 特性的過程中&#xff0c;華為云ModelArts Studio 的自定義提示詞…

Spring Boot屬性配置方式

一、Spring Boot屬性配置方式。 在編寫完成后端程序之前&#xff0c;可以通過yml配置文件鍵值對的方式修改配置環境&#xff0c;一旦打包完成&#xff0c;再次修改yml配置文件較為麻煩&#xff0c;此時&#xff0c;可以使用以下配置方式&#xff1a; 1.命令行參數方式 …

Webpack原理剖析與實現

1. 整體架構設計 Webpack 5 的整體架構設計包括以下幾個核心模塊: Compiler:負責整個編譯過程,從讀取配置、解析模塊、生成依賴圖,到輸出最終的打包結果,主要文件是 lib/Compiler.js 。 Compilation:代表一次編譯過程,包括所有模塊、依賴關系和編譯結果,主要文件是 li…

【Python使用】嘿馬python運維開發全體系教程第2篇:日志管理,Linux概述【附代碼文檔】

教程總體簡介&#xff1a;網絡設定 學習目標 1、手動設定 2、DHCP自動獲取 系統基本優化 一、永久關閉SELinux 1. 永久關閉 二、關閉防火墻 2. 臨時啟動關閉防火墻 三、設定運行級別為3&#xff08;命令行模式&#xff09; 四、修改ssh端口號 ssh服務 一、ssh介紹 二、客戶端遠…

Hibernate報No Dialect mapping for JDBC type 1111(APP)

文章目錄 環境癥狀問題原因解決方案報錯編碼 環境 系統平臺&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;4.5 癥狀 客戶應用中報錯No Dialect mapping for JDBC type 1111。 問題原因 客戶使用Hibernate&#xff0c;實體類的中設置的數據類型與數…

【數據分析】環境數據降維與聚類分析教程:從PCA到可視化

禁止商業或二改轉載,僅供自學使用,侵權必究,如需截取部分內容請后臺聯系作者! 文章目錄 介紹教程內容數據預處理主成分分析(PCA)聚類分析可視化分析結果提取簇特征教程目的加載R包數據下載導入數據數據預處理主成分分析(PCA)計算相關矩陣繪制相關矩陣熱圖執行PCA可視化…

mac 安裝python,切換python版本

一、安裝多版本的PYTHON 在macOS上&#xff0c;你可以通過Homebrew包管理器安裝多個版本的Python 安裝Homebrew 首先&#xff0c;如果你的macOS上沒有安裝Homebrew&#xff0c;需要先進行安裝。打開終端&#xff08;Terminal&#xff09;并輸入以下命令&#xff1a; /bin/b…

AMD圖形和計算架構:RNDA

AMD圖形和計算架構&#xff1a;RNDA AMD RDNA 是 AMD 為顯卡&#xff08;GPU&#xff09;設計的 圖形和計算架構&#xff0c;專為高性能游戲、實時渲染和并行計算優化。目前已經迭代到 RDNA 3&#xff08;如 RX 7000 系列顯卡&#xff09;&#xff0c;與 NVIDIA 的 RTX 系列和…