feat(20260506新版bladex): 项目init

This commit is contained in:
xuyanbo
2026-07-06 11:32:19 +08:00
committed by kongxz
parent 19d41454dc
commit 475422d815
652 changed files with 131273 additions and 85 deletions
@@ -0,0 +1,23 @@
<?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">
<parent>
<artifactId>blade-service-api</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-authority-api</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-secure</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,98 @@
/**
* 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.system.cache;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.system.feign.IApiKeyClient;
import java.util.Objects;
import static org.springblade.core.secure.constant.ApiKeyConstant.*;
/**
* API Key 缓存
*
* @author Chill
*/
public class ApiKeyCache {
private static IApiKeyClient apiKeyClient;
private static IApiKeyClient getApiKeyClient() {
if (apiKeyClient == null) {
apiKeyClient = SpringUtil.getBean(IApiKeyClient.class);
}
return apiKeyClient;
}
/**
* 获取用户信息
*
* @param apiKey API Key
* @return BladeUser
*/
public static BladeUser getUser(String apiKey) {
BladeUser bladeUser = CacheUtil.get(API_KEY_CACHE, CACHE_USER_PREFIX, apiKey, BladeUser.class, Boolean.FALSE);
if (bladeUser != null) {
// 若用户ID为空,说明是缓存的空对象标记,返回null防止缓存穿透
return bladeUser.getUserId() != null ? bladeUser : null;
}
bladeUser = getApiKeyClient().getUser(apiKey);
// 动态缓存用户对象,防止缓存穿透
CacheUtil.put(API_KEY_CACHE, CACHE_USER_PREFIX, apiKey, Objects.requireNonNullElseGet(bladeUser, BladeUser::new), Boolean.FALSE);
return bladeUser;
}
/**
* 获取访问路径权限
*
* @param apiKey API Key
* @return apiPath
*/
public static String getApiPath(String apiKey) {
String apiPath = CacheUtil.get(API_KEY_CACHE, CACHE_PATH_PREFIX, apiKey, String.class, Boolean.FALSE);
if (apiPath == null) {
apiPath = getApiKeyClient().getApiPath(apiKey);
// 动态缓存权限路径,防止缓存穿透
CacheUtil.put(API_KEY_CACHE, CACHE_PATH_PREFIX, apiKey, Func.toStrWithEmpty(apiPath, FULL_PATH), Boolean.FALSE);
}
return apiPath;
}
/**
* 移除缓存
*
* @param apiKey API Key
*/
public static void removeCache(String apiKey) {
CacheUtil.evict(API_KEY_CACHE, CACHE_USER_PREFIX, apiKey, Boolean.FALSE);
CacheUtil.evict(API_KEY_CACHE, CACHE_PATH_PREFIX, apiKey, Boolean.FALSE);
}
}
@@ -0,0 +1,62 @@
/**
* 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.system.config;
import lombok.AllArgsConstructor;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.secure.config.RegistryConfiguration;
import org.springblade.core.secure.handler.IApiKeyHandler;
import org.springblade.core.secure.handler.IApiKeyLogHandler;
import org.springblade.core.secure.props.KeyProperties;
import org.springblade.system.handler.ApiKeyHandler;
import org.springblade.system.handler.ApiKeyLogHandler;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 权限认证配置类
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
@AutoConfigureBefore(RegistryConfiguration.class)
public class AuthorityConfiguration {
@Bean
public IApiKeyLogHandler apiKeyLogHandler(JdbcTemplate jdbcTemplate, BladeProperties bladeProperties, ServerInfo serverInfo) {
return new ApiKeyLogHandler(jdbcTemplate, bladeProperties, serverInfo);
}
@Bean
public IApiKeyHandler apiKeyHandler(KeyProperties keyProperties, IApiKeyLogHandler apiKeyLogHandler) {
return new ApiKeyHandler(keyProperties, apiKeyLogHandler);
}
}
@@ -0,0 +1,67 @@
/**
* 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.system.feign;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.secure.BladeUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* API Key Feign接口类
*
* @author Chill
*/
@FeignClient(
value = AppConstant.APPLICATION_SYSTEM_NAME,
fallback = IApiKeyClientFallback.class
)
public interface IApiKeyClient {
String API_PREFIX = "/feign/client/api-key";
String GET_USER = API_PREFIX + "/get-user";
String GET_API_PATH = API_PREFIX + "/get-api-path";
/**
* 通过 API Key 获取用户信息
*
* @param apiKey API Key
* @return BladeUser
*/
@GetMapping(GET_USER)
BladeUser getUser(@RequestParam("apiKey") String apiKey);
/**
* 获取 API Key 的访问路径权限
*
* @param apiKey API Key
* @return apiPath
*/
@GetMapping(GET_API_PATH)
String getApiPath(@RequestParam("apiKey") String apiKey);
}
@@ -0,0 +1,49 @@
/**
* 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.system.feign;
import org.springblade.core.secure.BladeUser;
import org.springframework.stereotype.Component;
/**
* IApiKeyClientFallback
*
* @author Chill
*/
@Component
public class IApiKeyClientFallback implements IApiKeyClient {
@Override
public BladeUser getUser(String apiKey) {
return null;
}
@Override
public String getApiPath(String apiKey) {
return null;
}
}
@@ -0,0 +1,129 @@
/**
* 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.system.handler;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.KeyCrypto;
import org.springblade.core.secure.handler.IApiKeyHandler;
import org.springblade.core.secure.handler.IApiKeyLogHandler;
import org.springblade.core.secure.props.KeyProperties;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.system.cache.ApiKeyCache;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import static org.springblade.core.secure.constant.ApiKeyConstant.DETAIL_API_KEY_ID;
import static org.springblade.core.secure.constant.ApiKeyConstant.FULL_PATH;
/**
* API Key 处理器微服务实现
*
* @author Chill
*/
@RequiredArgsConstructor
public class ApiKeyHandler implements IApiKeyHandler {
/**
* 路径匹配器
*/
private static final PathMatcher PATH_MATCHER = new AntPathMatcher();
private final KeyProperties keyProperties;
private final IApiKeyLogHandler apiKeyLogHandler;
@Override
public BladeUser getUser(String apiKey) {
long startTime = System.currentTimeMillis();
// 检查功能是否启用
if (!keyProperties.getEnabled()) {
return null;
}
// 检查令牌格式是否合法
String parseKey = KeyCrypto.parseKey(apiKey, keyProperties.getCryptoKey());
if (Func.isBlank(parseKey)) {
return null;
}
// 加载用户信息
BladeUser bladeUser = ApiKeyCache.getUser(apiKey);
if (bladeUser == null) {
return null;
}
// 验证访问路径权限
HttpServletRequest request = WebUtil.getRequest();
if (request != null) {
String requestPath = request.getRequestURI();
if (!validateApiPath(apiKey, requestPath)) {
return null;
}
}
// 异步保存 API Key 调用日志
long apiKeyId = Func.toLong(bladeUser.getDetail().get(DETAIL_API_KEY_ID));
if (apiKeyId > 0L) {
long time = System.currentTimeMillis() - startTime;
apiKeyLogHandler.saveLog(bladeUser, apiKeyId, time, request);
}
return bladeUser;
}
@Override
public void removeCache(String apiKey) {
ApiKeyCache.removeCache(apiKey);
}
@Override
public String generateKey() {
return KeyCrypto.generateKey(keyProperties.getCryptoKey());
}
/**
* 验证请求路径是否有访问权限
*
* @param apiKey API Key
* @param requestPath 请求路径
* @return true 有权限,false 无权限
*/
private boolean validateApiPath(String apiKey, String requestPath) {
String apiPath = ApiKeyCache.getApiPath(apiKey);
// 如果未配置访问权限,默认允许所有访问
if (Func.isBlank(apiPath) || FULL_PATH.equals(apiPath)) {
return true;
}
// 解析逗号分隔的路径并匹配
String[] paths = apiPath.split(StringPool.COMMA);
for (String path : paths) {
String trimmedPath = path.trim();
if (Func.isNotBlank(trimmedPath) && PATH_MATCHER.match(trimmedPath, requestPath)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,106 @@
/**
* 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.system.handler;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.handler.IApiKeyLogHandler;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.support.IdGenerator;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.UrlUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.lang.Nullable;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import static org.springblade.core.secure.constant.ApiKeyConstant.API_KEY_LOG_INSERT;
/**
* API Key 调用日志处理器微服务实现
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class ApiKeyLogHandler implements IApiKeyLogHandler {
private final JdbcTemplate jdbcTemplate;
private final BladeProperties bladeProperties;
private final ServerInfo serverInfo;
@Override
public void saveLog(BladeUser bladeUser, Long apiKeyId, long time, @Nullable HttpServletRequest request) {
CompletableFuture.runAsync(() -> {
try {
jdbcTemplate.update(API_KEY_LOG_INSERT, collectLogParams(bladeUser, apiKeyId, time, request));
} catch (Exception logException) {
log.error("API Key日志保存失败: {}", logException.getMessage());
}
});
}
/**
* 收集日志参数
*
* @param bladeUser 认证用户
* @param apiKeyId API Key 主键ID
* @param time 认证耗时(ms)
* @param request 当前请求对象
* @return SQL 参数数组
*/
private Object[] collectLogParams(BladeUser bladeUser, Long apiKeyId, long time, @Nullable HttpServletRequest request) {
String tenantId = Func.toStrWithEmpty(bladeUser.getTenantId(), BladeConstant.ADMIN_TENANT_ID);
String createBy = Func.toStrWithEmpty(bladeUser.getAccount(), bladeUser.getUserName());
// 请求信息
String requestUri = StringPool.EMPTY;
String httpMethod = StringPool.EMPTY;
String remoteIp = StringPool.EMPTY;
String userAgent = StringPool.EMPTY;
String params = StringPool.EMPTY;
if (request != null) {
requestUri = UrlUtil.getPath(request.getRequestURI());
httpMethod = request.getMethod();
remoteIp = WebUtil.getIP(request);
userAgent = Func.toStr(request.getHeader(WebUtil.USER_AGENT_HEADER));
params = WebUtil.getRequestContent(request);
}
return new Object[]{
IdGenerator.getId(), tenantId,
bladeProperties.getName(), serverInfo.getIpWithPort(), serverInfo.getHostName(), bladeProperties.getEnv(),
apiKeyId, requestUri, httpMethod, remoteIp, userAgent, params,
String.valueOf(time), createBy, new Date()
};
}
}