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

Spring Boot OAuth2.0应用

本文展示Spring Boot中,新版本OAuth2.0的简单实现,版本信息:

spring-boot 2.7.10
spring-security-oauth2-authorization-server 0.4.0
spring-security-oauth2-client 5.7.7
spring-boot-starter-oauth2-resource-server 2.7.10

展示三个服务,分别为

  • 授权服务:作为认证中心,用于向客户端发放授权码code与令牌token
  • 资源服务:保存客户端要访问的资源
  • 客户端:向授权服务发起请求,获取授权码code,再用code换取token,最后使用token访问资源


授权服务搭建

总体预览

授权服务预览

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>OAuth2-test</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</artifactId><version>0.4.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.26</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

配置类

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.*;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;import javax.sql.DataSource;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;@EnableWebSecurity
@Configuration
public class OAuth2Config {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate DataSource dataSource;@BeanUserDetailsManager userDetailsManager() {return new JdbcUserDetailsManager(dataSource);}@Beanpublic SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {// 定义授权服务配置OAuth2AuthorizationServerConfigurer configurer = new OAuth2AuthorizationServerConfigurer();// 获取授权服务器相关的请求端点RequestMatcher endpointsMatcher = configurer.getEndpointsMatcher();http.authorizeHttpRequests(authorize -> authorize// 配置放行的请求,/register用于客户端及用户的注册// /register/*代表放行api下的单层路径,/register/**代表其下的所有子路径.antMatchers("/register/**", "/login").permitAll()// 其他任何请求都需要认证.anyRequest().authenticated())//配置登录页,用于授权请求未认证时进行用户登录授权.formLogin().and()// 忽略掉相关端点的CSRF(跨站请求伪造攻击防御): 对授权端点的访问不被CSRF拦截.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)//如果是post请求,即便上面已经放行,还是会被csrf过滤器拦截,所以针对post请求,如果开启了csrf防护,需要再配置放行.ignoringAntMatchers("/register/**"))// 使用BearerTokenAuthenticationFilter对AccessToken及idToken进行解析验证// idToken是开启OIDC时,授权服务连同AccessToken(就是访问资源需要的token)一起返回给客户端的,用于客户端验证用户身份,结合此处配置使用BearerTokenAuthenticationFilter来验证idToken.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)// 应用授权服务器的配置,使其生效.apply(configurer);configurer//开启oidc,客户端会对资源所有者进行身份认证,确保用户身份的真实性、防止身份伪造、增强安全性。// 开启后,除了访问令牌access_token,还会多一个用户身份认证的idToken.oidc(Customizer.withDefaults())//配置用何种方式保存注册的客户端信息,默认为内存保存,这里配置为数据库保存,表名为'oauth2_registered_client'//保存客户端注册信息,主要用于后续各种认证时对比客户端是否有效.registeredClientRepository(registeredClientRepository())//配置用何种方式保存OAuth2客户端的授权请求的信息。这包括授权码、访问令牌、刷新令牌等。// 默认为内存保存,这里配置为数据库保存,表名为'oauth2_authorization'//授权信息会作为认证依据,在后续请求token时被读取,不存在授权信息则不给客户端生成token.authorizationService(authorizationService())//配置用何种方式存储用户对客户端请求的授权同意(consent)信息。// 默认为内存保存,这里配置为数据库保存,表名为'oauth2_authorization_consent'//请求code时检查是客户端否已授权,未授权不予code.authorizationConsentService(authorizationConsentService())/*** OAuth2AuthorizationService 与 OAuth2AuthorizationConsentService区别:*  oauth2_authorization 主要与令牌管理相关,负责存储令牌及其生命周期信息。*  oauth2_authorization_consent 主要用于管理用户授权同意的记录,确保用户的授权选择被正确记录和遵守。* *///配置OAuth2认证各项端点的http访问路径,如获取授权码的、获取token的、验证token的等等.authorizationServerSettings(authorizationServerSettings());return http.build();}/*** 注册客户端应用的保存方式, 对应 oauth2_registered_client 表*/@Beanpublic RegisteredClientRepository registeredClientRepository() {return new JdbcRegisteredClientRepository(jdbcTemplate);}/*** 令牌的发放记录, 对应 oauth2_authorization 表*/
//  @Bean 这里的bean注解放开,就可以不用在上面的OAuth2AuthorizationServerConfigurer中配置了public OAuth2AuthorizationService authorizationService() {return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository());}/*** 把资源拥有者授权确认操作保存到数据库, 对应 oauth2_authorization_consent 表*/
//   @Bean 这里的bean注解放开,就可以不用在上面的OAuth2AuthorizationServerConfigurer中配置了public OAuth2AuthorizationConsentService authorizationConsentService() {return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository());}/*** AuthorizationServerS 的相关配置*/
//   @Bean 这里的bean注解放开,就可以不用在上面的OAuth2AuthorizationServerConfigurer中配置了public AuthorizationServerSettings authorizationServerSettings(){//使用默认配置return AuthorizationServerSettings.builder().build();}/*** token的配置项:过期时间、是否复用refreshToken刷新令牌等等* */@Beanpublic TokenSettings clientTokenSettings(){return TokenSettings.builder()// 令牌存活时间:2小时.accessTokenTimeToLive(Duration.ofHours(2))// 令牌可以刷新,重新获取.reuseRefreshTokens(true)// 刷新时间:30天(30天内当令牌过期时,可以用刷新令牌重新申请新令牌,不需要再认证).refreshTokenTimeToLive(Duration.ofDays(30)).build();}/***  针对 OAuth 2.0 客户端的各种设置* */@Beanpublic ClientSettings clientSettings(){return ClientSettings.builder()// 是否需要用户授权确认.requireAuthorizationConsent(true)//指定使用client_secret_jwt认证方式时的签名算法.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)//如果为true,当客户端使用授权码时,服务器会强制要求提供 PKCE 参数(code verifier 和 code challenge)//.requireProofKey(true)//为 OAuth 2.0 客户端配置一个 JWKS 的 URL 地址, 当其他服务需要验证该客户端的 JWT 时,它们可以访问这个 URL 获取用于验证的公钥。//.jwkSetUrl("").build();}/*** 用于授权服务生成token令牌的JWT设置,如下代码使用非对称加密* 资源服务会通过issuer获取此配置来对token进行验证,验证通过则客户端可以访问资源*/@Beanpublic JWKSource<SecurityContext> jwkSource() throws Exception {// 生成RSA密钥对KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);KeyPair keyPair = keyPairGenerator.generateKeyPair();// 构建JWKRSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())//公钥.privateKey(keyPair.getPrivate())//私钥// keyID是用来唯一标识密钥的,keyID可以帮助服务器区分不同的密钥。如果有多个密钥存在,服务器可以根据JWT中提供的kid值快速找到用于签名该JWT的密钥.keyID(UUID.randomUUID().toString()).build();// 构建JWKSetJWKSet jwkSet = new JWKSet(rsaKey);// 返回JWKSource实例return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);}/*** JWT token 解码配置*/@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}/*** 密码编码器,用于对密码进行加密* 比如使用bcrypt对客户端密钥编码后,在数据库中其值大致为如下格式,多了一个{bcrypt}前缀:*  {bcrypt}$2a$10$BaExfIkMtKtdqMVfkxlAR.fWlRDoJrmTOEz4oM4jZ3fxkio9IMYJS** */@Beanpublic PasswordEncoder passwordEncoder() {Map<String,PasswordEncoder> encoders = new HashMap<>();//使用bcrypt进行密码编码(这是目前最常用和推荐的密码加密方法)。encoders.put("bcrypt", new BCryptPasswordEncoder());//基于 PBKDF2 算法进行密码编码encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());//基于 SCrypt 算法进行密码编码encoders.put("scrypt", new SCryptPasswordEncoder());//基于 SHA-256 算法进行密码编码encoders.put("sha256", new StandardPasswordEncoder());//DelegatingPasswordEncoder 是一个委托的密码编码器,它可以根据密码存储时的前缀标识符来选择不同的密码编码器。//DelegatingPasswordEncoder 以 "bcrypt" 为默认的编码器,同时允许使用其他定义在 encoders Map 中的编码器。PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("bcrypt", encoders);return passwordEncoder;}}

yaml文件

server:port: 8080spring:datasource:url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: wangziyu123driver-class-name: com.mysql.cj.jdbc.Driver

controller

主要用于向授权服务注册客户端与用户,保存到数据库中

import com.oauth.entity.ClientDemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.web.bind.annotation.*;import java.util.UUID;@RestController
@RequestMapping("register")
public class RegisterController {//用于把用户存储到数据库@Autowiredprivate UserDetailsManager userDetailsManager;//客户端token配置@Autowiredprivate TokenSettings clientTokenSettings;//客户端配置@Autowiredprivate ClientSettings clientSettings;//注册客户端的存储库,已通过配置类指定为数据库存储@Autowiredprivate RegisteredClientRepository registeredClientRepository;//密码编码器@Autowiredprivate PasswordEncoder passwordEncoder;//用户注册方法@GetMapping("user")public String addUser(String userName,String password,String role) {UserDetails userDetails = User.builder().username(userName)//密码在数据库中存储为:{bcrypt}$2a$10$z******* 这样的格式,会加上{bcrypt}的前缀,后续OAuth2需要根据前缀做相关处理.password(passwordEncoder.encode(password)).roles(role).build();userDetailsManager.createUser(userDetails);return "用户注册成功";}//客户端注册方法@PostMapping("client")public String addClient(@RequestBody ClientDemo client) {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())//客户端ID和密钥.clientId(client.getId()).clientSecret(passwordEncoder.encode(client.getSecret()))//客户端获取token时的认证方式,这里指定使用client_secret_basic方式,即请求头的Authentication: Basic Auth.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)// 回调地址:授权码模式下,授权服务器会携带code向当前客户端的如下地址进行重定向。只能使用IP或域名,不能使用 localhost.redirectUri(client.getRedirectUri())// 授权范围(当前客户端的授权范围).scopes(scopes -> scopes.addAll(client.getScopes()))//配置支持多种授权模式.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)//授权码模式.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)// 刷新令牌.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)//客户端凭证模式.authorizationGrantType(AuthorizationGrantType.PASSWORD)//密码模式// OIDC 支持, 用于客户端对用户(资源所有者)的身份认证.scope(OidcScopes.OPENID) //OIDC 并不是授权码模式的必需部分,但如果客户端请求包含 openid scope,就必须启用 OIDC 支持。.scope(OidcScopes.PROFILE)//token配置项.tokenSettings(clientTokenSettings)// 客户端配置项.clientSettings(clientSettings).build();registeredClientRepository.save(registeredClient);return "客户端注册成功";}/*** 展示授权码的回调* */@GetMapping("returnCode")public String getCode(@RequestParam String code){return code;}
}

entity

import lombok.Data;import java.util.List;//注册客户端实体类
@Data
public class ClientDemo {private String id;private String secret;private String redirectUri;private List<String> scopes;
}

主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class OAuth2Server {public static void main(String[] args) {SpringApplication.run(OAuth2Server.class, args);}
}

用到的数据表

security框架中的表,如下语句为mysql数据库执行,注意yaml配置的数据库要与实际建表的数据库一致

authorities表

CREATE TABLE `authorities` (`username` varchar(50) NOT NULL,`authority` varchar(50) NOT NULL,UNIQUE KEY `ix_auth_username` (`username`,`authority`),CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

users表

CREATE TABLE `users` (`username` varchar(50) NOT NULL,`password` varchar(500) NOT NULL,`enabled` tinyint(1) NOT NULL,PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

oauth2_authorization表

CREATE TABLE `oauth2_authorization` (`id` varchar(100) NOT NULL,`registered_client_id` varchar(100) NOT NULL,`principal_name` varchar(200) NOT NULL,`authorization_grant_type` varchar(100) NOT NULL,`authorized_scopes` varchar(1000) DEFAULT NULL,`attributes` blob,`state` varchar(500) DEFAULT NULL,`authorization_code_value` blob,`authorization_code_issued_at` timestamp NULL DEFAULT NULL,`authorization_code_expires_at` timestamp NULL DEFAULT NULL,`authorization_code_metadata` blob,`access_token_value` blob,`access_token_issued_at` timestamp NULL DEFAULT NULL,`access_token_expires_at` timestamp NULL DEFAULT NULL,`access_token_metadata` blob,`access_token_type` varchar(100) DEFAULT NULL,`access_token_scopes` varchar(1000) DEFAULT NULL,`oidc_id_token_value` blob,`oidc_id_token_issued_at` timestamp NULL DEFAULT NULL,`oidc_id_token_expires_at` timestamp NULL DEFAULT NULL,`oidc_id_token_metadata` blob,`refresh_token_value` blob,`refresh_token_issued_at` timestamp NULL DEFAULT NULL,`refresh_token_expires_at` timestamp NULL DEFAULT NULL,`refresh_token_metadata` blob,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

oauth2_authorization_consent表

CREATE TABLE `oauth2_authorization_consent` (`registered_client_id` varchar(100) NOT NULL,`principal_name` varchar(200) NOT NULL,`authorities` varchar(1000) NOT NULL,PRIMARY KEY (`registered_client_id`,`principal_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

oauth2_registered_client表

CREATE TABLE `oauth2_registered_client` (`id` varchar(100) NOT NULL,`client_id` varchar(100) NOT NULL,`client_id_issued_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,`client_secret` varchar(200) DEFAULT NULL,`client_secret_expires_at` timestamp NULL DEFAULT NULL,`client_name` varchar(200) NOT NULL,`client_authentication_methods` varchar(1000) NOT NULL,`authorization_grant_types` varchar(1000) NOT NULL,`redirect_uris` varchar(1000) DEFAULT NULL,`scopes` varchar(1000) NOT NULL,`client_settings` varchar(2000) NOT NULL,`token_settings` varchar(2000) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

spring-security-oauth2-authorization-server下,可以看到对应sql

数据表



资源服务搭建

总体预览

资源服务预览

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>OAuth2-test-resource</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><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><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;@EnableWebSecurity
@Configuration
public class ResourceServerConfig {@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http//所有请求都需要验证.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())//开启jwt,用于令牌解析.oauth2ResourceServer(resourceServer -> resourceServer.jwt());return http.build();}}

yaml

server:port: 8081spring:security:oauth2:resourceserver:jwt:#项目启动初始化时,在JwtDecoderProviderConfigurationUtils的getConfiguration方法处,发起http://127.0.0.1:8080/.well-known/openid-configuration请求,向授权服务获取元数据端点信息,#此项为必须配置,资源服务会根据此地址获取的信息来对token进行验证issuer-uri: http://127.0.0.1:8080#如果配置了issuer-uri,此项可以不配置。因为通过issuer-uri配置的值,在JwtDecoders的withProviderConfiguration方法中自动获取为http://127.0.0.1:8080/oauth2/jwks#jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks

controller

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class MessagesController {@GetMapping("/read/resource")@PreAuthorize("hasAuthority('SCOPE_read')")//限制访问资源所需要的权限public String getResource1(){return "已成功获取资源";}}

主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ResourcesServer {public static void main(String[] args) {SpringApplication.run(ResourcesServer.class, args);}}


请求测试

第一步注册用户与客户端

注册用户

三个参数分别是用户名、密码、权限

http://127.0.0.1:8080/register/user?userName=wzy&password=wzy&role=ADMIN

执行:

用户注册成功

查看用户表

用户表



注册客户端

http://127.0.0.1:8080/register/client

请求体json

  • id:客户端id
  • secret:客户端密钥
  • redirectUri:重定向地址,授权服务会将授权码通过此地址返回给客户端
  • scopes:客户端的权限范围
{"id": "test-client","secret": "FjKNY8p2&Xw9Lqe$GH7Rd3Bt*5mZ4Pv#CV2sE6J!n","redirectUri": "http://127.0.0.1:8080/register/returnCode","scopes": ["read", "write"]
}

执行请求

客户端注册成功

查看数据库表oauth2_registered_client中是否注册上:

客户端注册表

第二步获取授权码

oauth2/authorize路径为security默认的授权码请求路径

http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=test-client&scope=read&redirect_uri=http://127.0.0.1:8080/register/returnCode

其他参数解释:

  • response_type:表示客户端请求授权码
  • client_id:客户端id
  • scope:客户端权限范围
  • redirect_uri:客户端重定向地址,要与与注册时一致

这些参数会由授权服务获取,来进行授权认证。

浏览器输入上面地址回车,自动跳转如下页面,输入注册的用户名密码点击Sign in

登录页

上面登录后,会再跳转到下面的授权页面,勾选要授予的权限read,然后Submit

授权页面


然后会获得授权码:

授权码

第三步换取token

请求地址

http://127.0.0.1:8080/oauth2/token

需要的参数

  • grant_type:授权模式,此处使用授权码模式,值固定为authorization_code
  • code:上一步返回的授权码
  • redirect_uri:重定向地址,与注册客户端时保持一致
  • client_id:客户端id,表单中可去除参数,不是必须
  • client_secret:客户端密钥,表单中可去除参数,不是必须

token请求请求体

请求头Auth处为必填,因为授权服务要验证客户端身份,类型选Bacis AuthUsername为客户端id,Password为客户端密钥(未加密的)

token请求header

如果你用的是postman,要按如下方式填写:

image-20240821201659492


实际请求中,Auth对应请求头的'Authorization: Basic dGVzdC1jbGllbnQ6RmpLTlk4cDImWHc5THFlJEdIN1JkM0J0KjVtWjRQdiNDVjJzRTZKIW4=',Basic后面是客户端id与密钥经过编码后的值,下面是实际请求展示:

curl --location --request POST 'http://127.0.0.1:8080/oauth2/token' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Authorization: Basic dGVzdC1jbGllbnQ6RmpLTlk4cDImWHc5THFlJEdIN1JkM0J0KjVtWjRQdiNDVjJzRTZKIW4=' \
--header 'Accept: */*' \
--header 'Host: 127.0.0.1:8080' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=ORM8bkef2X1fhvCmrIqzXSwzYwxD-RbD4yzcotRVW36iaLJJMiLLoCe7kbRCWtmMVGCB7ESJAqkBUbSC_zoUL5KXEX63f4Mc1MVTLe_DS-PKpvAwqzYb7Hv1qQ1ftLeZ' \
--data-urlencode 'redirect_uri=http://127.0.0.1:8080/register/returnCode'

点击发送后,返回的access_token即为令牌token

得到令牌

需要注意的是:授权码是一次性的,换取token后,原授权码就会失效,再获取token要使用新的授权码


最后获取资源

地址为资源服务的controller地址

http://127.0.0.1:8081/read/resource

参数如下,token处填的就是上面获取的access_token,发送后返回已成功获取资源,即为成功

请求资源

如果是postman测试工具,按照如下填写:

image-20240821202154474



客户端

关于客户端,其实在上面的请求测试中,我们已经模拟了客户端的操作。

在实际开发中,也存在许多客户端的变体形式,可能是前后端分离的前端项目,也有可能是单独的后端微服务程序,这里以单独的Spring Boot后端程序展示OAuth2 Clinet的使用。

Spring Boot同样提供了OAuth2的客户端集成,在授权码模式下,使用spring-boot-starter-oauth2-client结合@RegisteredOAuth2AuthorizedClient注解,客户端可以自动实现授权码的请求及令牌的获取,而不需要上面的手动请求操作。

总体预览

因为SpringBoot-OAuth2的请求缓存默认使用session实现,本文演示又使用了三个不同端口的服务,所以结合spring-session-data-redis实现会话管理,来达到自动请求的目的。

客户端预览

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>OAuth2-test-client</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId></dependency><!-- 用于客户端向资源服务发起请求 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><!-- 使用redis管理会话,实现不同服务的session共享 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency></dependencies></project>

yaml

server:port: 8082spring:security:oauth2:client:registration:#除了重定向地址,此处的客户端各项配置要与授权服务注册客户端的RegisterController中一致test-client:provider: oauth2server #这里的值可以自定义,需要和下面的issuer-uri上面的一致client-id: test-client  #客户端idclient-secret: FjKNY8p2&Xw9Lqe$GH7Rd3Bt*5mZ4Pv#CV2sE6J!n  #客户端密钥client-authentication-method: client_secret_basic  #客户端认证方式authorization-grant-type: authorization_code   #客户端支持的授权模式#重定向地址,格式在下方注释,这里要修改为:'客户端ip:port/login/oauth2/code/客户端id值'redirect-uri: "http://127.0.0.1:8082/login/oauth2/code/test-client"#redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"scope: read,openid,profile,write #权限provider:# 配置服务提供地址oauth2server:# issuer-uri 用于客户端向授权服务获取jwks信息issuer-uri: http://127.0.0.1:8080#共享session使用redis配置redis:host: 127.0.0.1port: 6379

config

package com.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;@EnableWebSecurity
@Configuration
public class OAuth2ClientConfig {@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()//所有请求都需要认证)//security默认情况下使用的oauth2Login配置.oauth2Login(Customizer.withDefaults())//security默认情况下使用的oauth2Client配置.oauth2Client(Customizer.withDefaults())//总是开启session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);return http.build();}}

controller

使用@RegisteredOAuth2AuthorizedClient注解作用:

  1. 当客户端请求资源并经过授权服务的认证后,客户端默认会将认证通过信息保存在内存中;
  2. @RegisteredOAuth2AuthorizedClient会使用Spring MVC的请求参数解析器,将保存的认证信息转为Controller的方法参数OAuth2AuthorizedClient对象;
  3. 最后在Controller中,可以直接从转换的OAuth2AuthorizedClient中取出token,再请求资源

示例如下:

package com.controller;import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;@RestController
@RequestMapping("/getResource")
public class ClientController {@GetMapping("/getToken")public String getToken(@RegisteredOAuth2AuthorizedClient("test-client") OAuth2AuthorizedClient oAuth2AuthorizedClient) {return oAuth2AuthorizedClient.getAccessToken().getTokenValue();}@GetMapping("/read")public String getServerARes1(@RegisteredOAuth2AuthorizedClient("test-client") OAuth2AuthorizedClient oAuth2AuthorizedClient) {//向资源服务发起请求,获取资源return getServer("http://127.0.0.1:8081/read/resource", oAuth2AuthorizedClient);}@GetMapping("/write")public String getServerARes2(@RegisteredOAuth2AuthorizedClient("test-client") OAuth2AuthorizedClient oAuth2AuthorizedClient) {//向资源服务发起请求,获取资源return getServer("http://127.0.0.1:8081/write/resource", oAuth2AuthorizedClient);}/*** 获取token,请求资源服务*/private String getServer(String url, OAuth2AuthorizedClient oAuth2AuthorizedClient) {// 获取 access_tokenString tokenValue = oAuth2AuthorizedClient.getAccessToken().getTokenValue();// 发起请求Mono<String> stringMono = WebClient.builder().defaultHeader("Authorization", "Bearer " + tokenValue) .build().get().uri(url).retrieve().bodyToMono(String.class);return stringMono.block();}
}

上面多了个write请求,将资源服务项目示例的Controller改为如下即可

package com.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class MessagesController {@GetMapping("/read/resource")@PreAuthorize("hasAuthority('SCOPE_read')")//限制访问资源所需要的权限public String getResource1(){return "已成功获取资源";}@GetMapping("/write/resource")@PreAuthorize("hasAuthority('SCOPE_write')")//限制访问资源所需要的权限public String getResource2(){return "已成功获取资源2";}}

主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class OAuth2Client {public static void main(String[] args) {SpringApplication.run(OAuth2Client.class, args);}
}

数据表修改

需要将oauth2_registered_client表中,注册客户端的重定向地址改为yaml中配置的地址,即:http://127.0.0.1:8082/login/oauth2/code/test-client

如果不使用此地址,授权服务无法将授权码自动返回给客户端

数据库改重定向地址

为了展示新的效果,清空oauth2_authorizationoauth2_authorization_consent两张表的数据

客户端测试

先启动redis,然后是授权服务,最后启动资源服务与客户端。

浏览器发起如下请求,来请求资源:

http://127.0.0.1:8082/getResource/read

127.0.0.1:8082/getResource/read是客户端向资源服务请求资源的地址,发起请求后,因为没有权限,会被客户端过滤器拦截(第一次请求还没有保存认证信息在上下文中)。

拦截后,客户端会向授权服务发起授权码请求,然后授权服务会要求用户登录授权,所以返回的是127.0.0.1:8080/login的登录页面:

登录页123

进行登录后,来到授权页面:

123

下面是图中授权页面的地址栏信息,包含客户端id、权限范围、state、重定向地址等参数:

http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=test-client&scope=read%20openid%20profile%20write&state=M43A6Pvs7ce-a98FigCQSsvPWkp4Rhs3bgr9xQLdKsE%3D&redirect_uri=http://127.0.0.1:8082/login/oauth2/code/test-client&nonce=Y3U-NfewZBtI6P24GtRRwHuzvUx-3ZfVGh6a_jh9Rys

然后勾选三个权限,并点击submit提交。

提交后,会直接跳转到资源。授权码的获取、交换令牌、携带令牌访问资源的过程,已经由框架为我们自动实现

读资源


在发起另一个资源的访问请求:

http://127.0.0.1:8082/getResource/write

因为已经登录授权过,所以第二个资源会直接返回:

写资源

通过请求测试可以发现,进行登录授权后,浏览器直接访问到了资源,而无需再手动进行授权码及token部分的操作,实现了客户端自动获取资源的效果。

相关文章:

Spring Boot OAuth2.0应用

本文展示Spring Boot中&#xff0c;新版本OAuth2.0的简单实现&#xff0c;版本信息&#xff1a; spring-boot 2.7.10 spring-security-oauth2-authorization-server 0.4.0 spring-security-oauth2-client 5.7.7 spring-boot-starter-oauth2-resource-server 2.7.10展示三个服务…...

Java | Leetcode Java题解之第363题矩形区域不超过K的最大数值和

题目&#xff1a; 题解&#xff1a; class Solution {public int maxSumSubmatrix(int[][] matrix, int k) {int ans Integer.MIN_VALUE;int m matrix.length, n matrix[0].length;for (int i 0; i < m; i) { // 枚举上边界int[] sum new int[n];for (int j i; j <…...

AI作画提示词(Prompts)工程:技巧与最佳实践

在人工智能领域&#xff0c;AI作画已成为一个令人兴奋的创新点&#xff0c;它结合了艺术与科技&#xff0c;创造出令人惊叹的视觉作品。本文将探讨在使用AI作画时的提示词工程&#xff0c;提供技巧与最佳实践。 理解AI作画 AI作画通常依赖于深度学习模型&#xff0c;尤其是生成…...

leetcode滑动窗口问题

想成功先发疯&#xff0c;不顾一切向前冲。 第一种 定长滑动窗口 . - 力扣&#xff08;LeetCode&#xff09;1456.定长子串中的元音的最大数目. - 力扣&#xff08;LeetCode&#xff09; No.1 定长滑窗套路 我总结成三步&#xff1a;入-更新-出。 1. 入&#xff1a;下标为…...

QT 控件使用案例

常用控件 表单 按钮 Push Button 命令按钮。Tool Button&#xff1a;工具按钮。Radio Button&#xff1a;单选按钮。Check Box&#xff1a;复选框按钮。Command Link Button&#xff1a;命令链接按钮。Dialog Button Box&#xff1a;按钮盒。 容器组控件(Containers) Group Box…...

【MySQL 10】表的内外连接 (带思维导图)

文章目录 &#x1f308; 一、内连接⭐ 0. 准备工作⭐ 1. 隐式内连接⭐ 2. 显式内连接 &#x1f308; 二、外连接⭐ 0. 准备工作⭐ 1. 左外连接⭐ 2. 右外连接 &#x1f308; 一、内连接 内连接实际上就是利用 where 子句对两张表形成的笛卡儿积进行筛选&#xff0c;之前所有的…...

【C语言】:与文件通信

1.文件是什么&#xff1f; 文件通常是在磁盘或固态硬盘上的一段已命名的存储区。C语言把文件看成一系列连续的字节&#xff0c;每个字节都能被单独的读取。这与UNIX环境中&#xff08;C的 发源地&#xff09;的文件结构相对应。由于其他环境中可能无法完全对应这个模型&#x…...

HTTPS通讯全过程

HTTPS通讯全过程 不得不说&#xff0c;https比http通讯更加复杂惹。在第一次接触https代码的时候&#xff0c;不知道为什么要用用证书&#xff0c;公钥是什么&#xff1f;私钥是什么&#xff1f;他们作用是什么&#xff1f;非对称加密和对称加密是啥&#xff1f;天&#xff0c;…...

建筑物规则化(实现) --- 特征边分组、重构、直角化

规则化建筑物 一、摘 要 建筑物多边形在地图综合中的两类处理模型:化简与直角化。 建筑物矢量数据来源广泛&#xff0c;在数据获取过程中&#xff0c;受GPS精确度、遥感影像分辨率或人为因素的影响&#xff0c;数据往往存在不同程度的误差。其中&#xff0c;图像分割、深度学习…...

pytorch的优化

在pytorch中&#xff0c;tensor是基于numpy与array的。内存共享。 在pythorch中&#xff0c;自定义层是继承nn.Module。将层与模型看成是模块&#xff0c;层与模型堪称模块&#xff0c;两者之间没有明确界限&#xff0c;定义方式与定义模型一样_init_与forward。 1、先定义全…...

React 入门第一天:从Vue到React的初体验

作为一名合格的前端工程师&#xff0c;怎么能只会Vue呢&#xff1f;学习React不仅是一场新技术的探索&#xff0c;更是对前端开发思维的一次重新审视。在这里&#xff0c;我将分享学习React的心得&#xff0c;希望能帮助那些和我一样从Vue转向React的开发者。 1. 为什么选择Re…...

Golang | Leetcode Golang题解之第357题统计各位数字都不同的数字个数

题目&#xff1a; 题解&#xff1a; func countNumbersWithUniqueDigits(n int) int {if n 0 {return 1}if n 1 {return 10}ans, cur : 10, 9for i : 0; i < n-1; i {cur * 9 - ians cur}return ans }...

【Linux】 gdb-调试器初入门(简单版使用)

&#x1f525;系列文章&#xff1a;《Linux入门》 目录 一、背景 二、什么是GDB &#x1f337;定义 &#x1f337;GDB调试工具---提供的帮助 三、GDB的安装教程-Ubuntu &#x1f337;gdb的安装 四、哪类程序可被调试 &#x1f337;程序的发布方式 &#x1f337;Debug版…...

Spring 的事务支持

文章目录 1、Spring如何管理事务2、编程式事务1_基本用法2_创建TransactionTemplate实例3_TransactionTemplate的内部结构4_总结 3、声明式事务1_使用Transactional注解2_事务的传播行为3_配置4_总结 1、Spring如何管理事务 Spring为事务管理提供了一致的编程模板&#xff0c;…...

基于STM32开发的智能家居照明控制系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化传感器数据采集显示与控制逻辑Wi-Fi通信应用场景 家庭智能照明办公室节能照明控制常见问题及解决方案 常见问题解决方案结论 1. 引言 智能家居照明控制系统通过集成光照传感器、继…...

程序员的底层思维~张建飞

前言 ◆ 成人学习的目的不是获取更多的信息量&#xff0c;而是学习更好的思维模型。 ◆ 好的思维能力是可以被复制和迁移的&#xff0c;它应该是普适的&#xff0c;而不应该有行业的界限。 第一部分 基础思维能力 ◆ 因为语言的抽象性&#xff0c;我在团队中会要求大家使用通用…...

美股收涨,半导体板块领涨;苹果iPhone出货预测上调

市场概况 在昨夜的交易中&#xff0c;美股三大股指全线收涨。道琼斯工业平均指数上涨1.39%&#xff0c;纳斯达克综合指数上涨2.34%&#xff0c;标准普尔500指数上涨1.61%。值得注意的是&#xff0c;英伟达股票涨幅近4%&#xff0c;推动了科技股的整体表现。美国十年期国债收益…...

[学习笔记]在不同项目中切换Node.js版本

文章目录 使用 Node Version Manager (NVM)安装 NVM使用 NVM 安装和切换 Node.js 版本为项目指定 Node.js 版本 使用环境变量指定 Node.js安装多个版本的 Node.js设置环境变量验证配置使用 npm 脚本切换 在开发中&#xff0c;可能会遇到不同的Vue项目需要不同的Node.js&#xf…...

SOL项目开发代币DApp的基本要求、模式创建与海外宣发策略

Solana&#xff08;SOL&#xff09;作为一个高性能区块链平台&#xff0c;以其快速的交易速度和低交易成本吸引了大量开发者和投资者。基于Solana开发的去中心化应用程序&#xff08;DApp&#xff09;和代币项目正逐步成为区块链领域的重要组成部分。要成功开发并推广一个SOL项…...

如何在 FastReport .NET 中构建和安装 Postgres 插件

FastReport .NET 是一款全功能的Windows Forms、ASP.NET和MVC报表分析解决方案。 功能非常丰富&#xff0c;功能广泛。今天我们将介绍如何使用报表设计器的 FastReport 插件连接数据库。 FastReport .NET 是适用于.NET Core 3&#xff0c;ASP.NET&#xff0c;MVC和Windows窗体…...

JVM指令重排序

文章目录 什么是指令重排序编译器优化JIT 编译优化处理器优化重排序数据依赖性 硬件层的内存屏障指令重排的代码验证好处减少管道阻塞提高缓存利用率利用并行执行单元性能提升更好地利用硬件资源 问题内存可见性问题编程复杂性增加调试困难 解决方案&#xff1a;Java内存模型&a…...

改造字典关键字:

怎样把第一个关键字的值都 加到所有关键字上&#xff1f; {type: 7, typenum: , typemon: } 我们可以使用字典的keys()方法来获取所有的关键字&#xff0c;然后通过遍历字典的方式将第一个关键字的值添加到其他关键字的名称上。以下是一个示例代码&#xff1a; data {type: …...

Neo4j 图数据库入门

图形数据库存储节点和关系&#xff0c;而不是表或文档。数据的存储方式就像你在白板上勾画想法一样。您的数据存储不受预定义模型的限制&#xff0c;允许以非常灵活的方式考虑和使用它。 一、核心概念&#xff1a;属性图形模型 Neo4j使用属性图数据库模型。图数据结构由节点(离…...

linux 磁盘满了,程序运行失败,如何处理?df -h

场景&#xff1a;紧急呼救&#xff0c;上传图片失败了。我一脸懵&#xff0c;服务器这是又咋地了&#xff0c;别邪乎姐姐&#xff0c;姐姐胆子小啊。 一、寻找问题原因 1、OSS出问题了&#xff1f; 然后我尝试了 IOS 的APP是没问题的&#xff0c;Android提示上传失败&#xf…...

Python编码系列—前端后浪:Python前后端分离开发实战指南

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…...

Docker学习之路【五】了解数据卷

定义与特性 Docker数据卷是一个特殊目录&#xff0c;&#xff0c;用于实现容器间数据的持久化和共享。数据卷存在于宿主机上&#xff0c;可以被一个或多个容器使用。它独立于容器的生命周期&#xff0c;意味着即使容器被删除&#xff0c;数据卷中的数据也会保留。数据卷的主要…...

matlab如何设置产生的随机数一致

在MATLAB中&#xff0c;确保产生的随机数序列一致&#xff0c;通常需要使用随机数生成器的种子&#xff08;seed&#xff09;。通过设置相同的种子值&#xff0c;可以确保在每次运行代码时&#xff0c;随机数生成器从相同的初始状态开始&#xff0c;从而生成相同的随机数序列。…...

ansible --------拓展

编辑 hosts 配置文件 [rootmo ~]# vim /etc/ansible/hosts # 创建目录 [rootmo ~]# mkdir /etc/ansible/playbook # 编辑配置文件 [rootmo ~]# vim /etc/ansible/playbook/nginx.yml # 执行测试 [rootmo ~]# ansible-playbook /etc/ansible/playbook/nginx.yml roles 修…...

gazebo下使用Fast-planner配置(包含mpc局部规划+控制Gazebo小车以及FastPlanner配置)

源码链接&#xff1a; https://github.com/USE-jx/NMPC_CASADI_CPP?tabreadme-ov-file #这是NMPC的 里面有Fast-Planner&#xff0c;但编译可能缺少东西&#xff0c;所以再放一个Fast-Planner的&#xff0c;可以装装缺少的库 https://github.com/HKUST-Aerial-Robotics/Fast-P…...

Python核心编程--Python要点总结

Python 核心编程包括了一些关键的要点&#xff0c;理解这些要点对于掌握 Python 至关重要。以下是 Python 核心编程的一些要点&#xff1a; 1. 数据类型与数据结构 基本数据类型: int, float, str, bool容器类型: list, tuple, set, dict不可变类型与可变类型: tuple 是不可变…...

网站开发怎么自动获取位置/常见的网络营销模式

目录 一、scala运算符本质 二、算术运算符 三、逻辑运算符 四、关系运算符 注&#xff1a;scala 与equals区别 五、赋值运算符 六、位运算符 一、scala运算符本质 在Scala中其实是没有运算符的&#xff0c;所有运算符都是方法。 1&#xff09;当调用对象的方法时&#…...

做设计做网站/搜索引擎关键词优化

用与改变root目录可以用pivot_root和chroot命令,但是这两者之间是有区别的pivot_root :直接将整个文件系统的root目录改为指定目录chroot :对于运行的指令以目标目录作为root目录,只影响单个进程.exec :用于结束当前执行的进程并继承其上下文,开始一个新的进程.PID不会改变. 和…...

合作网站开发/业务推广方式

起步 首先你需要在服务器安装宝塔服务&#xff0c;通过宝塔下载软件。 下载软件 去宝塔的软件商店下载PM2管理器和MongoDB数据库。 去你的服务器把你自己的后端端口放行一下&#xff0c;不然运行无法请求到 我这里设置的是3000端口&#xff0c;所以需要把3000端口加入到你…...

如何利用网站做产品推广/ip切换工具

1959年Shell发明&#xff0c;第一个突破O(n2)的排序算法&#xff0c;是简单插入排序的改进版。它与插入排序的不同之处在于&#xff0c;它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。 排序思路: 1.希尔排序可以理解为插入排序的升级版, 先将待排序数组按照指定步长划…...

怎么上传自己的做网站/seo助理

Java 导入 Java 导出 上传 前提准备2个Jar包&#xff1a; commons-fileupload-x.x.x.jar commons-io-x.x.x.jar 准备上传页面同步上传&#xff1a;<% page language"java" pageEncoding"UTF-8"%> <!DOCTYPE HTML> <html><head><…...

南京营销型网站/百度指数分析平台

解决org.apache.commons.lang.xwork.StringUtils异常今夜&#xff0c;晴&#xff0c;时间&#xff0c;凌晨两点本码农在敲代码时遇到一个问题&#xff0c;就是页面用Ajax传输json数据到后台时&#xff0c;Struts框架使用json-default&#xff0c;在调用模型后返回到页面时&…...