This commit is contained in:
kk
2026-07-07 18:01:21 +08:00
commit b259f94d4b
1088 changed files with 121778 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
<?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>
<parent>
<artifactId>blade-core-auth</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-starter-key</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<properties>
<module.name>org.springblade.blade.starter.key</module.name>
</properties>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,109 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.support.Kv;
import java.io.Serial;
import java.io.Serializable;
/**
* 用户实体
*
* @author Chill
*/
@Data
@Hidden
public class BladeUser implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 客户端id
*/
@Schema(hidden = true)
private String clientId;
/**
* 用户id
*/
@Schema(hidden = true)
private Long userId;
/**
* 账号
*/
@Schema(hidden = true)
private String account;
/**
* 用户名
*/
@Schema(hidden = true)
private String userName;
/**
* 昵称
*/
@Schema(hidden = true)
private String nickName;
/**
* 租户ID
*/
@Schema(hidden = true)
private String tenantId;
/**
* 第三方认证ID
*/
@Schema(hidden = true)
private String oauthId;
/**
* 部门id
*/
@Schema(hidden = true)
private String deptId;
/**
* 岗位id
*/
@Schema(hidden = true)
private String postId;
/**
* 角色id
*/
@Schema(hidden = true)
private String roleId;
/**
* 角色名
*/
@Schema(hidden = true)
private String roleName;
/**
* 用户详情
*/
@Schema(hidden = true)
private Kv detail;
}
@@ -0,0 +1,155 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure;
import org.springblade.core.secure.constant.ApiKeyConstant;
import org.springblade.core.tool.utils.Charsets;
import org.springblade.core.tool.utils.Exceptions;
import org.springblade.core.tool.utils.HexUtil;
import org.springblade.core.tool.utils.Pkcs7Encoder;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Objects;
/**
* Key加解密工具类
*
* @author BladeX
*/
public class KeyCrypto {
public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
public static String genAesKey() {
return StringUtil.random(32);
}
/**
* 生成 API Key
*
* @param cryptoKey 加密密钥
* @return 带前缀的加密 Key
*/
public static String generateKey(String cryptoKey) {
return ApiKeyConstant.API_KEY_PREFIX + encryptToHex(StringUtil.randomUUID(), cryptoKey);
}
/**
* 解析 API Key
*
* @param key 带前缀的加密 Key
* @param cryptoKey 加密密钥
* @return 解密后的原始值
*/
@Nullable
public static String parseKey(@Nullable String key, String cryptoKey) {
if (StringUtil.isBlank(key) || !key.startsWith(ApiKeyConstant.API_KEY_PREFIX)) {
return null;
}
try {
String encryptedPart = key.substring(ApiKeyConstant.API_KEY_PREFIX.length());
return decryptFormHexToString(encryptedPart, cryptoKey);
} catch (Exception e) {
return null;
}
}
public static String encryptToHex(String content, String aesTextKey) {
return HexUtil.encodeToString(encrypt(content, aesTextKey));
}
public static String encryptToHex(byte[] content, String aesTextKey) {
return HexUtil.encodeToString(encrypt(content, aesTextKey));
}
@Nullable
public static String decryptFormHexToString(@Nullable String content, String aesTextKey) {
byte[] hexBytes = decryptFormHex(content, aesTextKey);
if (hexBytes == null) {
return null;
}
return new String(hexBytes, DEFAULT_CHARSET);
}
@Nullable
public static byte[] decryptFormHex(@Nullable String content, String aesTextKey) {
if (StringUtil.isBlank(content)) {
return null;
}
return decryptFormHex(content.getBytes(DEFAULT_CHARSET), aesTextKey);
}
public static byte[] decryptFormHex(byte[] content, String aesTextKey) {
return decrypt(HexUtil.decode(content), aesTextKey);
}
public static byte[] encrypt(String content, String aesTextKey) {
return encrypt(content.getBytes(DEFAULT_CHARSET), aesTextKey);
}
public static byte[] encrypt(String content, Charset charset, String aesTextKey) {
return encrypt(content.getBytes(charset), aesTextKey);
}
public static byte[] encrypt(byte[] content, String aesTextKey) {
return encrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
}
public static byte[] encrypt(byte[] content, byte[] aesKey) {
return aes(Pkcs7Encoder.encode(content), aesKey, Cipher.ENCRYPT_MODE);
}
public static String decryptToString(byte[] content, String aesTextKey) {
return new String(decrypt(content, aesTextKey), DEFAULT_CHARSET);
}
public static byte[] decrypt(byte[] content, String aesTextKey) {
return decrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
}
public static byte[] decrypt(byte[] encrypted, byte[] aesKey) {
return Pkcs7Encoder.decode(aes(encrypted, aesKey, Cipher.DECRYPT_MODE));
}
private static byte[] aes(byte[] encrypted, byte[] aesKey, int mode) {
Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32");
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(mode, keySpec, iv);
return cipher.doFinal(encrypted);
} catch (Exception e) {
throw Exceptions.unchecked(e);
}
}
}
@@ -0,0 +1,121 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.constant;
/**
* API Key 常量
*
* @author Chill
*/
public interface ApiKeyConstant {
/**
* API Key 前缀
*/
String API_KEY_PREFIX = "ak-";
/**
* 启用状态
*/
int STATUS_ACTIVE = 1;
/**
* 缓存名称
*/
String API_KEY_CACHE = "blade:apikey";
/**
* 用户缓存前缀
*/
String CACHE_USER_PREFIX = "apikey:user:";
/**
* 路径缓存前缀
*/
String CACHE_PATH_PREFIX = "apikey:path:";
/**
* 全路径匹配
*/
String FULL_PATH = "/**";
/**
* 角色名称列名
*/
String ROLE_NAME_COLUMN = "role_name";
/**
* 角色别名列名
*/
String ROLE_ALIAS_COLUMN = "role_alias";
/**
* BladeUser.detail 中存放 API Key 主键ID 的 key
*/
String DETAIL_API_KEY_ID = "apiKeyId";
// ====================================================================
// SQL 查询语句(分步查询策略)
// 采用多步单表查询而非联表查询,防止大数据量用户表联查导致查询速度过慢
// ====================================================================
/**
* 查询 API Key 基本信息(第一步:仅查 blade_api_key 表)
*/
String API_KEY_SELECT_STATEMENT = """
SELECT id, tenant_id, user_id, api_key, api_path, expire_time, ext_params, status
FROM blade_api_key WHERE api_key = ? AND status = 1 AND is_deleted = 0
""";
/**
* 查询关联用户信息(第二步:通过 userId 查 blade_user 表)
*/
String API_KEY_USER_SELECT_STATEMENT = """
SELECT account, name, real_name, dept_id, post_id, role_id
FROM blade_user WHERE id = ? AND status = 1 AND is_deleted = 0
""";
/**
* 查询角色名称与别名(第三步:通过 roleId 查 blade_role 表,%s 由代码动态填充 IN 列表)
*/
String API_KEY_ROLE_SELECT_STATEMENT = """
SELECT role_name, role_alias FROM blade_role WHERE is_deleted = 0 AND status = 1 AND id IN (%s)
""";
// ====================================================================
// 调用日志持久化
// ====================================================================
/**
* 插入 API Key 调用日志
*/
String API_KEY_LOG_INSERT = """
INSERT INTO blade_api_key_log
(id, tenant_id, service_id, server_ip, server_host, env, api_key_id, request_uri, method, remote_ip, user_agent, params, time, create_by, create_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""";
}
@@ -0,0 +1,72 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.env;
import org.springblade.core.auto.annotation.AutoEnvPostProcessor;
import org.springblade.core.launch.utils.PropsUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ClassUtils;
/**
* 超级密钥配置初始化。
* <p>
* 微服务侧 (存在 blade-core-cloud) 将 {@code path-prefix} 落值为服务名,路径校验时补回网关剥离的前缀,
* 使 api_path 配置在单体与微服务下语义一致。
*
* @author Chill
*/
@AutoEnvPostProcessor
public class KeyEnvPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final String PATH_PREFIX_KEY = "blade.key.path-prefix";
private static final String APPLICATION_NAME_KEY = "spring.application.name";
/**
* 是否微服务部署: 存在 blade-core-cloud 即经网关按服务名路由
*/
private static final boolean MICROSERVICE = ClassUtils.isPresent(
"org.springblade.core.cloud.client.BladeCloudApplication", KeyEnvPostProcessor.class.getClassLoader());
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String serviceName = environment.getProperty(APPLICATION_NAME_KEY, StringPool.EMPTY);
if (MICROSERVICE && StringUtil.isNotBlank(serviceName) && StringUtil.isBlank(environment.getProperty(PATH_PREFIX_KEY))) {
PropsUtil.setProperty(System.getProperties(), PATH_PREFIX_KEY, StringPool.SLASH.concat(serviceName));
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
@@ -0,0 +1,65 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.exception;
import lombok.Getter;
import org.springblade.core.tool.api.IResultCode;
import org.springblade.core.tool.api.ResultCode;
import java.io.Serial;
/**
* Key异常
*
* @author Chill
*/
public class KeyException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
@Getter
private final IResultCode resultCode;
public KeyException(String message) {
super(message);
this.resultCode = ResultCode.UN_AUTHORIZED;
}
public KeyException(IResultCode resultCode) {
super(resultCode.getMessage());
this.resultCode = resultCode;
}
public KeyException(IResultCode resultCode, Throwable cause) {
super(cause);
this.resultCode = resultCode;
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
@@ -0,0 +1,96 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.handler;
import jakarta.servlet.http.HttpServletRequest;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.constant.ApiKeyConstant;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
/**
* API Key 处理器接口
* 用于通过 API Key 获取用户信息
*
* @author Chill
*/
public interface IApiKeyHandler {
/**
* 通过 API Key 获取用户信息
*
* @param apiKey API Key (ak-xxx格式)
* @return BladeUser 用户信息,若不存在或已过期则返回 null
*/
BladeUser getUser(String apiKey);
/**
* 移除 API Key 相关缓存
*
* @param apiKey API Key
*/
void removeCache(String apiKey);
/**
* 生成 API Key
* 格式: ak-[UUID无中划线]
*
* @return 生成的 API Key
*/
default String generateKey() {
return ApiKeyConstant.API_KEY_PREFIX + StringUtil.randomUUID();
}
/**
* 判断给定的认证字符串是否为有效的 API Key
*
* @param auth 认证字符串
* @return true 如果是有效的 API Key,否则返回 false
*/
default boolean isApiKey(String auth) {
return StringUtil.isNotBlank(auth) && auth.startsWith(ApiKeyConstant.API_KEY_PREFIX);
}
/**
* 判断当前请求是否包含有效的 API Key
*
* @return true 如果请求中包含有效的 API Key,否则返回 false
*/
default boolean isApiKeyRequest() {
HttpServletRequest request = WebUtil.getRequest();
if (request == null) {
return false;
}
String auth = request.getHeader(TokenConstant.AUTH_HEADER);
if (StringUtil.isBlank(auth)) {
auth = request.getParameter(TokenConstant.AUTH_HEADER);
}
return isApiKey(auth);
}
}
@@ -0,0 +1,54 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.handler;
import jakarta.servlet.http.HttpServletRequest;
import org.springblade.core.secure.BladeUser;
import org.springframework.lang.Nullable;
/**
* API Key 调用日志处理器接口
* <p>
* 下游工程可自定义实现以支持不同的日志存储策略(如消息队列、远程服务等)。
*
* @author Chill
*/
public interface IApiKeyLogHandler {
/**
* 保存 API Key 调用日志
* <p>
* 该方法在主请求线程中调用,实现类可在内部自行决定同步或异步策略。
* request 由调用方在主线程中获取后传入,实现类无需再通过线程上下文获取。
*
* @param bladeUser 认证通过的用户信息
* @param apiKeyId API Key 主键ID
* @param time 认证耗时(ms)
* @param request 当前请求对象(可能为空)
*/
void saveLog(BladeUser bladeUser, Long apiKeyId, long time, @Nullable HttpServletRequest request);
}
@@ -0,0 +1,68 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.secure.props;
import lombok.Data;
import org.springblade.core.secure.exception.KeyException;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 超级密钥配置
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.key")
public class KeyProperties {
/**
* 超级密钥是否开启
*/
private Boolean enabled = Boolean.FALSE;
/**
* 超级密钥
*/
private String cryptoKey = StringPool.EMPTY;
/**
* 路径校验前缀 (微服务侧由 KeyEnvPostProcessor 落值为服务名,单体为空)
*/
private String pathPrefix = StringPool.EMPTY;
/**
* 获取超级密钥
*/
public String getCryptoKey() {
if (StringUtil.isBlank(cryptoKey)) {
throw new KeyException("请配置 blade.key.crypto-key 的值");
}
return this.cryptoKey;
}
}