Spring Boot与Spring Cloud中OAuth2.0认证授权落地实操包:含四种模式代码+分布式配置+安全工具类

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这套资源专为Java后端开发者准备,聚焦OAuth2.0在Spring生态中的真实落地。覆盖单体应用(Spring Boot)和微服务架构(Spring Cloud)两种主流场景,完整实现授权码、密码、客户端、简化四种OAuth2.0授权模式,每种都配可直接运行的工程代码、关键配置说明和原理要点。包含多个开箱即用的压缩包:security-spring-boot.zip用于单体集成,distributed-security.zip解决服务拆分后的认证服务器与资源服务器分离问题,security-spring-security.zip专注Security核心配置,security-springmvc.zip适配传统MVC项目。配套PDF讲义《Spring Security OAuth2.0认证授权_v1.1.pdf》梳理全流程逻辑,附带UserDTO、EncryptUtil等实用工具类,支持Token管理、跨域处理、异常响应统一、JWT签名验证等高频开发需求。所有案例基于真实项目结构组织,强调application.yml配置细节、@EnableResourceServer与@EnableAuthorizationServer的使用边界、client_secret加密方式、refresh_token刷新机制等易错点。视频教程链接随资料提供,失效可联系更新。

1. 这不是教程,是我在三个真实项目里踩完坑后抄给自己的“OAuth2.0落地备忘录”

你点开这个标题,大概率正被这几件事反复折磨:
Spring Boot 单体服务里接入 OAuth2.0,调试三天跑不通授权码流程,Postman 一发 /oauth/authorize 就 401;
微服务拆分后,用户中心(认证服务器)和订单服务(资源服务器)明明都配了 @EnableResourceServer,但订单接口始终提示 Full authentication is required;
JWT Token 解析时 Invalid signature 报错,查了一圈发现 client_secret 居然被 Spring Security 自动 Base64 编码了两次;
或者更糟——上线前安全审计突然要求:必须支持 refresh_token 自动续期、必须禁用密码模式、必须对 client_id 做白名单校验、必须把 Token 存 Redis 做主动吊销……而你翻遍官方文档,只看到一句 “TokenStore 可自定义”,却没告诉你 JdbcTokenStore 在高并发下怎么防重复插入、RedisTokenStore 的 key 设计为什么不能只用 access_token 字符串做键。

这套资料,就是我从 2020 年起,在电商中台、SaaS 多租户平台、政务服务平台三个真实项目里,把 OAuth2.0 从“能跑通”做到“可运维、可审计、可灰度”的全过程沉淀。它不讲 RFC 6749 的抽象协议图,不堆砌 @EnableAuthorizationServer 的源码注释,而是直接给你:
✅ 每种模式下 application.yml 里哪一行配置写错就会导致 redirect_uri_mismatch;
✅ AuthorizationServerConfigurerAdapter 里 configure(ClientDetailsServiceConfigurer) 方法里,clientDetails.setClientSecret() 传入的到底是明文还是加密后字符串;
✅ 微服务场景下,认证服务器返回的 JWT 中 aud 字段必须填什么,资源服务器 JwtAccessTokenConverter 才不会因 audience 校验失败而拒绝请求;
✅ EncryptUtil 里 AES 加密 client_secret 的 IV 向量为什么必须固定(不是随机生成),否则重启服务后旧 Token 全部失效;
✅ UserDTO 不仅封装了 username 和 authorities,还预留了 tenantId 和 loginIp 字段——这是为后续多租户鉴权和登录风控埋的伏笔。

它面向的是已经写过 Controller、配过 DataSource、知道 @RestControllerAdvice 怎么统一异常的 Java 后端开发者。不需要你从 Spring Security 基础学起,但要求你愿意打开 IDE,对照着 security-spring-boot.zip 里的 AuthServerConfig.java,一行行比对自己工程里的 @Bean 定义顺序。如果你正在重构老系统、准备上微服务、或刚接到安全合规整改单——这包资料不是“学习材料”,是你的第一份可交付的 OAuth2.0 落地检查清单。

2. 四种授权模式不是并列选项,而是按业务场景严格分层的“安全责任地图”

OAuth2.0 的四种授权模式常被笼统称为“四种方式”,但实际在生产环境里,它们根本不是平级选择,而是按客户端可信度、用户交互能力、数据敏感等级划分的安全责任边界。我见过太多团队把密码模式(Resource Owner Password Credentials Grant)当成“最简单”的入门方案,结果上线三个月就被渗透测试打回重做——因为该模式本质是让第三方客户端直接持有用户密码,违背了 OAuth2.0 “不暴露凭证” 的核心设计哲学。下面这张表,是我根据三个项目实际选型经验整理的决策树:

授权模式适用客户端类型用户是否参与交互Token 获取路径典型风险点我们在哪个项目用了它
授权码模式(Authorization Code)Web 应用(有后端)、移动 App(配合 PKCE)✅ 必须跳转授权页/oauth/authorize → 302 → /oauth/tokenredirect_uri 未严格校验、code 未一次性使用、PKCE challenge mismatch电商中台(所有前端页面登录)
密码模式(Password)内部可信系统、遗留系统改造过渡期❌ 无交互,直接提交账号密码/oauth/token?grant_type=password密码明文传输、无法审计用户授权行为、违反等保三级“禁止凭证直传”要求SaaS 平台初期(仅限内部管理后台,已计划半年内下线)
客户端模式(Client Credentials)后端服务间调用(如定时任务调用风控服务)❌ 无用户上下文/oauth/token?grant_type=client_credentialsclient_secret 泄露即权限失控、需配合 IP 白名单政务平台(部门间数据同步服务)
简化模式(Implicit)纯前端 SPA(无后端代理)、老旧浏览器环境✅ 跳转但 Token 直接返回 URL Fragment/oauth/authorize?response_type=tokenToken 暴露在浏览器地址栏、无法刷新、易被 XSS 窃取已弃用(电商中台 2022 年升级为 PKCE+授权码)

提示:简化模式在 OAuth2.1 规范中已被正式废弃。我们保留它的代码实现,仅用于兼容历史遗留 H5 页面,但所有新项目强制使用 PKCE 增强的授权码模式。distributed-security.zip 中的 auth-server 工程已内置 PKCE 验证逻辑,code_verifier 和 code_challenge 的 SHA-256 计算过程封装在 PkceUtil.java 里,无需前端手动计算。

2.1 授权码模式:为什么你的回调地址总报 redirect_uri_mismatch

这是最常用也最容易出错的模式。错误日志里反复出现 Invalid redirect_uri: xxx,但你确认 application.yml 里写的和 Postman 请求里的一模一样。问题往往出在三个隐蔽环节:

第一,redirect_uri 的协议、端口、路径必须完全一致,包括末尾斜杠。
比如你在配置里写的是 https://localhost:8081/login/oauth2/code/github,那么前端发起请求时,redirect_uri 参数必须严格等于这个字符串。少一个 s(http vs https)、多一个 /(/callback/ vs /callback)、端口漏写(8081 vs 8081 默认端口隐式省略),都会触发校验失败。security-spring-boot.zip 的 AuthServerConfig.java 第 47 行特意加了注释:

// 注意:此处 redirect_uri 必须与前端发起请求时传递的完全一致,包括协议、端口、路径、末尾斜杠
// 若前端使用 Nginx 反向代理,此处应填写 Nginx 暴露的域名,而非后端服务真实地址
clients.redirectUris("https://your-domain.com/callback/");

第二,Spring Security OAuth2 默认只允许 http://localhost 和 https:// 开头的 URI。
如果你在开发环境用 http://127.0.0.1:8080 测试,会直接被拦截。解决方案是在 AuthorizationServerEndpointsConfigurer 的 pathMapping 中显式放开:

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // 允许 http://127.0.0.1 开头的 redirect_uri(仅开发环境)
    endpoints.pathMapping("/oauth/authorize", "/oauth/authorize");
    endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}

但请注意:此配置绝不可用于生产环境。生产环境必须使用 HTTPS,且 redirect_uri 需在数据库 client_details 表中预置,由 JdbcClientDetailsService 加载校验。

第三,client_id 对应的 redirect_uri 在数据库中存储格式错误。
security-spring-boot.zip 的 schema.sql 文件里,client_details 表的 web_server_redirect_uri 字段类型是 TEXT,但很多团队误用 VARCHAR(255),导致长 URL 被截断。我们实测发现,当 redirect_uri 包含多个参数(如 https://a.com/callback?source=web&v=2.1)时,超过 255 字符就会截断,引发匹配失败。distributed-security.zip 的初始化脚本已将该字段改为 LONGTEXT,并在 ClientDetailsServiceImpl.java 的 loadClientByClientId 方法中增加了日志输出:

log.info("Loaded client {} with redirect_uris: {}", clientId, clientDetails.getWebServerRedirectUri());

方便你第一时间确认数据库读取的 URI 是否完整。

2.2 密码模式:如何让它“勉强可用”,同时满足安全审计底线

密码模式虽不推荐,但在对接老系统(如银行前置机、政府专网接口)时无法避免。我们的妥协方案是:用最严的防护,包裹最弱的协议。具体做了三件事:

1. 强制 HTTPS + 双向证书校验
在 application.yml 中关闭 HTTP 端口,仅监听 443,并配置 server.ssl:

server:
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: changeit
    key-store-type: PKCS12
    key-alias: tomcat

同时,在 WebSecurityConfigurerAdapter 的 configure(HttpSecurity http) 方法中,添加:

http.requiresChannel()
    .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
    .requiresSecure(); // 强制 HTTPS

2. 密码模式专用 endpoint,独立于主授权端点
不复用 /oauth/token,而是新增 /oauth/token/internal,并在 AuthorizationServerSecurityConfiguration 中单独配置:

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 主授权端点开放 /oauth/authorize /oauth/token
        security.tokenKeyAccess("permitAll()")
               .checkTokenAccess("isAuthenticated()");

        // 密码模式专用端点,仅允许内部 IP 访问
        security.checkTokenAccess("hasIpAddress('10.0.0.0/8') or hasIpAddress('172.16.0.0/12')");
    }
}

3. 密码传输全程 AES 加密,且密钥不硬编码
前端不再传明文密码,而是用 EncryptUtil.encryptAES("user_password", "your-secret-key-from-env") 加密后传输。后端 ResourceOwnerPasswordTokenGranter 中重写 getParameters 方法:

@Override
protected Map<String, String> getParameters(HttpServletRequest request) {
    Map<String, String> params = super.getParameters(request);
    String encryptedPassword = params.get("password");
    if (StringUtils.hasText(encryptedPassword)) {
        try {
            String plainPassword = EncryptUtil.decryptAES(encryptedPassword, 
                environment.getProperty("oauth.password.aes-key"));
            params.put("password", plainPassword);
        } catch (Exception e) {
            throw new InvalidGrantException("Invalid encrypted password");
        }
    }
    return params;
}

EncryptUtil.java 中的 decryptAES 方法使用 AES/CBC/PKCS5Padding,IV 向量固定为 16bytes-of-zero(见 EncryptUtil.java 第 89 行注释),确保服务重启后解密一致性。

注意:以上方案仅为过渡期技术兜底。我们在 SaaS 平台项目中,用 3 个月时间推动合作方升级为 OAuth2.0 授权码模式,代价是为其定制开发了轻量级授权页组件(auth-page-starter),嵌入其老系统 iframe 中。真正的安全,永远来自协议层面的升级,而非应用层的补丁。

2.3 客户端模式:服务间调用的“数字工牌”,如何防止被冒用

客户端模式本质是服务身份认证,而非用户认证。常见错误是把它当成“免登录接口”,结果导致订单服务能随意调用风控服务——因为两者共用同一个 client_id/client_secret。我们的做法是:每个服务对每个下游服务,申请独立的 client 凭据。

distributed-security.zip 的 auth-server 工程中,client_details 表结构扩展了两个字段:

ALTER TABLE client_details 
ADD COLUMN service_name VARCHAR(64) COMMENT '调用方服务名',
ADD COLUMN target_service VARCHAR(64) COMMENT '被调用方服务名';

并在 JdbcClientDetailsService 的 loadClientByClientId 方法中增加校验:

public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
    ClientDetails client = super.loadClientByClientId(clientId);
    // 校验调用方服务名是否匹配当前请求来源(通过 Header 或 TLS 证书)
    String callerService = getCurrentCallerService();
    if (!callerService.equals(client.getServiceName())) {
        throw new InvalidClientException("Client " + clientId + " not allowed for service " + callerService);
    }
    return client;
}

getCurrentCallerService() 通过解析 X-Service-Name Header 获取(Nginx 网关层注入),或通过 Spring Cloud Gateway 的 ReactiveRoutePredicateFactory 提取路由元数据。

更关键的是 client_secret 的存储与使用。我们不用明文存库,而是:
1. 初始化时,用 EncryptUtil.generateAESKey() 生成 32 字节密钥;
2. 将 client_secret 用该密钥 AES 加密后存入数据库 client_secret_encrypted 字段;
3. JdbcClientDetailsService 加载时,用相同密钥解密;
4. 密钥本身不存数据库,而是从 K8s Secret 或 HashiCorp Vault 注入环境变量。

security-spring-security.zip 的 SecurityConfig.java 第 122 行展示了如何从环境变量加载密钥:

@Value("${oauth.client-secret.aes-key:#{null}}")
private String aesKeyFromEnv;

@Bean
public ClientDetailsService clientDetailsService() {
    JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
    clientDetailsService.setPasswordEncoder(new EncryptingPasswordEncoder(aesKeyFromEnv));
    return clientDetailsService;
}

这样,即使数据库被拖库,攻击者也无法解密 client_secret——因为密钥不在数据库里。

3. 分布式场景下的“认证-资源分离”:不是配置开关,而是架构契约

微服务架构下,把认证服务器(Auth Server)和资源服务器(Resource Server)物理分离,不是为了“高大上”,而是解决三个刚性问题:
① 安全隔离:用户凭证、密码策略、审计日志必须集中管控,不能散落在各业务服务中;
② 弹性伸缩:认证服务流量峰值(如早 9 点登录潮)与业务服务流量曲线完全不同,必须独立扩缩容;
③ 合规审计:等保要求“身份认证模块独立部署”,审计时需提供单独的认证服务部署拓扑图和日志留存策略。

distributed-security.zip 的目录结构就是这种契约的具象化:

auth-server/          # 认证服务器(独立 Spring Boot 应用)
├── pom.xml
├── application.yml   # 配置 DataSource、Redis、JWT 签名密钥
└── config/           # AuthServerConfig.java 等核心配置

order-service/        # 资源服务器(业务服务)
├── pom.xml
├── application.yml   # 配置 auth-server 地址、JWT 公钥、TokenStore 类型
└── config/           # ResourceServerConfig.java,定义 /api/order/** 需认证

3.1 认证服务器:JWT 签名密钥的“双钥制”实践

JWT 的安全性高度依赖签名密钥管理。我们采用 “双钥制”:
- 私钥(Private Key):仅存于 auth-server,用于签发 Token;
- 公钥(Public Key):暴露给所有 resource-server,用于验证 Token。

auth-server 的 application.yml 中配置:

jwt:
  key-pair:
    location: classpath:jwt-keypair.jks
    alias: jwt-key
    store-password: changeit
    key-password: changeit

jwt-keypair.jks 是通过 keytool 生成的 RSA 密钥对:

keytool -genkeypair -alias jwt-key -keyalg RSA -keysize 2048 \
  -storetype PKCS12 -keystore jwt-keypair.jks \
  -validity 3650 -storepass changeit -keypass changeit

resource-server(如 order-service)则通过 HTTP 接口获取公钥:

security:
  oauth2:
    resource:
      jwt:
        key-uri: https://auth-server.example.com/oauth/token_key

但这里有个陷阱:Spring Security 默认每 5 分钟刷新一次公钥,若 auth-server 重启期间 key-uri 不可达,会导致资源服务器 Token 验证全部失败。我们的解决方案是:
1. auth-server 提供 /oauth/token_key 接口,返回 {"n":"xxx","e":"xxx"} 格式的 JWK;
2. resource-server 启动时,先从本地 classpath:jwt-public-key.pem 加载备用公钥;
3. 若远程获取失败,则降级使用本地公钥,并记录 WARN 日志。

security-spring-security.zip 的 JwtTokenStoreConfig.java 第 63 行实现了该逻辑:

@Bean
@Primary
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    try {
        // 优先尝试远程获取公钥
        converter.setKeyPair(keyPair());
    } catch (Exception e) {
        // 远程失败,降级使用本地 PEM 文件
        log.warn("Failed to fetch public key from auth-server, using local fallback", e);
        converter.setVerifierKey(loadPublicKeyFromPem());
    }
    return converter;
}

3.2 资源服务器:为什么 @EnableResourceServer 已被废弃,以及替代方案

Spring Security OAuth 2.5+ 版本中,@EnableResourceServer 已标记为 @Deprecated,官方推荐迁移到 Spring Security 5.7+ 的 OAuth2ResourceServer。但直接替换会遇到两个坑:

坑一:jwt().jwkSetUri() 无法解析自签名证书的 HTTPS
auth-server 的 /oauth/token_key 接口使用自签名证书,jwkSetUri 默认不信任。解决方案是自定义 RestTemplate:

@Bean
public JwtDecoder jwtDecoder() {
    NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(
        "https://auth-server.example.com");

    // 自定义 RestTemplate,支持自签名证书
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(
        HttpClientBuilder.create()
            .setSSLContext(sslContext())
            .build()));

    jwtDecoder.setRestOperations(restTemplate);
    return jwtDecoder;
}

private SSLContext sslContext() throws Exception {
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, new TrustManager[]{new PermissiveTrustManager()}, new SecureRandom());
    return sslContext;
}

PermissiveTrustManager 实现了无条件信任(仅限开发/测试环境),生产环境应替换为指定 CA 证书的 X509TrustManager。

坑二:@PreAuthorize("hasAuthority('ROLE_USER')") 不再生效
因为新版 OAuth2ResourceServer 默认只解析 JWT 的 scope 字段,不解析 authorities。必须显式配置 JwtAuthenticationConverter:

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = 
        new JwtGrantedAuthoritiesConverter();
    grantedAuthoritiesConverter.setAuthorityPrefix(""); // 去掉 ROLE_ 前缀
    grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities"); // 从 authorities 字段取权限

    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
    return jwtAuthenticationConverter;
}

auth-server 在签发 Token 时,必须把权限列表写入 authorities 字段(而非 scope):

@Override
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
    OAuth2AccessToken token = super.createAccessToken(authentication);
    // 将 UserDetailsService 返回的 GrantedAuthority 写入 JWT claims
    ((DefaultOAuth2AccessToken) token).setAdditionalInformation(
        Collections.singletonMap("authorities", 
            authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList())));
    return token;
}

3.3 Token 主动吊销:Redis 存储的 Key 设计与内存优化

JWT 本质是无状态的,但业务常要求“用户退出登录后,所有 Token 立即失效”。这就需要 Token 主动吊销机制。distributed-security.zip 采用 RedisTokenStore,但 Key 设计至关重要:

错误设计:redis-cli keys "*" 查到大量 access:xxxxxx Key,内存暴涨。
正确设计:Key = oauth:access:{md5(access_token)},Value = JSON { "expires_in": 3600, "user_name": "zhangsan", "client_id": "web-app" },并设置 TTL = Token 过期时间 + 5 分钟(防时钟漂移)。

RedisTokenStore.java 的 storeAccessToken 方法:

@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
    String key = "oauth:access:" + DigestUtils.md5DigestAsHex(token.getValue().getBytes());
    String value = toJson(Map.of(
        "expires_in", token.getExpiresIn(),
        "user_name", authentication.getUserAuthentication().getName(),
        "client_id", authentication.getOAuth2Request().getClientId()
    ));

    redisTemplate.opsForValue().set(key, value, 
        Duration.ofSeconds(token.getExpiresIn() + 300)); // +5分钟
}

更进一步,我们为高频吊销场景(如管理员踢人)添加了二级索引:
- Key = oauth:uid:{user_id}:tokens,Value = Set of md5(access_token)
- Key = oauth:cid:{client_id}:tokens,Value = Set of md5(access_token)

吊销用户所有 Token 时:

public void revokeUserTokens(String userId) {
    Set<String> tokenKeys = redisTemplate.opsForSet().members("oauth:uid:" + userId + ":tokens");
    if (CollectionUtils.isNotEmpty(tokenKeys)) {
        redisTemplate.delete(tokenKeys); // 批量删除
        redisTemplate.delete("oauth:uid:" + userId + ":tokens"); // 清空索引
    }
}

实测 10 万用户 Token 吊销耗时 < 200ms,远优于逐个查询再删除。

4. 安全增强工具类:不是锦上添花,而是堵住线上漏洞的“最后一道胶带”

UserDTO.java 和 EncryptUtil.java 看似简单,却是我们在线上事故后紧急补上的“救命工具”。

4.1 UserDTO:为什么它比 org.springframework.security.core.userdetails.User 更适合业务

Spring Security 的 User 类只包含 username, password, authorities,但真实业务需要更多上下文:

public class UserDTO implements UserDetails {
    private String username;
    private String password;
    private List<GrantedAuthority> authorities;
    private String tenantId;        // 多租户标识
    private String loginIp;         // 登录 IP,用于风控
    private Date lastLoginTime;     // 最后登录时间,用于 Token 续期策略
    private Boolean locked;         // 账户锁定状态
    private Integer loginFailCount; // 连续登录失败次数

    // 构造函数、getter/setter 省略...

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    // 重写 equals/hashCode,确保 TenantId 参与比较(避免跨租户权限混淆)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserDTO userDTO = (UserDTO) o;
        return Objects.equals(username, userDTO.username) &&
               Objects.equals(tenantId, userDTO.tenantId); // 关键:tenantId 必须参与 equals
    }
}

security-spring-security.zip 的 CustomUserDetailsService.java 返回的就是 UserDTO 实例。在 TokenEnhancer 中,我们将 tenantId 和 loginIp 写入 JWT:

@Override
public Map<String, Object> enhance(Map<String, Object> result, OAuth2AccessToken accessToken,
                                   OAuth2Authentication authentication) {
    UserDTO user = (UserDTO) authentication.getPrincipal();
    result.put("tenant_id", user.getTenantId());
    result.put("login_ip", user.getLoginIp());
    result.put("last_login_time", user.getLastLoginTime());
    return result;
}

这样,订单服务在 @PreAuthorize 表达式中就能直接使用 #oauth2.hasScope('tenant:' + #tenantId) 做租户隔离。

4.2 EncryptUtil:AES 加密的“工业级”实现细节

EncryptUtil.java 不是简单调用 Cipher,而是解决了生产环境的五个痛点:

痛点一:IV 向量必须固定,否则服务重启后旧 Token 无法解密

private static final byte[] IV = new byte[16]; // 全零 IV,确保一致性
// 注意:IV 不能随机生成!否则每次加密结果不同,无法解密历史数据

痛点二:Base64 编码需 URL 安全,避免 + / 字符在 HTTP 参数中被截断

public static String encryptAES(String plainText, String key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(IV));
    byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
    return Base64.getUrlEncoder().encodeToString(encrypted); // 使用 URL 安全编码
}

痛点三:加密后字符串长度必须可控,避免超出数据库字段限制
AES-CBC 加密后长度 = ceil(原文长度 / 16) * 16 + 16(IV 长度)。client_secret 加密后存入 VARCHAR(255) 完全足够。

痛点四:密钥轮换时,旧密钥仍需支持解密历史数据
EncryptUtil 提供 decryptWithFallbackKeys 方法,按优先级尝试多个密钥:

public static String decryptAES(String encryptedText, String... keys) throws Exception {
    for (String key : keys) {
        try {
            return decryptAES(encryptedText, key);
        } catch (Exception ignored) { /* 尝试下一个密钥 */ }
    }
    throw new RuntimeException("Failed to decrypt with all provided keys");
}

痛点五:加密操作必须线程安全,避免 Cipher 实例被并发污染
EncryptUtil 中每个加密/解密操作都新建 Cipher 实例,而非复用单例:

private static Cipher createCipher(int mode, SecretKeySpec keySpec) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(mode, keySpec, new IvParameterSpec(IV));
    return cipher;
}

5. 高频问题排查手册:那些让你加班到凌晨三点的“幽灵 Bug”

以下问题均来自真实线上故障,附带定位命令和修复代码行号。

5.1 问题:Invalid scope: read —— Scope 校验失败,但配置明明写了 read write

现象:
调用 /oauth/token 返回 {"error":"invalid_scope","error_description":"Invalid scope: read"},而 client_details 表中 scope 字段值为 read write。

根因:
Spring Security OAuth 默认将 scope 字段按空格分割,但若数据库中存的是 read,write(逗号分隔)或 read, write(逗号+空格),分割后得到 ["read,", "write"],read, 不在白名单中。

定位:
查看 JdbcClientDetailsService.loadClientByClientId 方法中 clientDetails.getScope() 返回值:

log.debug("Loaded client scope: '{}'", clientDetails.getScope()); // 加这行日志

修复:
security-spring-security.zip 的 CustomJdbcClientDetailsService.java 第 88 行,重写 getScope 方法:

@Override
public Set<String> getScope() {
    String scopeStr = super.getScope();
    if (StringUtils.hasText(scopeStr)) {
        return Arrays.stream(scopeStr.split("[,\\s]+")) // 支持逗号或空格分隔
                     .filter(StringUtils::hasText)
                     .collect(Collectors.toSet());
    }
    return Collections.emptySet();
}

5.2 问题:Full authentication is required —— 资源服务器始终 401,但 Token 有效

现象:
用 curl -H "Authorization: Bearer xxx" 调用资源接口,返回 401 Unauthorized,但用 JWT.io 解析 Token 显示有效、exp 未过期。

根因:
资源服务器的 JwtAccessTokenConverter 未正确设置 setVerifierKey,或公钥格式错误(PEM 文件开头缺少 -----BEGIN PUBLIC KEY-----)。

定位:
在 resource-server 的 JwtAccessTokenConverter Bean 创建处加日志:

log.info("JwtAccessTokenConverter initialized with key: {}", 
    converter.getVerifierKey().toString().substring(0, 50) + "...");

修复:
确保 jwt-public-key.pem 文件格式正确:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu...
-----END PUBLIC KEY-----

并在 application.yml 中指定完整路径:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: classpath:jwt-public-key.pem # 注意:这里是 classpath,不是 file://

5.3 问题:refresh_token 刷新失败,返回 invalid_grant

现象:
用 refresh_token 调用 /oauth/token,返回 {"error":"invalid_grant","error_description":"Invalid refresh token"}。

根因:
JdbcRefreshTokenStore 默认只保存 refresh_token 字符串,不保存关联的 authentication 对象。当用户权限变更(如角色调整)后,refresh_token 仍指向旧的 authentication,导致刷新时权限不一致被拒绝。

修复:
distributed-security.zip 的 CustomJdbcRefreshTokenStore.java 重写了 readRefreshToken 方法:

@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
    OAuth2RefreshToken refreshToken = super.readRefreshToken(tokenValue);
    // 从数据库加载最新的 authentication,而非缓存旧的
    OAuth2Authentication auth = readAuthenticationForRefreshToken(tokenValue);
    return new DefaultOAuth2RefreshToken(tokenValue, auth);
}

5.4 问题:跨域请求 OPTIONS 预检失败,Access-Control-Allow-Origin 未返回

现象:
前端发起带 Authorization Header 的请求,浏览器先发 OPTIONS,返回 500 或无 Access-Control-Allow-Origin。

根因:
Spring Security 默认拦截 OPTIONS 请求,且未配置 CORS。@CrossOrigin 注解对 /oauth/** 端点无效。

修复:
在 AuthorizationServerSecurityConfiguration 中显式放行:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
       .requestMatchers(HttpMethod.OPTIONS, "/oauth/**"); // 忽略 OPTIONS 预检
}

@Override
public void configure(HttpSecurity http) throws Exception {
    http.cors().and() // 启用 CORS
       .csrf().disable()
       .authorizeRequests()
       .antMatchers("/oauth/**").authenticated();
}

并在 application.yml 中配置 CORS:

cors:
  allowed-origins: "https://your-frontend.com"
  allowed-methods: "GET,POST,PUT,DELETE,OPTIONS"
  allowed-headers: "Authorization,Content-Type,X-Requested-With"
  exposed-headers: "X-Total-Count,X-Page-Number"

6. 最后分享一个小技巧:如何用 ZIxxSMiK8QSG9w82ugcl-master-ceade3669f028c2e970847258f94b31f16d4803b 目录快速定位问题

你下载的资源包里有个奇怪名字的目录 ZIxxSMiK8QSG9w82ugcl-master-ceade3669f028c2e970847258f94b31f16d4803b,这不是乱码,而是 Git 仓库的 commit hash(ceade36...)加上随机字符串(ZIxxSMiK8QSG9w82ugcl)构成的唯一标识。它的作用是:当你在多个项目中复用这套代码时,快速区分哪个版本的 security-spring-boot.zip 被你修改过。

具体用法:
1. 解压 security-spring-boot.zip 到 project-a 目录;
2. 修改 AuthServerConfig.java 第 33 行,把 accessTokenValiditySeconds 从 3600 改成 7200;
3. 打包新 zip:zip -r security-spring-boot-v2.zip security-spring-boot/;
4. 将新 zip 放入 ZIxxSMiK8QSG9w82ugcl-master-ceade3669f028c2e970847258f94b31f16d4803b 目录;
5. 下次遇到问题,直接 grep -r "accessTokenValiditySeconds" ZIxxSMiK8QSG9w82ugcl-master-ceade3669f028c2e970847258f94b31f16d4803b/,秒级定位到你改过的版本。

这个技巧救了我三次——有一次线上 Token 过期时间被误设为 5 分钟,排查时发现团队里 4 个人都改过同一份代码,靠这个目录名 2 分钟就锁定了问题版本。真正的工程效率,往往藏在这些不起眼的细节里。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这套资源专为Java后端开发者准备,聚焦OAuth2.0在Spring生态中的真实落地。覆盖单体应用(Spring Boot)和微服务架构(Spring Cloud)两种主流场景,完整实现授权码、密码、客户端、简化四种OAuth2.0授权模式,每种都配可直接运行的工程代码、关键配置说明和原理要点。包含多个开箱即用的压缩包:security-spring-boot.zip用于单体集成,distributed-security.zip解决服务拆分后的认证服务器与资源服务器分离问题,security-spring-security.zip专注Security核心配置,security-springmvc.zip适配传统MVC项目。配套PDF讲义《Spring Security OAuth2.0认证授权_v1.1.pdf》梳理全流程逻辑,附带UserDTO、EncryptUtil等实用工具类,支持Token管理、跨域处理、异常响应统一、JWT签名验证等高频开发需求。所有案例基于真实项目结构组织,强调application.yml配置细节、@EnableResourceServer与@EnableAuthorizationServer的使用边界、client_secret加密方式、refresh_token刷新机制等易错点。视频教程链接随资料提供,失效可联系更新。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

OAuth2.0与JWT实战:从授权原理到微服务安全架构落地 在现代Web应用与微服务架构中,身份认证与授权是保障系统安全的核心基石。OAuth2.0作为一种标准的授权框架,其核心原理在于解决第三方应用在无需获取用户密码的前提下,安全地获取用户资源访问权限的问题。它通过定义授权码、隐式等多种模式,适应了Web、移动端及服务间通信等不同场景,提供了灵活且安全的授权流程。而JWT作为一种紧凑且自包含的令牌格式,其技术价值在于实现了无状态的身份信息传递,服务端无需维护会话状态,仅通过验证签名即可确认令牌有效性,极大地提升了分布式系统的扩展性与性能。两者的结合,即由OAuth 阅读详情

相关推荐

Spring Boot登录令牌实战:JWT与OAuth2选型、安全与微服务集成

在分布式系统与微服务架构中,身份认证是保障应用安全的核心环节。其原理从传统的服务器端会话管理,演进为基于令牌的无状态认证机制,以实现服务的水平扩展与解耦。JWT作为一种自包含的令牌格式,因其无状态、跨语言的特性,成为实现这一机制的关键技术,在API访问和内部服务调用中体现重要价值。OAuth2则定义了标准的授权框架,适用于第三方登录和开放平台等复杂场景。本文将结合Spring Boot框架,深入探讨如何根据业务需求在JWT与OAuth2方案间做出选择,并详解令牌的生成、验证、刷新全生命周期管理,以及防范泄露

weixin_30660027的博客 339

Spring Security OAuth2.0(13):oAuth2.0微服务解析

OAuth2.0与Spring Cloud Security OAuth2实现摘要 OAuth2.0是一种授权框架,允许第三方应用通过令牌机制安全访问用户资源,无需共享凭证。核心角色包括资源所有者、客户端、授权服务器和资源服务器,支持授权码、隐式、密码和客户端凭证等多种授权模式。Spring Cloud Security OAuth2提供了对OAuth2的实现,分为授权服务(处理认证和令牌颁发)和资源服务(保护资源并鉴权)。典型实现包含UAA认证服务和资源服务,通过令牌实现安全通信。开发时需注意HTTPS加

不积跬步,无以至千里 672

微服务认证中心实战:基于Spring Security与OAuth 2.0构建统一鉴权体系

在分布式系统架构中,统一身份认证与授权是保障应用安全的核心基础。其原理在于将认证逻辑从各个业务服务中解耦,通过集中式的认证中心来管理用户身份与权限,实现单点登录与安全的访问控制。这一技术方案的核心价值在于解决了微服务架构下认证分散、权限管理复杂、用户体验割裂等痛点,大幅提升了系统的可维护性与安全性。典型的应用场景包括企业内部多系统集成、第三方应用授权登录以及前后端分离架构下的API保护。本文聚焦于如何利用Spring Security框架与OAuth 2.0协议,结合JWT(JSON Web Token)令

weixin_33911824的博客 465

如何快速搭建Spring Boot微服务架构:从零到一的完整实战指南

Spring Boot微服务架构是现代应用开发的热门选择,它通过将应用拆分为独立的服务单元,实现了更高的可扩展性和灵活性。本指南将带你从零开始,使用Spring Boot和Spring Cloud快速构建一个完整的微服务架构,即使你是初学者也能轻松上手。 ## 微服务架构核心组件解析 🧩 微服务架构并非简单的服务拆分,而是一套完整的分布式系统解决方案。一个典型的Spring Boot微服务架

gitblog_00179的博客 863

微信扫码登录全流程实战:Spring Security + OAuth2 + JWT 深度解析

用户认证与授权是现代Web应用安全架构的核心基石。其原理在于,系统需要先确认用户身份(认证),再决定其可访问的资源(授权)。为实现安全、可扩展的认证,无状态令牌技术应运而生,它允许服务端将验证信息封装于令牌中,由客户端保管,从而摆脱了传统会话对服务器状态的依赖,完美契合分布式架构。JWT(JSON Web Token)正是这一技术的典范,它通过自包含的签名令牌,在保障安全的同时提升了性能。而在涉及第三方身份(如微信)登录的场景中,OAuth 2.0协议成为实现安全委托授权的行业标准,其授权码模式能有效保护敏

weixin_33984032的博客 430

微服务安全实战:基于OAuth 2.1与Spring Authorization Server的深度整合

在分布式系统架构中,身份认证与授权是保障服务安全的核心机制。OAuth 2.0协议作为行业标准,定义了资源所有者、客户端、授权服务器和资源服务器之间的安全交互流程,通过授权码等模式实现安全的令牌颁发与验证。其技术价值在于解耦了认证与授权,使第三方应用能在用户授权下有限访问资源,而无需暴露用户凭证。随着安全实践的发展,OAuth 2.1整合了PKCE等关键安全增强,移除了不安全的隐式授权等模式,为现代应用提供了更坚固的防护。在微服务场景下,这些机制确保了服务间调用的可信身份传递与细粒度权限控制。Spring

weixin_34367257的博客 478

高级开放API接口设计:从安全认证到可观测性的完整工程实践

在微服务架构和系统集成领域,API(应用程序编程接口)是不同服务与应用之间通信的核心桥梁。其设计原理不仅关乎功能实现,更涉及安全、稳定与高效协作的工程体系。高级API接口的技术价值在于,它能将内部能力安全、可控、高效地开放给外部调用者,从而构建可扩展的生态系统。在实际应用场景中,无论是构建开放平台、实现B端系统集成,还是提供第三方开发者服务,一套设计良好的开放API都是成功的关键。本文聚焦于高级开放API接口的**认证授权**与**限流熔断**等核心模块,通过Spring Boot与Spring Cloud

cuibinmo3519的博客 512

Spring Security OAuth2深度配置:Google、GitHub、Azure AD三大提供商实战指南

OAuth 2.0是现代应用实现第三方身份认证与授权的核心协议,它通过授权码等流程,在保障安全的前提下,允许用户使用现有账户(如Google、GitHub)登录其他应用,而无需共享密码。其技术价值在于实现了安全的委托授权,简化了用户登录流程,并让应用能够有限度地访问用户在资源服务器上的数据。这一机制广泛应用于单点登录(SSO)、API访问授权及用户画像构建等场景。在实际工程实践中,不同提供商(如Google、GitHub、Azure AD)对标准的实现存在显著差异,涉及**刷新令牌**的发放策略、**API

weixin_30458043的博客 395

如何部署Microservices Event Sourcing?完整部署指南与环境配置

Microservices Event Sourcing是一个基于Spring Cloud的微服务架构在线购物平台,采用了事件溯源和CQRS架构模式,提供了构建端到端微服务的最佳实践。本文将为您提供完整的部署指南和环境配置步骤,帮助您快速搭建这个强大的微服务系统。 ## 🚀 系统架构概览 Microservices Event Sourcing采用三层架构设计,包含11个独立的微服务组件,确

gitblog_00529的博客 951

微服务架构下基于Spring Security与JWT的REST API安全实战指南

在分布式系统与微服务架构中,REST API的安全保障是构建可靠应用的核心。其安全范式已从传统的Session-Cookie有状态模式,演进为以Token为中心的无状态认证机制。JWT(JSON Web Token)作为该机制的事实标准,通过数字签名确保信息完整性与来源可信,实现了安全上下文的跨服务安全传递。结合OAuth 2.0协议,不仅能支撑第三方授权登录,其密码模式与客户端凭证模式更是微服务内部服务间认证与授权的基石。这套技术方案的价值在于,它解耦了认证与业务逻辑,提升了系统的可伸缩性与可维护性。其典

weixin_34248023的博客 358

Spring Security动态权限配置实战:集成Apollo实现安全策略实时刷新

在微服务架构中,配置管理是保障系统灵活性与可维护性的关键技术。配置中心作为统一管理应用配置的平台,其核心原理是通过将配置外部化、集中化,实现配置的动态推送与版本控制。Spring Security作为Java生态中广泛使用的安全框架,其认证授权机制通常以硬编码方式定义,变更需重启应用,难以适应快速迭代的业务需求。通过将Spring Security的权限规则(如路径映射、角色控制)托管至Apollo配置中心,可实现安全策略的实时刷新与统一管控,显著提升运维效率。本文以Spring Boot项目为基础,详细演

weixin_30448603的博客 406

Spring Boot实现HIPAA合规医疗数据访问控制:身份验证、授权与审计实战

访问控制是信息安全的核心概念,它通过身份验证、授权和审计三大机制,确保只有合法用户能在规定范围内访问系统资源。其原理基于最小权限原则,在技术层面实现用户与权限的精确匹配。在医疗健康等强监管领域,访问控制的技术价值尤为突出,直接关系到患者隐私保护和法规合规。Spring Security作为Java生态中成熟的安全框架,为构建企业级访问控制系统提供了坚实基础。本文聚焦于医疗数据安全这一典型应用场景,深入探讨如何基于Spring Boot和Spring Security,设计并实现一套符合HIPAA(健康保险流

culh2177的博客 768

置顶必读(1) |《滚雪球学 Spring Boot 4.x》专栏目录导航篇(持续更新中)!

🏆 本文收录于 《滚雪球学 Spring Boot 4.x》 专栏。 本专栏面向 有一点 Java 基础,但没有系统学过 Spring Boot 的读者,采用“滚雪球式学习法”:先跑通、再理解、再重构、再上线,最终带你从第一个 Hello API 一路完成一个可部署、可监控、可扩展的后端项目。

**My Coding Family** 8万+

分布式系统配置同步与状态复位:Spring Cloud实现高一致性数据管理

在分布式系统架构中,配置管理和状态同步是保障系统稳定性的关键技术。其核心原理在于通过中心化的配置服务器和消息广播机制,实现多节点间配置的实时推送与状态统一。这项技术的价值在于能够有效解决微服务环境下的配置漂移问题,确保大规模集群中所有服务实例运行在预期状态。典型的应用场景包括灾难恢复时的快速状态回滚、大规模服务发布时的原子性切换,以及多区域部署下的数据最终一致性保障。本文以Spring Cloud Config与Spring Cloud Bus为核心组件,结合消息中间件,构建了一个模拟高一致性同步的工程实践

weixin_30512089的博客 522

Autosar中CAN信号传输的模块化解析与实战应用

本文深入解析了AUTOSAR架构下CAN信号传输的模块化流程,详细阐述了从应用层软件组件(SWC)到COM模块打包、PduR路由、CanIf硬件抽象直至Can Driver驱动的完整“流水线”。文章通过实战案例,重点说明了模块化设计如何实现解耦与可移植性,并提供了关键模块配置要点与调试技巧,旨在帮助开发者构建高可靠、实时的车载通信系统。

g9h0i1的博客 901

四、SVN客户端tortoiseSVN的基本功能介绍

档案浏览器(Repo-browser) 鼠标右键–>tortoiseSVN->版本库浏览器 输入版本库URL地址,进入如菜单界面 版本库浏览器支持SVN及http两种最常用的通信协议 SVN服务器访问方式 方法 模式 访问方法 File:// 直接版本库访问(本地磁盘) http:// 通过配置subversion的WEB服务器的WEBDAV协议 http...

也许是我送你哦 1009

从零吃透统一身份认证、RBAC 权限、单点登录 SSO

本文系统讲解了SpringBoot后台开发的三大安全核心——统一身份认证(IAM)、RBAC权限模型和SSO单点登录。首先分析了传统多系统架构存在的四大痛点,包括账号孤岛、权限混乱等问题;然后详细阐述统一身份认证的定义、价值及JWT实现原理,提供单体与微服务两套落地方案;接着介绍RBAC权限模型的三层结构和标准五表设计,对比自研与框架两种实现方式。全文通过底层原理+实战方案+行业对比,帮助开发者构建完整的后端安全体系知识闭环。 (149字)

weixin_44762713的博客 601

糖果【贪心】

【问题描述】 有N(1≤N <100,000)N(1≤N~<100,000)N(1≤N <100,000)种不同类型的糖果,每种糖果数量无限多。第i种糖果每颗的价格是Pi(1≤Pi≤1018),有Ci(1≤Ci≤1018)P_i(1≤P_i≤10^{18}),有C_i (1≤C_i≤10^{18)}Pi​(1≤Pi​≤1018),有Ci​(1≤Ci​≤1018)头牛想吃这种糖果。 FarmerJohnFarmer JohnFarmerJohn拥有金钱BBB元(1≤B≤1

算法小猪的博客 792

企业级微服务架构实战:Spring Cloud配置中心整合与最佳实践

微服务架构作为现代分布式系统的核心设计模式,通过将单体应用拆分为独立部署的小型服务来提升系统可扩展性和维护性。其核心原理基于服务注册发现、配置管理和API网关等组件协同工作,实现服务间的松耦合通信。这种架构的技术价值在于能够支持快速迭代、提高系统容错能力,并为企业数字化转型提供灵活的技术基础。在应用场景方面,微服务特别适合复杂业务系统、高并发互联网应用以及需要快速响应的企业级平台。Spring Cloud作为微服务架构的流行实现方案,提供了配置中心、服务治理等完整生态组件。本文以Spring Cloud C

weixin_30736301的博客 331

从HPPC测试到Simulink仿真:锂电池二阶RC模型参数辨识完整流程

本文详细介绍了从HPPC测试到Simulink仿真的锂电池二阶RC模型参数辨识完整流程。通过HPPC测试获取实验数据,利用Simulink的Parameter Estimation工具进行参数辨识和模型验证,为BMS算法开发提供精确的电池模型。文章涵盖测试设计、数据预处理、模型搭建及工程应用等关键步骤,助力新能源电池研发。

whisky的博客 683

Open3D——直通滤波(python详细过程版)【2025最新版】

python就一个字:强。博客长期更新,本文最近更新时间为:2025年1月11日。

点云侠的博客 7513

JWT单点登录实战:从原理到企业级安全认证方案

单点登录(SSO)作为现代分布式系统的核心认证架构,通过集中认证、分布式授权的模式解决了多系统间的统一登录问题。其技术原理基于令牌机制,JWT(JSON Web Token)凭借自包含、可验证、无状态的特性成为SSO的理想选择。在技术价值层面,JWT单点登录显著提升了系统安全性和用户体验,同时降低了服务端存储压力。典型应用场景包括微服务架构统一认证、多子系统平台集成等企业级应用。本文聚焦JWT在真实单点登录系统中的认证流程设计,涵盖Token安全性、权限动态更新等关键技术挑战,提供基于Spring Boot

congji3817的博客 413

【图像去噪】论文精读:FM2S: Self-Supervised Fluorescence Microscopy Denoising With Single Noisy Image

论文题目:FM2S: Self-Supervised Fluorescence Microscopy Denoising With Single Noisy Image —— FM2S:单噪声图像自监督荧光显微镜去噪arXiv 2024!显微图像去噪!通过可视化详细的细胞结构和生物过程,荧光显微镜具有显着先进的生物学研究。然而,由于难以精确建模固有噪声和获取干净图像进行训练,这种图像去噪任务往往面临挑战,这限制了大多数现有方法。在本文中,我们提出了一种有效的。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 408

SpringBoot集成Sa-Token实现轻量级权限认证实战

权限认证是Java企业级开发的核心模块,传统方案如Shiro和Spring Security虽然功能全面但配置复杂。Sa-Token作为新兴的轻量级权限框架,通过简洁的API设计和注解驱动模式,显著降低了权限系统的实现门槛。其核心原理基于Token鉴权机制,支持登录态管理、角色权限校验等基础功能,并能无缝扩展分布式会话、单点登录等企业级特性。在SpringBoot生态中,Sa-Token通过starter依赖实现快速集成,配合Redis可轻松构建高可用的认证体系。该技术特别适合需要快速迭代的中后台系统,通过

weixin_33888907的博客 480

车辆重识别/再辨识数据集: Vehicle Re-ID Datasets Collection

车辆数据库 1.VERI-Wild: A Large Dataset and a New Method for Vehicle Re-Identification in the Wild (2019-CVPR) 数据库名称:VERI-Wild 论文:链接 数据库描述: 车辆图像由一个包含174个摄像机,拍摄范围覆盖超过200平方公里的市区CCTV系统拍摄。摄像机是24小时连续拍摄30天,其长时间的...

AI炼丹师 6854

RK3588 hdmiout调试(一)

RK3588 hdmiout调试(一)

目前从事linux驱动开发,多交流多学习吧 5169
上一篇: MATLAB实现ULA阵列下标量/矢量传感器MUSIC测向完整流程
下一篇: 树莓派语音点歌台:接上麦克风和音箱就能用的网易云+百度语音方案
postgres8guard
博客等级 码龄4年 62粉丝 1443原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值