当前位置: 首页 > news >正文

使用 Spring Boot 和 Keycloak 的 OAuth2 快速指南

1. 概述

本教程是关于使用 Spring Boot 和 Keycloak 通过 OAuth2 配置后端的。

我们将使用 Keycloak 作为 OpenID 提供程序。我们可以将其视为负责身份验证和用户数据(角色、配置文件、联系信息等)的用户服务。它是最完整的 OpenID Connect (OIDC) 实现之一,具有以下功能:

  • 单点登录 (SSO) 和单点注销(Back-Channel Logout)

  • 身份代理、社交登录和用户联合

  • 用于服务器管理和用户帐户管理的用户界面

  • 可通过编程方式控制一切的管理员 REST API

在 Spring Security 中查看 OAuth2 的配置选项后,我们将配置两个不同的 Spring Boot 应用程序:

  • 使用 oauth2Login 的有状态客户端

  • 无状态的 oauth2 ResourceServer

2. 使用 Docker 的 Keycloak 快速入门

在本节中,我们将启动一个具有预配置 Realm 的 Keycloak 服务器。我们将在第 6 节中了解如何创建这样的 Realm。

2.1. Docker Compose 文件

在开发人员的桌面上对授权服务器进行沙盒化的最简单方法是拉取 Keycloak Docker 镜像。要配置它,我们将使用 Docker compose 文件:


services:postgres:image: postgres:16.2ports:- 5432:5432volumes:- ./postgres_data:/var/lib/postgresql/data- /etc/localtime:/etc/localtime:roenvironment:POSTGRES_DB: ${POSTGRES_DB}POSTGRES_USER: ${POSTGRES_USER}POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}networks:- keycloak_networkkeycloak:image: quay.io/keycloak/keycloak:26.1.2command: startenvironment:KC_HOSTNAME: 192.168.1.212KC_HOSTNAME_PORT: 8080KC_HOSTNAME_STRICT_BACKCHANNEL: falseKC_HTTP_ENABLED: trueKC_HOSTNAME_STRICT_HTTPS: falseKC_HEALTH_ENABLED: trueKEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}KC_DB: postgresKC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB}KC_DB_USERNAME: ${POSTGRES_USER}KC_DB_PASSWORD: ${POSTGRES_PASSWORD}TZ: Asia/Shanghaivolumes:- ./keycloak/:/opt/keycloak/data/import/- /etc/localtime:/etc/localtime:roports:- 8080:8080restart: alwaysdepends_on:- postgresnetworks:- keycloak_networkvolumes:postgres_data:driver: localnetworks:keycloak_network:driver: bridge
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=password

2.2. 使用 Companion Project Realm

配套项目包含一个 JSON 文件,其中包含我们需要的一些 Keycloak 对象的定义:

  • realm:cemx

  • client:cemc-client,需要携带 secret 凭证,开启Client authentication

  • 一个映射器,用于将realm role 添加到颁发给 cemx-keycloak-client 客户端的 Access Token和 ID Token(默认情况下,只有访问令牌包含领域角色)

  • 在领域级别定义的 ADMIN 角色(Keycloak 还支持在客户端级别定义角色)

  • 两个用户:davy(具有 ADMIN 角色)和 test(没有 ADMIN 角色)。

compose 文件中的 —import-realm 参数指示 Keycloak 从其 data/import/ 文件夹中的 JSON 文件加载对象。

给定 compose 文件中定义的卷,我们应该keycloak/cemx-keycloak-realm.json 文件从配套项目复制到相对于 compose 文件的 ./keycloak/ 文件夹

2.3. 启动 Docker 容器

为了简化初始配置,上面的 Docker compose 文件使用了一个 KEYCLOAK_ADMIN_PASSWORD 环境变量,我们应该在启动容器之前设置该变量:

export KEYCLOAK_ADMIN_PASSWORD=admin
docker compose up -d

运行这些命令后,Keycloak 将启动。一旦我们看到一行包含 Keycloak 26.1.2 […] started,我们就知道启动完成。

我们现在可以通过浏览到 http://l192.168.1.212:8080,使用 admin/admin 作为凭据来使用 Keycloak 管理控制台。

3. Spring Security for OAuth2

3.1. OAuth2 参与者

让我们首先了解 OAuth2 的三个主要参与者。

Authorization server:负责资源所有者的身份验证并向客户端颁发令牌** – 在本教程中,我们将使用 Keycloak 来实现此目的

客户端驱动流程:从授权服务器获取令牌、存储令牌,并使用有效令牌授权对资源服务器的请求。当资源所有者是用户时,客户端使用授权代码流程(或者,当用户设备的输入功能有限时,使用设备流程)针对授权服务器登录,并获取令牌以代表他们向资源服务器发送请求。

在本教程中,我们的客户端是具有 oauth2Login 和 Postman 的 Spring 应用程序。

Resource server:提供对 REST 资源的安全访问**。它们检查令牌的有效性(issuer、expiration、audience等)并控制客户端访问(client scopes, resource owner roles, 资源所有者与访问的资源之间的关系等)。我们将 REST API 配置为资源服务器。

从上面可以看出,选择从授权服务器获取令牌的流是 OAuth2 客户端的责任。因此,用户登录和注销从来都不是资源服务器的问题

3.2. oauth2登录

Spring Security oauth2Login 配置授权代码和刷新令牌流。它还配置了一个授权的客户端存储库来存储令牌(默认在会话中),并配置了一个授权的客户端管理器供我们访问它 - 在需要时刷新令牌,然后再返回令牌。

使用 oauth2Login 对 Spring 客户端的请求是通过会话 cookie 授权的。这就是为什么应该始终在具有oauth2Login的Security(Web)FilterChainbean 中启用对 CSRF 攻击的保护

成功授权请求后,安全上下文中的 Authentication 类型为 OAuth2AuthenticationToken。

如果使用 OpenID 自动配置提供程序,则 OAuth2AuthenticationToken 主体是从 ID 令牌构建的 OidcUser*,否则,需要对 userinfo 端点进行额外调用才能将 OAuth2User 设置为主体。

Spring Security 权限是使用GrantedAuthoritiesMapper或自定义OAuth2UserService进行映射的。

3.3. oauth2ResouceServer 服务器

Spring Security oauth2ResouceServer配置 Bearer token 安全性。它提供了introspection(不透明令牌)和 JWT decoding。

对于资源服务器,用户状态由令牌声明保存,并且可以禁用会话。这带来了两大好处:

  • 可以禁用 CSRF 保护 – CSRF 攻击涉及会话,此处未使用

  • 资源服务器易于扩展 – 无论请求路由到哪个实例,客户端和资源所有者的状态都会随声明一起提供

成功授权请求后,安全上下文中的 Authentication 类型可以使用身份验证转换器进行自定义。但是,默认身份验证类型、如何从令牌转换以及如何解析它包含的权限的详细信息取决于资源服务器类型:

  • 使用 JWT decoder时,默认身份验证类型为 JwtAuthenticationToken,并且使用访问令牌声明将颁发机构映射到 JWT 身份验证转换器
  • 使用 access tokens introspection时,默认身份验证类型为 BearerTokenAuthentication,并且使用内省端点响应通过自定义内省器

令牌类型适用性:

  • JWT decoder:
    适用于 JWT 类型的访问令牌。JWT 具有自包含性,使得资源服务器可以独立地验证和解析令牌,无需频繁与授权服务器通信。
  • access tokens introspection:
    适用于各种类型的访问令牌,包括Bearer Token(即令牌本身不包含可直接解析的信息)。Bearer Token通常由授权服务器生成并管理,其内容对资源服务器是不透明的,因此需要通过内省机制向授权服务器查询令牌的有效性和相关信息。

3.4. 在 oauth2Login 和 oauth2ResourceServer 之间进行选择

从上面我们可以看出 oauth2Loginoauth2ResourceServer 的用途是不同的。Spring Security 为每个提供了不同的 Spring Boot 启动器,因为两者不应该位于同一个Security(Web)FilterChain bean 中

3.4.1 两者的区别
  • 授权基础

    • oauth2Login:基于会话(Session)进行授权。当用户登录成功后,服务器会创建一个会话,在会话期间跟踪用户的状态和权限信息。例如,用户登录一个电商网站,登录成功后服务器创建会话,在用户浏览商品、添加到购物车等操作时,服务器通过会话识别用户身份。

    • oauth2ResourceServer:基于 Bearer 令牌进行授权。客户端在请求资源时,需要在请求头中携带有效的 Bearer 令牌,资源服务器通过验证该令牌来确定请求的合法性。比如,一个移动应用请求获取用户的订单信息,需要在请求头中添加Authorization: Bearer < token >。

  • CSRF 保护

    • oauth2Login:由于基于会话,存在 CSRF(跨站请求伪造)攻击的风险,所以需要进行 CSRF 保护。CSRF 攻击是指攻击者通过诱导用户在已登录的网站上执行恶意操作,利用用户的会话身份进行非法请求。例如,用户在已登录银行网站的情况下,访问了恶意网站,恶意网站可能会利用用户在银行网站的会话发起转账请求。

    • oauth2ResourceServer:由于采用无状态的 Bearer 令牌机制,不存在会话的概念,所以不需要 CSRF 保护。每个请求都携带独立的令牌,资源服务器只验证令牌的有效性,而不依赖会话状态。

  • 认证和权限映射差异

    • oauth2Login:认证类型通常与用户登录过程相关,权限映射可能基于用户在身份提供者处的角色和权限信息。例如,用户使用 Google 登录应用,应用会根据 Google 返回的用户信息和角色来分配相应的权限。

    • 使用 JWT 解码器的 oauth2ResourceServer:默认的认证类型是JwtAuthenticationToken,权限通过 JWT 认证转换器利用访问令牌声明进行映射。JWT 令牌本身包含了用户的权限信息,资源服务器通过解码 JWT 令牌来获取这些信息。

    • 使用内省机制的 oauth2ResourceServer:默认的认证类型是BearerTokenAuthentication,权限通过使用内省端点响应的自定义内省器来解析。资源服务器将令牌发送到授权服务器的内省端点,授权服务器返回令牌的有效性和权限信息。

3.4.2 REST 端点配置倾向

由于无状态应用的可扩展性优势,通常更倾向于使用oauth2ResourceServer来配置 REST 端点。无状态应用的每个请求都是独立的,不依赖于服务器端的会话状态,因此可以轻松地进行水平扩展,通过增加服务器实例来处理更多的请求。

但使用oauth2ResourceServer要求向 REST API 发送(或路由)请求的组件能够完成一系列操作,包括从授权服务器获取令牌、将令牌存储在某种状态(如会话或本地存储)中、在令牌过期时刷新令牌,并在请求时使用访问令牌进行授权。

3.4.3 oauth2Login 的主要用例及扩展问题
  • 主要用例:oauth2Login主要用于服务器端渲染(SSR)的用户界面和将 Spring Cloud Gateway 用作 OAuth2 后端前端(BFF)。在服务器端渲染的应用中,用户通过登录页面进行身份验证,服务器生成包含用户信息的会话,然后渲染页面并返回给客户端。

  • 扩展问题:由于oauth2Login基于会话,是有状态的,在进行水平扩展以实现高可用性或负载均衡时,需要使用 Spring Session 或智能代理等技术来管理会话状态,确保用户在不同服务器实例之间的会话一致性。例如,使用 Spring Session 可以将会话信息存储在 Redis 等分布式缓存中,使得不同的服务器实例都可以访问和管理会话。

3.5. 组合异构请求授权机制

为了授权来自不同异构消费者的请求,我们可能需要使用 oauth2Loginoauth2ResourceServerx509formLoginBasic auth 等配置多个应用程序。例如,在 OAuth2 BFF 上,前端请求使用 oauth2Login 授权,而 actuator 端点使用 oauth2ResourceServerBasic 进行授权。

在这种情况下,每个请求授权机制都应该使用不同的Security(Web)FilterChain bean,所有 bean 都用不同的 @Order 装饰,除了最后一个 bean 之外,所有 bean 都按顺序使用 securityMatcher 定义它应该应用于哪个请求。如果没有 securityMatcher,则过滤器链将用作到目前为止尚未匹配的所有请求的默认值。

4. Thymeleaf 与登录

我们使用 Spring Boot 和 Keycloak 的 OAuth2 的第一个用例是 Thymeleaf 应用程序,它使用 OpenID Provider 对用户进行身份验证。它很简单,但是,演示了在有状态应用程序中使用 Keycloak 的基于角色的访问控制 (RBAC)。

值得注意的是:

  • 在具有单页或移动应用程序的系统中,我们将使用具有类似 OAuth2 客户端配置的 OAuth2 BFF

  • 我们的 Thymeleaf 应用程序是一个 OAuth2 客户端,因为它使用 oauth2Login 和 ID 令牌来构建用户身份验证,但它不使用访问令牌(它不会向资源服务器发送请求)。如前所述,浏览器和我们的 Spring 后端之间的请求是通过会话 cookie 授权的。

  • 要水平扩展,我们需要跨实例共享会话(Spring Session)或智能代理,将来自同一用户代理的所有请求根到同一实例。

4.1. 依赖项

使用 OAuth2 启用用户登录的最重要依赖项是 spring-boot-starter-oauth2-client。当然,当我们创建一个渲染 Thymeleaf 模板的 servlet 应用程序时,我们还需要spring-boot-starter-webspring-boot-starter-thymeleaf

让我们将它们添加到pom.xml中:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

如果我们想模拟身份验证以测试 Spring OAuth 2 应用程序中的访问控制,我们还需要具有测试范围的spring-security-test

4.2. 提供者和注册配置

由于 Keycloak 在 ${issuer-uri}/.well-known/openid-configuration 中公开了其 OpenID 配置,并且鉴于 Spring Security 可以从其 OpenId 配置中自动配置提供程序,因此定义其issuer-uri就足够了:

spring.security.oauth2.client.provider.cemx-keycloak.issuer-uri=http://192.168.1.121:8080/realms/cemx

现在,让我们使用上面的提供程序配置客户端注册

spring.security.oauth2.client.registration.keycloak.provider=cemx-keycloak
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.client-id=cemc-client
spring.security.oauth2.client.registration.keycloak.client-secret=**********
spring.security.oauth2.client.registration.keycloak.scope=openid

我们在 client-id 中指定的值与 Keycloak 管理控制台中client一致,从 Credentials (凭据) 选项卡中获取 client-secret 的值。

注意上述“keycloak”这个字眼,后面在配置Keycloak的 Valid redirect URIs http://192.168.1.128:8081/login/oauth2/code/keycloak ,要保持一致

4.3. 将 Keycloak Realm 角色映射到 Spring 安全权限

有多种方法可以使用 oauth2Login 在过滤器链中映射权限,但最简单的可能是 暴露 一个 GrantedAuthoritiesMapper bean,本教程采用该方法。

让我们首先定义一个 bean,负责从 Keycloak 声明集中提取权限,它可以是 ID 或访问令牌有效负载,以及 userinfo 或内省响应正文:

interface AuthoritiesConverter extends Converter<Map<String, Object>, Collection<GrantedAuthority>> {
}@SuppressWarnings("unchecked")
@Bean
AuthoritiesConverter realmRolesAuthoritiesConverter() {return claims -> {final var realmAccess = Optional.ofNullable((Map<String, Object>) claims.get("realm_access"));final var roles = realmAccess.flatMap(map -> Optional.ofNullable((List<String>) map.get("roles")));return roles.stream().flatMap(Collection::stream).map(SimpleGrantedAuthority::new).map(GrantedAuthority.class::cast).toList();};
}

由于 JVM 中的泛型类型擦除,并且由于应用程序上下文中可能有许多具有不同输入和输出的 Converter bean,因此当 Bean 工厂搜索 Converter<Map<String、Object>、Collection> Bean 时,此 AuthoritiesConverter 接口的的约束和提示是非常有用的。

当我们使用其 issuer-uri 自动配置 OIDC 提供程序时,我们在 GrantedAuthoritiesMapper 中获得的输入是 OidcUserAuthority 实例:

@Bean
GrantedAuthoritiesMapper authenticationConverter(Converter<Map<String, Object>, Collection<GrantedAuthority>> realmRolesAuthoritiesConverter) {return (authorities) -> authorities.stream().filter(authority -> authority instanceof OidcUserAuthority).map(OidcUserAuthority.class::cast).map(OidcUserAuthority::getIdToken).map(OidcIdToken::getClaims).map(realmRolesAuthoritiesConverter::convert).filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toSet());
}

4.4. 将SecurityFilterChain Bean 放在一起

以下是我们将使用的完整 SecurityFilterChain

@Bean
SecurityFilterChain clientSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception {http.oauth2Login(Customizer.withDefaults());http.logout((logout) -> {final var logoutSuccessHandler =new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/");logout.logoutSuccessHandler(logoutSuccessHandler);});http.authorizeHttpRequests(requests -> {requests.requestMatchers("/", "/favicon.ico").permitAll();requests.requestMatchers("/admin").hasAuthority("ADMIN");requests.anyRequest().denyAll();});return http.build();
}

说明:

oauth2Login() 方法将 OAuth2LoginAuthenticationFilter 添加到过滤器链中。此过滤器拦截请求并应用所需的逻辑来处理授权代码和刷新令牌流。它还在 session 中存储令牌。

要了解 OAuth2 注销的工作原理,我们应该记住,用户至少有两个独立的会话:一个在授权服务器上(在我们的例子中是 Keycloak),另一个在具有 oauth2Login 的每个客户端上。要完全注销,必须终止所有会话

OpenID 标准定义了实现此目的的不同方法,但我们将重点介绍使用 OidcClientInitiatedLogoutSuccessHandler配置的 OpenID Connect RP-Initiated Logout。

在此流程中,用户代理(用户的浏览器)首先从依赖方(使用 oaut2Login 的 Spring 应用程序)注销以关闭其会话。RP 通过重定向到 OpenID 提供程序 (OP) 作为响应,其中包含链接到要关闭的会话的 ID 令牌和注销后 URL(在我们的例子中为 Thymeleaf 索引)。然后,OP 关闭其会话并将用户代理重定向到提供的 URL。

总而言之,我们定义了访问控制规则:

  • 要使用户从索引登录,它必须可供匿名请求访问。

  • /admin 路径只能由被授予ADMIN 权限的经过身份验证的用户访问。

4.5. Thymeleaf 网页

我们将 Thymeleaf 用于我们的两个网页:

  • index.html 根据用户的状态显示登录注销按钮。它还包含一个用于导航到 /admin 的按钮,为了获得良好的用户体验,我们只向具有 ADMIN 权限的经过身份验证的用户显示。

  • admin.html 仅包含静态内容。

我们的 index.html 具有条件逻辑,能够使用用户会话中的值:

<div class="container"><h1 class="form-signin-heading">Cemx: Keycloak &amp; Spring Boot</h1><p>Welcome to a Thymeleaf UI served by Spring Boot and secured using Keycloak!</p><div th:if="${!isAuthenticated}"><a href="/oauth2/authorization/keycloak"><button type="button"class="btn btn-lg btn-primary btn-block">Login</button></a></div><div th:if="${isAuthenticated}"><p>Hi <span th:utext="${name}">..!..</span>!</p><a href="/logout"><button type="button" class="btn btn-lg btn-primary">Logout</button></a><a th:if="${isAdmin}" href="/admin"><button type="button" class="btn btn-lg btn-primary">Browse to Admin userspage</button></a></div>
</div>

4.6. 控制器

UiController 构建索引页模型(name,以及 isAuthenticatedisAdmin 标志),并解析 Thymeleaf 模板:

@Controller
public class UiController {@GetMapping("/")public String getIndex(Model model, Authentication auth) {model.addAttribute("name",auth instanceof OAuth2AuthenticationToken oauth && oauth.getPrincipal() instanceof OidcUser oidc ? oidc.getPreferredUsername() : "");model.addAttribute("isAuthenticated", auth != null && auth.isAuthenticated());model.addAttribute("isAdmin", auth != null && auth.getAuthorities().stream().anyMatch(authority -> Objects.equals("ADMIN", authority.getAuthority())));return "index.html";}@GetMapping("/admin")public String getAdmin(Model model, Authentication auth) {return "admin.html";}
}

4.7. 使用 oauth2Login 尝试 Thymeleaf 应用程序

现在,我们可以使用我们最喜欢的 IDE 启动应用程序。或者,从 maven 父上下文中,我们将运行:

sh ./mvnw -pl spring-boot-mvc-client spring-boot:run

通过将浏览器指向 http://192.168.1.128:8081/,我们应该会看到一个带有登录按钮的页面。

导航到为 ADMIN 用户保留的页面的按钮只有在以 davy 身份(而不是 test)登录时才可见。

RP 发起的注销后的下一次登录尝试中,我们应该必须输入凭证。

如果我们从 Java conf 中删除 logout 部分,则从 Spring 应用程序注销时 Keycloak 会话不会结束,新的登录尝试将静默完成。Keycloak 不会显示登录表单,因为从它的角度来看,我们仍然处于登录状态。

5. 带有 JWT 解码器的 REST API

现在让我们继续介绍带有 Spring Boot 和 Keycloak 的 OAuth2,以及需要 JWT 格式的 Bearer 访问令牌的无状态 REST API。

5.1. 依赖项

最重要的依赖项是 spring-boot-starter-oauth2-resource-server。当我们创建一个 servlet 应用程序时,我们还需要 spring-boot-starter-web

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

5.2. 配置 JWT 解码器

为了在资源服务器上验证访问令牌,我们可以选择 JWT decoder或introspection。第一个请求要求授权服务器以 JWT 格式颁发访问令牌。但是,它的效率要高得多,因为自省需要针对每个请求从资源服务器调用授权服务器。这会导致延迟,并可能使授权服务器过载。

Keycloak 访问令牌是 JWT,使用 Spring Boot,单个属性就足以使用 JWT 解码器配置资源服务器:

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://1923168.1.212:8080/realms/cemx

5.3. 将 Keycloak Realm 角色映射到 Spring 安全权限

对于带有 JWT decoder的资源服务器,我们应该configure the authentication converter.。

我们可以在这里重用以前的 authorities converter bean 的实现:

@Bean
JwtAuthenticationConverter authenticationConverter(Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter) {JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> authoritiesConverter.convert(jwt.getClaims()));return jwtAuthenticationConverter;
}

值得注意的是,这里是Access Token ,而不是ID Token

5.4. 将 SecurityFilterChain Bean 放在一起

现在,我们已准备好定义 Resource Server 安全筛选器链:

@Bean
SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http, Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter) throws Exception {http.oauth2ResourceServer(resourceServer -> {resourceServer.jwt(jwtDecoder -> {jwtDecoder.jwtAuthenticationConverter(jwtAuthenticationConverter);});});http.sessionManagement(sessions -> {sessions.sessionCreationPolicy(SessionCreationPolicy.STATELESS);}).csrf(AbstractHttpConfigurer::disable);http.authorizeHttpRequests(requests -> {requests.requestMatchers("/me").authenticated();requests.anyRequest().denyAll();});return http.build();
}

在此代码中:

  • 第一个块支持使用 JWT 解码器和自定义身份验证转换器进行资源服务器配置,以将 Keycloak 角色转换为 Spring Security 权限。

  • 然后,我们完全禁用会话和针对 CSRF 的保护。

  • 最后,我们定义一些访问控制:只有具有有效 Bearer 令牌的请求才能访问 /me 端点,并且我们禁止访问任何其他资源

5.5. REST 控制器

为了演示资源服务器中的 Keycloak 角色映射,我们将在安全上下文中公开一个端点,该端点反映了 JwtAuthenticationToken 中的一些信息:

@RestController
public class MeController {@GetMapping("/me")public UserInfoDto getGreeting(JwtAuthenticationToken auth) {return new UserInfoDto(auth.getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME),auth.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());}public static record UserInfoDto(String name, List<String> roles) {}
}

这里,将身份验证强制转换为 JwtAuthenticationToken 是安全的,因为访问仅限于 isAuthenticated(),并且 JwtAuthenticationToken 是我们的身份验证转换器返回的内容

如果路由是 permitAll(),我们还应该处理 AnonymousAuthenticationToken,以防未经授权的请求 ( 那些没有 Bearer Token的请求)。

5.6. 试用 REST API

现在,我们已准备好实时试用我们的 REST API。就像我们对 Thymeleaf 应用程序所做的那样,我们可以使用我们最喜欢的 IDE 运行它,或者从 maven 父上下文执行:

sh ./mvnw -pl spring-boot-resource-server spring-boot:run

然后,要使用 Postman 从 Keycloak 获取Access Token,我们应该打开集合或请求的 Authorization 选项卡,选择 OAuth2,并使用我们已经在 Keycloak(重定向 URI)和 Spring 属性中设置的值填写表单,或者我们从 OpenID 配置 http://192.168.1.212:8080/realms/cemx/.well-known/openid-configuration中获得的值:
在这里插入图片描述

最后,我们向 http://192.168.1.128:8082/me 发送 GET 请求。响应应该是带有 Keycloak 登录和领域角色的 JSON 有效负载:
在这里插入图片描述

6. 创建 Keycloak Realm

在本教程中,我们在创建 Docker 容器时导入的自定义领域中对 Keycloak 对象进行了沙盒处理。这在团队中加入新开发人员或尝试替代配置时非常有用。

让我们详细介绍一下这些对象最初是如何创建的。

6.1. Realm*

让我们导航到左上角以单击Create Realm 按钮:
在这里插入图片描述

以下操作都是针对cemx这个realm的

6.2. 创建 Keycloak Client

现在,我们将导航到 Clients 页面,在那里我们可以创建一个名为cemc-client 的新客户端:
在这里插入图片描述

在下一步中,我们将确保Client authentication is enabled (这将使客户端支持),并且只选中Standard flow,这将激活授权代码和刷新令牌流:
在这里插入图片描述

最后,我们需要允许客户端的重定向 URI 和来源:
在这里插入图片描述

由于我们使用 oauth2Login 的 Spring Boot 客户端应用程序配置为在端口 8081 上运行,并且将 keycloak作为注册 ID,因此我们设置:

  • http://192.168.1.128:8081/login/oauth2/code/keycloak 作为 Valid redirect URIs。

  • http://192.168.1.128:8081/ 为有效的注销后 URI(Thymeleaf 索引页)

  • Web origins为+,以允许来自有效重定向 URI 的所有源

    • 允许的CORS来源。要允许所有有效重定向URI的来源,请添加“+”。但这不包括“”通配符。要允许所有来源,请明确添加“”。

6.3. 配置哪些 JSON 有效负载包含 Realm 角色

为了确保将 Keycloak 角色添加到我们在 Spring 应用程序中使用的各种有效负载中,我们导航到客户端详细信息中的 Client scopes 选项卡:
在这里插入图片描述

点击 cemc-client-dedicated 查看其配置详细信息:
在这里插入图片描述

单击 Add predefined mapper 按钮,然后选择 realm roles mapper:
在这里插入图片描述

在编辑时,我们可以选择哪些JSON 有效负载包含Realm roles:

  • access tokens:用于具有JWT Decoder的 Resource Server

  • introspection:对于具有 token introspection 的资源服务器(Spring Security 措辞中的不透明 token)

  • ID tokens::适用于具有 oauth2Login 和 OpenID 的客户端(使用 issuer-uri 自动配置提供程序)

  • userinfo:适用于具有 oauth2Login 但没有 OpenID 的客户端(issuer-uri 留空,并在 Spring conf 中设置其他提供程序属性)

因为我们例子中的 oauth2Login OpenID Client端和Resource Server 端中都实现了一些基于角色的访问控制(RBAC),所以我们应该确保将领域角色添加到访问令牌和 ID 令牌中。
在这里插入图片描述

6.4. Realm 角色

在 Keycloak 中,我们可以定义角色,并将其分配给用户,用于所有 Realm 领域或每个Client。

在本教程中,我们将重点介绍Real roles。导航到 Realm roles 页面:
在这里插入图片描述

在那里,我们创建 ADMIN 角色:
在这里插入图片描述

6.5. 用户和 Realm 角色分配

让我们转到 Users 页面添加两个用户(一个名为 admin 并被授予 ADMIN 角色,另一个名为 igor 并被授予没有 realm 角色):

我们首先创建一个名为admin 的新用户:
在这里插入图片描述

单击 Create 按钮后,我们可以看到用户详细信息。我们应该浏览到 credential 选项卡以设置其密码(123456)
在这里插入图片描述
最后,我们导航到 Role Mappings 选项卡以分配ADMIN 角色(请注意 Filter by realm roles 下拉列表):
在这里插入图片描述
创建test用户,请重复上述步骤,只不过不要分配role

6.6. 导出 Realm 数据库

完成 realm 配置后,我们可以使用以下命令将其导出(在 Docker desktop 中,使用正在运行的容器的 Exec 选项卡):

cd /opt/keycloak/bin/
sh ./kc.sh export --dir /tmp/keycloak/ --users realm_file

然后,我们可以从 Files (文件) 选项卡中收集每个领域的 JSON 文件。

7. 总结

在本文中,我们使用 Spring Boot 和 Keycloak 配置了带有 OAuth2 的后端。

除了使用 Docker 进行最小的 Keycloak 设置外,我们还了解了如何导入和导出领域。

此外,在查看了 Spring 应用程序中 OAuth2 的不同配置选项后,我们将 Spring 应用程序配置为具有 oauth2Login 的有状态 OAuth2 客户端或无状态 OAuth2 资源服务器。

相关文章:

使用 Spring Boot 和 Keycloak 的 OAuth2 快速指南

1. 概述 本教程是关于使用 Spring Boot 和 Keycloak 通过 OAuth2 配置后端的。 我们将使用 Keycloak 作为 OpenID 提供程序。我们可以将其视为负责身份验证和用户数据&#xff08;角色、配置文件、联系信息等&#xff09;的用户服务。它是最完整的 OpenID Connect &#xff0…...

4个小时开发DeepSeek+baiduNaotu一键生成思维导图

一、引言 最近发现AI生成思维导图的解决方案普遍存在两个断层&#xff1a;用户需手动复制模型输出的JSON数据到脑图软件&#xff0c;且缺乏实时可视化反馈。基于日常使用的BaiduNaotu框架&#xff08;其轻量级架构与简洁的UI设计已满足基础需求&#xff09;&#xff0c;我决定…...

DeepSeek 开源狂欢周(一)FlashMLA:高效推理加速新时代

上周末&#xff0c;DeepSeek在X平台&#xff08;Twitter&#xff09;宣布将开启连续一周的开源&#xff0c;整个开源社区为之沸腾&#xff0c;全球AI爱好者纷纷为关注。没错&#xff0c;这是一场由DeepSeek引领的开源盛宴&#xff0c;推翻了传统推理加速的种种限制。这周一&…...

视频批量分段工具

参考原文&#xff1a;视频批量分段工具 选择视频文件 当您启动这款视频批量分段工具程序后&#xff0c;有两种便捷的方式来选择要处理的视频文件。其一&#xff0c;您可以点击程序界面中的 “文件” 菜单&#xff0c;在下拉选项里找到 “选择视频文件” 按钮并点击&#xff1b…...

【OMCI实践】ONT上线过程的omci消息(五)

引言 在前四篇文章中&#xff0c;主要介绍了ONT上线过程的OMCI交互的第一、二、三个阶段omci消息&#xff0c;本篇介绍第四个阶段&#xff0c;OLT下发配置到ONT。前三个阶段&#xff0c;每个厂商OLT和ONT都遵循相同标准&#xff0c;OMCI的交换过程大同小异。但第四个阶段&…...

git从零学起

从事了多年java开发&#xff0c;一直在用svn进行版本控制&#xff0c;如今更换了公司&#xff0c;使用的是git进行版本控制&#xff0c;所以打算记录一下git学习的点滴&#xff0c;和大家一起分享。 百度百科&#xff1a; Git&#xff08;读音为/gɪt/&#xff09;是一个开源…...

服务器间迁移conda环境

注意&#xff1a;可使用迁移miniconda文件 or 迁移yaml文件两种方式&#xff0c;推荐前者&#xff0c;基本无bug&#xff01; 一、迁移miniconda文件&#xff1a; 拷贝旧机器的miniconda文件文件到新机器: 内网拷贝&#xff1a;scp -r mazhf192.168.1.233:~/miniconda3 ~/ 外…...

计算机毕业设计SpringBoot+Vue.js精准扶贫管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

[RH342]tcpdump

[RH342]tcpdump 1. 题目2. 解题 1. 题目 服务器serverc 和 servera 之间有进程定期发送一个明文密码,找出它2. 解题 找出通信端口 抓包分析 tcpdump -X -vv port 6644红框中就是密码,所以密码是root123...

LeetCode-Hot100-001两数之和

给出个人解答&#xff0c;不懂的可以在评论区问 代码 使用的手写的hash函数 class Hash{ public:static const int MAXN 10007;int num;struct Data{int key;int v; int nxt;};vector<Data> data;vector<int> head;Hash(): num(0), data(3*MAXN), head(3*MAXN)…...

(2.26 “详细分析示例“ 暴力+位运算 最长优雅子数组)leetcode 2401

a&b0说明a和b的每一位都是一个0和一个1 不存在两个均为1的位次 a|0a 0与任何数|都等于它本身 &#xff08;mask&#xff09;的作用&#xff1a; 担心两数的1在用一位导致mask覆盖了&#xff1f; 答&#xff1a;出现这种情况说明mask与nums j后就直接break 由&#xff1a;…...

【Go】十六、protobuf构建基础服务信息、grpc服务启动的基础信息

商品服务 服务结构 创建 goods 服务&#xff0c;将之前 user 服务的基本结构迁移到 goods 服务上&#xff0c;完整目录是&#xff1a; mxshop_srvs user_srv … tmp … goods_srv config config.go 配置的读取表 global global.go 数据库、日志初始化、全局变量定义 handler …...

ONNX转RKNN的环境搭建

将ONNX模型转换为RKNN模型的过程记录 工具准备 rknn-toolkit:https://github.com/rockchip-linux/rknn-toolkit rknn-toolkit2:https://github.com/airockchip/rknn-toolkit2 rknn_model_zoo:https://github.com/airockchip/rknn_model_zoo ultralytics_yolov8:https://github…...

解决npm run dev报错

解决&#xff1a;Node.js 版本更新后与 OpenSSL 不兼容导致的npm报错“Error: error:0308010C:digital envelope routines::unsupported” 方法一&#xff1a;更改系统环境变量方法二&#xff1a;更改项目环境变量方法三&#xff1a;更换 Node.js 版本方法四&#xff1a;升级依…...

【Kubernetes】对资源进行PATCH

文章目录 1 更新资源的方式2 PATCH的三种方式2.1 JSON Patch2.2 Merge Patch2.3 Strategic Merge Patch 3 kubectl中的patch命令4 PATCH的优势和问题5 参考文档 1 更新资源的方式 K8S的核心就是各种资源以及针对资源的控制器&#xff0c;为了能够操作资源对象&#xff0c;apis…...

打破关节动力桎梏!杭州宇树科技如何用“一体化设计”重塑四足机器人性能?

核心价值&#xff1a;通过集成电机与行星减速器、创新双联齿轮结构&#xff0c;实现机器人关节动力单元体积缩小50%&#xff0c;力矩控制精度提升30%。&#xff08;申请人&#xff1a;杭州宇树科技有限公司&#xff0c;申请号&#xff1a;201821267397.0&#xff09; 一、技术解…...

一劳永逸解决vsocde模块import引用问题

这里写目录标题 原因解决方案 原因解决方案 原因&#xff1a; VSCode中需要显式地声明PYTHONPATH&#xff0c;不然根本找不到本项目内的模块和包的路径。 解决方法&#xff0c;加入到setting。json里当前Project路径&#xff0c;以后运行就自动添加了&#xff1a; 打开设置 …...

在 Vue 组件中,如何确认父组件在 add 模式下传入 value 的情况及其对子组件 getProducts() 方法的触发影响?

文章目录 父组件中 <ave-form> 的使用add 模式下触发逻辑value 的传入情况是否触发 getProducts()&#xff1f; 验证 add 模式下 getProducts() 是否触发结论&#xff1a; 检查父组件传入 value 的完整情况如何明确知道父组件传入的 value最终回答 父组件 index.vue子组件…...

Unity XR-XR Interaction Toolkit开发使用方法(十)组件介绍(XR Interaction Group)

目录 一、插件介绍 二、主要组件 XR Interaction Manager XR Controller XR Interactor XR Direct Interactor XR Ray Interactor XR Socket Interactor XR Gaze Interactor 三、XR Interaction Group 1、组件介绍 2、核心功能与特点 优先级与冲突管理 动态交互切…...

docker简介-学习与参考

docker Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱…...

3dtiles平移旋转工具制作

3dtiles平移旋转缩放原理及可视化工具实现 背景 平时工作中&#xff0c;通过cesium平台来搭建一个演示场景是很常见的事情。一般来说&#xff0c;演示场景不需要多完善的功能&#xff0c;但是需要一批三维模型搭建&#xff0c;如厂房、电力设备、园区等。在实际搭建过程中&…...

【第十节】C++设计模式(结构型模式)-Flyweight( 享元)模式

目录 一、问题背景 二、模式选择 三、代码实现 四、总结讨论 一、问题背景 享元模式&#xff08;Flyweight Pattern&#xff09;在对象存储优化中的应用 在面向对象系统的设计与实现中&#xff0c;创建对象是最常见的操作之一。然而&#xff0c;如果一个应用程序使用了过多…...

VScode在windows10上使用clang-format

用途&#xff1a;自动调整代码格式&#xff0c;如缩进等。 clang-format官方文档&#xff1a;ClangFormat — Clang 21.0.0git documentation 前提&#xff1a;有一个.clang-format文件 下载LLVM&#xff1a;https://github.com/llvm/llvm-project/releases&#xff0c;将可…...

青少年编程与数学 02-010 C++程序设计基础 11课题、程序结构

青少年编程与数学 02-010 C程序设计基础 11课题、程序结构 一、C程序结构二、main函数1. main 函数的基本形式1.1 无参数形式1.2 带参数形式 2. 参数解释3. 示例3.1 无参数形式3.2 带参数形式 4. 编译和运行4.1 编译4.2 运行 5. main 函数的返回值6. 总结 三、预处理指令1. #in…...

Node.js与MySQL的深入探讨

Node.js与MySQL的深入探讨 引言 Node.js,一个基于Chrome V8引擎的JavaScript运行时环境,以其非阻塞、事件驱动的方式在服务器端应用中占据了一席之地。MySQL,作为一款广泛使用的开源关系型数据库管理系统,凭借其稳定性和高效性,成为了许多应用的数据库选择。本文将深入探…...

【洛谷贪心算法题】P2240部分背包问题

【解题思路】 贪心策略选择 对于部分背包问题&#xff0c;关键在于如何选择物品放入背包以达到最大价值。由于物品可以分割&#xff0c;遍历排序后的物品数组&#xff0c;根据物品重量和背包剩余容量的关系&#xff0c;决定是将整个物品放入背包还是分割物品放入背包&#xff…...

ArcGIS Pro技巧实战:高效矢量化天地图地表覆盖图

在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;地表覆盖图的矢量化是一项至关重要的任务。天地图作为中国国家级的地理信息服务平台&#xff0c;提供了丰富且详尽的地表覆盖数据。然而&#xff0c;这些数据通常以栅格格式存在&#xff0c;不利于进行空间分析和数据…...

AF3 pair_sequences函数解读

AlphaFold3 msa_pairing模块的pair_sequences函数的核心目标是基于 MSA(多序列比对)中的物种信息,在多条链之间建立 MSA 配对索引,从而帮助 AlphaFold3 捕捉共进化信息,提升蛋白复合物预测的准确性。函数pair_sequences 通过调用 _make_msa_df、 _create_species_dict 以…...

在VSCode 中使用通义灵码最新版详细教程

在 VSCode 中使用通义灵码&#xff1a;最新版详细教程与使用场景 Visual Studio Code&#xff08;简称 VSCode&#xff09;是一款由微软开发的轻量级、功能强大的开源代码编辑器&#xff0c;支持多种编程语言&#xff0c;深受开发者喜爱。而通义灵码&#xff08;TONGYI Lingma…...

ssh和rdp踩坑

ssh和rdp&#xff08;远程桌面&#xff09;踩坑 使用微软账号登录windows的话&#xff0c;ssh的用户名是本地用户名&#xff08;就是c盘用户文件夹下的用户名&#xff09;&#xff0c;rdp的用户名是微软账号用户名&#xff0c;但是密码都是微软账号的密码&#xff0c;跟登录密…...

Linux驱动学习(四)--字符设备注册

上一节讲到的字符设备注册与销毁是通过cdev_init、cdev_add、cdev_del等函数分步执行的&#xff0c;本小节用一种更简单的方式&#xff0c;来注册字符设备 register_chrdev 如果major为0&#xff0c;该函数将动态的分配一个主设备号并且返回对应的值如果major > 0&#xff…...

vue3 下载文件 responseType-blob 或者 a标签

在 Vue 3 中&#xff0c;你可以使用 axios 或 fetch 来下载文件&#xff0c;并将 responseType 设置为 blob 以处理二进制数据。以下是一个使用 axios 的示例&#xff1a; 使用 axios 下载文件 首先&#xff0c;确保你已经安装了 axios&#xff1a; npm install axios然后在你…...

Meta最新研究:从单张照片到3D数字人的革命性突破

随着人工智能技术的发展,3D建模和虚拟人物生成逐渐变得更加普及和高效。Meta(前身为Facebook)的最新研究成果展示了如何仅通过一张普通手机拍摄的照片就能生成高质量、全方位的3D数字人。这项技术不仅适用于虚拟试衣、游戏角色建模,还能广泛应用于AR/VR内容生成等领域。本文…...

大中型虚拟化园区网络设计

《大中型虚拟化园区网络设计》属于博主的“园区网”专栏&#xff0c;若想成为HCIE&#xff0c;对于园区网相关的知识需要非常了解&#xff0c;更多关于园区网的内容博主会更新在“园区网”专栏里&#xff0c;请持续关注&#xff01; 一.前言 华为云园区网络解决方案(简称Cloud…...

Vue 项目中配置代理的必要性与实现指南

Vue 项目中配置代理的必要性与实现指南 在 Vue 前端项目的开发过程中&#xff0c;前端与后端地址通常不同&#xff0c;可能引发跨域问题。为了在开发环境下顺畅地请求后端接口&#xff0c;常常会通过配置**代理&#xff08;proxy&#xff09;**来解决问题。这篇文章将详细解析…...

chromadb向量数据库使用 (1)

目录 完整代码代码解释 完整代码 import chromadb chroma_client chromadb.Client()collection chroma_client.create_collection(name"my_collection")collection.add(documents["This is a document about pineapple","This is a document about…...

玩机日记 12 fnOS使用lucky反代https转发到外网提供服务

目录 1、安装lucky 2、更新lucky 3、上传ssl证书 4、设置安全入口&#xff0c;替换fnOS的应用url 5、添加https反代 这一篇主要是解决一下飞牛反代https的问题。可以先看玩机日记 12.5 在PVE Windows11上部署本地AI模型&#xff0c;使用群晖反代https转发到外网提供服务&a…...

5分钟学会SpringAI

引言 要开发一个Spring AI的入门案例&#xff0c;我们可以从一个简单的Spring Boot项目开始&#xff0c;然后集成Spring AI的功能来实现基本的生成式AI任务。下面是一个步骤指南&#xff0c;帮助你快速启动并运行一个简单的Spring AI应用。 步骤 1: 准备环境 首先&#xff0…...

专业的UML开发工具StarUML

专业的UML开发工具StarUML 可靠的软件建模软件StarUML StarUML 是一款支持统一建模语言 (UML)框架的开源建模软件。它提供了几种类型的图表&#xff0c;并允许用户生成多种语言的代码。在它的帮助下&#xff0c;软件开发人员可以创建设计、概念和编码解决方案。但是&#xff0…...

go语言环境下载与配置(Windows)

下载 Go下载 - Go语言中文网 - Golang中文社区 建议在D盘中创建文件夹安装到 D 盘 &#xff0c;方便进行管理&#xff0c;然后进行傻瓜式安装。 安装 验证安装 go version 安装成功 配置环境变量 winE --> 右击此电脑 --> 选择属性 --> 高级系统设置 --> 点击…...

矩阵系列 题解

1.洛谷 P1962 斐波那契数列 题意 大家都知道&#xff0c;斐波那契数列是满足如下性质的一个数列&#xff1a; F n { 1 ( n ≤ 2 ) F n − 1 F n − 2 ( n ≥ 3 ) F_n \left\{\begin{aligned} 1 \space (n \le 2) \\ F_{n-1}F_{n-2} \space (n\ge 3) \end{aligned}\right. …...

C++ 正则表达式分组捕获入门指南

在 C 中&#xff0c;正则表达式&#xff08;regex&#xff09;是一种用于匹配字符串模式的强大工具。正则表达式不仅能帮助你查找符合特定模式的字符&#xff0c;还能捕获匹配的子字符串&#xff08;即分组捕获&#xff09;。这篇文章将介绍 C 正则表达式中的分组捕获机制&…...

动态数据表格:基于 PrimeFaces 的运行时列选择实现

在现代的 Web 应用开发中&#xff0c;动态数据表格是一个非常实用的功能&#xff0c;它允许用户根据自己的需求选择显示哪些列。这种灵活性不仅提升了用户体验&#xff0c;还能适应不同的数据展示需求。今天&#xff0c;我们将通过一个具体的实现案例&#xff0c;展示如何使用 …...

Plugin ‘mysql_native_password‘ is not loaded`

Plugin ‘mysql_native_password’ is not loaded mysql_native_password介绍1. 使用默认的认证插件2. 修改 my.cnf 或 my.ini 配置文件3. 加载插件&#xff08;如果确实没有加载&#xff09;4. 重新安装或检查 MySQL 版本 遇到错误 ERROR 1524 (HY000): Plugin mysql_nativ…...

Kotlin 知识点二 延迟初始化和密封类

对变量延迟初始化 Kotlin 语言的许多特性&#xff0c;包括变量不可变&#xff0c;变量不可为空&#xff0c;等等。这些特性 都是为了尽可能地保证程序安全而设计的&#xff0c;但是有些时候这些特性也会在编码时给我们带来不 少的麻烦。 比如&#xff0c;如果你的类中存在很多…...

DeepSeek开源周第二弹:DeepEP如何用RDMA+FP8让MoE模型飞起来?

一、引言&#xff1a;MoE模型的通信瓶颈与DeepEP的诞生 在混合专家&#xff08;MoE&#xff09;模型训练中&#xff0c;专家间的全对全&#xff08;All-to-All&#xff09;通信成为性能瓶颈。传统方案在跨节点传输时带宽利用率不足50%&#xff0c;延迟高达300μs以上。DeepSee…...

C++ Primer 成员访问运算符

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…...

无人机遥控器的亮度 和 两个工作频率

工作频率 2.4000-2.4835 GHz &#xff0c; 5.725-5.850 GHz 1.这是一个无人机的遥控器的两个工作频率&#xff0c;为什么会有两个工作频率&#xff1f; 无人机的遥控器采用双频段设计&#xff08;2.4GHz 和 5.8GHz&#xff09;&#xff0c;主要是为了解决以下问题并优化性…...

ubuntu20.04安装docker

3台主机&#xff0c;2台都能正确安装&#xff0c;第三台怎么都安装不成功&#xff1b; 3台主机都是一样的配置和系统&#xff1b; 后来看来是其外网的ip不一样&#xff0c;导致第三台主机可能被Qiang&#xff0c;不过错误只是提示签名不正确&#xff0c;在设置签名时好像没有…...

【含文档+PPT+源码】基于过滤协同算法的旅游推荐管理系统设计与实现

项目介绍 本课程演示的是一款基于过滤协同算法的旅游推荐管理系统设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系…...