update 优化 客户端管理 增加白名单路径和白名单IP功能 可限制客户端能访问的具体路径与可访问的具体IP地址

This commit is contained in:
疯狂的狮子Li
2026-04-16 14:14:25 +08:00
parent a5e8951bcd
commit 981743da00
17 changed files with 323 additions and 49 deletions

View File

@@ -1,12 +1,16 @@
package org.dromara.web.service; package org.dromara.web.service;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.system.domain.SysClient; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.vo.SysClientVo; import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.web.domain.vo.LoginVo; import org.dromara.web.domain.vo.LoginVo;
import java.util.function.Consumer;
/** /**
* 授权策略 * 授权策略
* *
@@ -34,6 +38,37 @@ public interface IAuthStrategy {
return instance.login(body, client); return instance.login(body, client);
} }
/**
* 按客户端配置构建统一登录参数。
*
* @param client 客户端配置
* @return Sa-Token 登录参数
*/
static SaLoginParameter buildLoginParameter(SysClientVo client) {
return buildLoginParameter(client, null);
}
/**
* 按客户端配置构建统一登录参数,并预留自定义扩展入口。
*
* @param client 客户端配置
* @param customizer 自定义扩展逻辑
* @return Sa-Token 登录参数
*/
static SaLoginParameter buildLoginParameter(SysClientVo client, Consumer<SaLoginParameter> customizer) {
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
model.setExtra(LoginHelper.CLIENT_ACCESS_PATH_KEY, client.getAccessPath());
model.setExtra(LoginHelper.CLIENT_IP_WHITELIST_KEY, client.getIpWhitelist());
if (ObjectUtil.isNotNull(customizer)) {
customizer.accept(model);
}
return model;
}
/** /**
* 登录 * 登录
* *

View File

@@ -61,13 +61,7 @@ public class EmailAuthStrategy implements IAuthStrategy {
LoginUser loginUser = loginService.buildLoginUser(user); LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = IAuthStrategy.buildLoginParameter(client);
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token // 生成token
LoginHelper.login(loginUser, model); LoginHelper.login(loginUser, model);

View File

@@ -73,13 +73,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
LoginUser loginUser = loginService.buildLoginUser(user); LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = IAuthStrategy.buildLoginParameter(client);
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token // 生成token
LoginHelper.login(loginUser, model); LoginHelper.login(loginUser, model);

View File

@@ -61,13 +61,7 @@ public class SmsAuthStrategy implements IAuthStrategy {
LoginUser loginUser = loginService.buildLoginUser(user); LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = IAuthStrategy.buildLoginParameter(client);
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token // 生成token
LoginHelper.login(loginUser, model); LoginHelper.login(loginUser, model);

View File

@@ -73,13 +73,7 @@ public class SocialAuthStrategy implements IAuthStrategy {
LoginUser loginUser = loginService.buildLoginUser(user); LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = IAuthStrategy.buildLoginParameter(client);
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token // 生成token
LoginHelper.login(loginUser, model); LoginHelper.login(loginUser, model);

View File

@@ -82,13 +82,7 @@ public class XcxAuthStrategy implements IAuthStrategy {
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
loginUser.setOpenid(openid); loginUser.setOpenid(openid);
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = IAuthStrategy.buildLoginParameter(client);
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token // 生成token
LoginHelper.login(loginUser, model); LoginHelper.login(loginUser, model);

View File

@@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.regex.RegexUtils; import org.dromara.common.core.utils.regex.RegexUtils;
import java.math.BigInteger;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@@ -52,7 +53,8 @@ public class NetUtils extends NetUtil {
public static boolean isInnerIPv6(String ip) { public static boolean isInnerIPv6(String ip) {
try { try {
// 判断是否为IPv6地址 // 判断是否为IPv6地址
if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) { InetAddress inetAddress = InetAddress.getByName(ip);
if (inetAddress instanceof Inet6Address inet6Address) {
// isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断 // isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
// isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断 // isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
// isLoopbackAddress 判断是否为环回地址与IPv4的 127.0.0.1 同理,用于表示本机 // isLoopbackAddress 判断是否为环回地址与IPv4的 127.0.0.1 同理,用于表示本机
@@ -81,4 +83,69 @@ public class NetUtils extends NetUtil {
return RegexUtils.isMatch(PatternPool.IPV4, ip); return RegexUtils.isMatch(PatternPool.IPV4, ip);
} }
/**
* 匹配 IP 规则,支持精确值、通配符与 CIDR。
*
* @param rule IP 规则
* @param clientIp 客户端 IP
* @return 是否匹配
*/
public static boolean isMatchIpRule(String rule, String clientIp) {
if (StringUtils.isBlank(rule) || StringUtils.isBlank(clientIp)) {
return false;
}
String ipRule = StringUtils.trim(rule);
if (StringUtils.equals(ipRule, clientIp)) {
return true;
}
if (ipRule.contains("/")) {
return isMatchCidr(ipRule, clientIp);
}
if (StringUtils.containsAny(ipRule, "*", "?")) {
String regex = ipRule
.replace(".", "\\.")
.replace("*", ".*")
.replace("?", ".");
return clientIp.matches(regex);
}
return false;
}
/**
* 匹配 CIDR 网段。
*
* @param cidr CIDR 规则
* @param clientIp 客户端 IP
* @return 是否命中
*/
public static boolean isMatchCidr(String cidr, String clientIp) {
try {
String[] parts = cidr.split("/");
if (parts.length != 2) {
return false;
}
InetAddress networkAddress = InetAddress.getByName(parts[0]);
InetAddress currentAddress = InetAddress.getByName(clientIp);
byte[] networkBytes = networkAddress.getAddress();
byte[] currentBytes = currentAddress.getAddress();
if (networkBytes.length != currentBytes.length) {
return false;
}
int prefixLength = Integer.parseInt(parts[1]);
int maxPrefix = networkBytes.length * 8;
if (prefixLength < 0 || prefixLength > maxPrefix) {
return false;
}
BigInteger mask = prefixLength == 0
? BigInteger.ZERO
: BigInteger.ONE.shiftLeft(prefixLength).subtract(BigInteger.ONE).shiftLeft(maxPrefix - prefixLength);
BigInteger network = new BigInteger(1, networkBytes);
BigInteger current = new BigInteger(1, currentBytes);
return network.and(mask).equals(current.and(mask));
} catch (UnknownHostException | NumberFormatException e) {
log.debug("IP白名单CIDR规则解析失败: {}", cidr, e);
return false;
}
}
} }

View File

@@ -40,6 +40,8 @@ public class LoginHelper {
public static final String DEPT_NAME_KEY = "deptName"; public static final String DEPT_NAME_KEY = "deptName";
public static final String DEPT_CATEGORY_KEY = "deptCategory"; public static final String DEPT_CATEGORY_KEY = "deptCategory";
public static final String CLIENT_KEY = "clientid"; public static final String CLIENT_KEY = "clientid";
public static final String CLIENT_ACCESS_PATH_KEY = "clientAccessPath";
public static final String CLIENT_IP_WHITELIST_KEY = "clientIpWhitelist";
/** /**
* 登录系统 基于 设备类型 * 登录系统 基于 设备类型

View File

@@ -1,6 +1,7 @@
package org.dromara.common.security.config; package org.dromara.common.security.config;
import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.filter.SaServletFilter; import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil; import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.interceptor.SaInterceptor;
@@ -13,6 +14,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus; import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.utils.NetUtils;
import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
@@ -26,6 +28,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/** /**
* 权限安全配置 * 权限安全配置
* *
@@ -38,6 +42,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SecurityConfig implements WebMvcConfigurer { public class SecurityConfig implements WebMvcConfigurer {
private static final String CLIENT_RULE_SEPARATOR_REGEX = "[,;\\r\\n]+";
private final SecurityProperties securityProperties; private final SecurityProperties securityProperties;
@Value("${message.path:/resource/message}") @Value("${message.path:/resource/message}")
private String messagePath; private String messagePath;
@@ -74,6 +80,7 @@ public class SecurityConfig implements WebMvcConfigurer {
"-100", "客户端ID与Token不匹配", "-100", "客户端ID与Token不匹配",
StpUtil.getTokenValue()); StpUtil.getTokenValue());
} }
validateClientAccessRules(request);
// 有效率影响 用于临时测试 // 有效率影响 用于临时测试
// if (log.isDebugEnabled()) { // if (log.isDebugEnabled()) {
@@ -109,4 +116,41 @@ public class SecurityConfig implements WebMvcConfigurer {
}); });
} }
/**
* 按客户端配置校验接口访问路径与来源 IP。
*
* @param request 当前请求
*/
private void validateClientAccessRules(HttpServletRequest request) {
String requestPath = StringUtils.blankToDefault(request.getServletPath(), request.getRequestURI());
String accessPath = getTokenExtra(LoginHelper.CLIENT_ACCESS_PATH_KEY);
if (StringUtils.isNotBlank(accessPath)) {
List<String> accessPathList = StringUtils.str2List(accessPath, CLIENT_RULE_SEPARATOR_REGEX, true, true);
if (!StringUtils.matches(requestPath, accessPathList)) {
throw new NotPermissionException("当前客户端未授权访问该接口路径");
}
}
String ipWhitelist = getTokenExtra(LoginHelper.CLIENT_IP_WHITELIST_KEY);
if (StringUtils.isNotBlank(ipWhitelist)) {
String clientIp = ServletUtils.getClientIP(request);
List<String> ipWhitelistList = StringUtils.str2List(ipWhitelist, CLIENT_RULE_SEPARATOR_REGEX, true, true);
boolean matched = ipWhitelistList.stream().anyMatch(rule -> NetUtils.isMatchIpRule(rule, clientIp));
if (!matched) {
throw new NotPermissionException("当前客户端IP不在白名单内");
}
}
}
/**
* 读取 token 扩展信息,兼容空值场景。
*
* @param key 扩展字段
* @return 扩展值
*/
private String getTokenExtra(String key) {
Object extra = StpUtil.getExtra(key);
return extra == null ? null : extra.toString();
}
} }

View File

@@ -54,6 +54,16 @@ public class SysClient extends BaseEntity {
*/ */
private String deviceType; private String deviceType;
/**
* 允许访问路径
*/
private String accessPath;
/**
* IP白名单
*/
private String ipWhitelist;
/** /**
* token活跃超时时间 * token活跃超时时间
*/ */

View File

@@ -64,6 +64,26 @@ public class SysClientBo implements Serializable {
*/ */
private String deviceType; private String deviceType;
/**
* 允许访问路径
*/
private String accessPath;
/**
* 允许访问路径列表
*/
private List<String> accessPathList;
/**
* IP白名单
*/
private String ipWhitelist;
/**
* IP白名单列表
*/
private List<String> ipWhitelistList;
/** /**
* token活跃超时时间 * token活跃超时时间
*/ */

View File

@@ -67,6 +67,28 @@ public class SysClientVo implements Serializable {
*/ */
private String deviceType; private String deviceType;
/**
* 允许访问路径
*/
@ExcelProperty(value = "允许访问路径")
private String accessPath;
/**
* 允许访问路径列表
*/
private List<String> accessPathList;
/**
* IP白名单
*/
@ExcelProperty(value = "IP白名单")
private String ipWhitelist;
/**
* IP白名单列表
*/
private List<String> ipWhitelistList;
/** /**
* token活跃超时时间 * token活跃超时时间
*/ */

View File

@@ -25,6 +25,7 @@ import org.springframework.stereotype.Service;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.function.UnaryOperator;
/** /**
* 客户端管理Service业务层处理 * 客户端管理Service业务层处理
@@ -37,6 +38,8 @@ import java.util.List;
@Service @Service
public class SysClientServiceImpl implements ISysClientService { public class SysClientServiceImpl implements ISysClientService {
private static final String CLIENT_RULE_SEPARATOR_REGEX = "[,;\\r\\n]+";
private final SysClientMapper baseMapper; private final SysClientMapper baseMapper;
/** /**
@@ -48,7 +51,7 @@ public class SysClientServiceImpl implements ISysClientService {
@Override @Override
public SysClientVo queryById(Long id) { public SysClientVo queryById(Long id) {
SysClientVo vo = baseMapper.selectVoById(id); SysClientVo vo = baseMapper.selectVoById(id);
vo.setGrantTypeList(StringUtils.splitList(vo.getGrantType())); fillClientRuleFields(vo);
return vo; return vo;
} }
@@ -61,7 +64,9 @@ public class SysClientServiceImpl implements ISysClientService {
@Cacheable(cacheNames = CacheNames.SYS_CLIENT, key = "#clientId") @Cacheable(cacheNames = CacheNames.SYS_CLIENT, key = "#clientId")
@Override @Override
public SysClientVo queryByClientId(String clientId) { public SysClientVo queryByClientId(String clientId) {
return baseMapper.selectVoOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId)); SysClientVo vo = baseMapper.selectVoOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId));
fillClientRuleFields(vo);
return vo;
} }
/** /**
@@ -75,7 +80,7 @@ public class SysClientServiceImpl implements ISysClientService {
public PageResult<SysClientVo> queryPageList(SysClientBo bo, PageQuery pageQuery) { public PageResult<SysClientVo> queryPageList(SysClientBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo); LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo);
Page<SysClientVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw); Page<SysClientVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
result.getRecords().forEach(r -> r.setGrantTypeList(StringUtils.splitList(r.getGrantType()))); result.getRecords().forEach(this::fillClientRuleFields);
return PageResult.build(result.getRecords(), result.getTotal()); return PageResult.build(result.getRecords(), result.getTotal());
} }
@@ -88,7 +93,9 @@ public class SysClientServiceImpl implements ISysClientService {
@Override @Override
public List<SysClientVo> queryList(SysClientBo bo) { public List<SysClientVo> queryList(SysClientBo bo) {
LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo); LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw); List<SysClientVo> list = baseMapper.selectVoList(lqw);
list.forEach(this::fillClientRuleFields);
return list;
} }
/** /**
@@ -117,6 +124,8 @@ public class SysClientServiceImpl implements ISysClientService {
public Boolean insertByBo(SysClientBo bo) { public Boolean insertByBo(SysClientBo bo) {
SysClient add = MapstructUtils.convert(bo, SysClient.class); SysClient add = MapstructUtils.convert(bo, SysClient.class);
add.setGrantType(CollUtil.join(bo.getGrantTypeList(), StringUtils.SEPARATOR)); add.setGrantType(CollUtil.join(bo.getGrantTypeList(), StringUtils.SEPARATOR));
add.setAccessPath(resolveRuleValue(bo.getAccessPath(), bo.getAccessPathList(), this::normalizeAccessPath));
add.setIpWhitelist(resolveRuleValue(bo.getIpWhitelist(), bo.getIpWhitelistList(), UnaryOperator.identity()));
// 生成clientid // 生成clientid
String clientKey = bo.getClientKey(); String clientKey = bo.getClientKey();
String clientSecret = bo.getClientSecret(); String clientSecret = bo.getClientSecret();
@@ -139,6 +148,8 @@ public class SysClientServiceImpl implements ISysClientService {
public Boolean updateByBo(SysClientBo bo) { public Boolean updateByBo(SysClientBo bo) {
SysClient update = MapstructUtils.convert(bo, SysClient.class); SysClient update = MapstructUtils.convert(bo, SysClient.class);
update.setGrantType(StringUtils.joinComma(bo.getGrantTypeList())); update.setGrantType(StringUtils.joinComma(bo.getGrantTypeList()));
update.setAccessPath(resolveRuleValue(bo.getAccessPath(), bo.getAccessPathList(), this::normalizeAccessPath));
update.setIpWhitelist(resolveRuleValue(bo.getIpWhitelist(), bo.getIpWhitelistList(), UnaryOperator.identity()));
return baseMapper.updateById(update) > 0; return baseMapper.updateById(update) > 0;
} }
@@ -185,4 +196,73 @@ public class SysClientServiceImpl implements ISysClientService {
return !exist; return !exist;
} }
/**
* 回填客户端扩展规则字段,便于前端直接展示和编辑。
*
* @param vo 客户端视图对象
*/
private void fillClientRuleFields(SysClientVo vo) {
if (ObjectUtil.isNull(vo)) {
return;
}
vo.setGrantTypeList(StringUtils.splitList(vo.getGrantType()));
vo.setAccessPathList(parseRuleList(vo.getAccessPath(), this::normalizeAccessPath));
vo.setIpWhitelistList(parseRuleList(vo.getIpWhitelist(), UnaryOperator.identity()));
}
/**
* 统一处理白名单与路径规则的入库格式。
*
* @param rawValue 原始字符串
* @param listValue 列表值
* @param normalizer 单条规则归一化器
* @return 逗号拼接后的规则串
*/
private String resolveRuleValue(String rawValue, List<String> listValue, UnaryOperator<String> normalizer) {
List<String> rules = CollUtil.isNotEmpty(listValue)
? listValue
: StringUtils.str2List(rawValue, CLIENT_RULE_SEPARATOR_REGEX, true, true);
if (CollUtil.isEmpty(rules)) {
return listValue != null || rawValue != null ? "" : null;
}
return CollUtil.join(rules.stream()
.map(normalizer)
.filter(StringUtils::isNotBlank)
.toList(), StringUtils.SEPARATOR);
}
/**
* 将规则串转换为列表。
*
* @param value 规则串
* @param normalizer 单条规则归一化器
* @return 规则列表
*/
private List<String> parseRuleList(String value, UnaryOperator<String> normalizer) {
return StringUtils.str2List(value, CLIENT_RULE_SEPARATOR_REGEX, true, true).stream()
.map(normalizer)
.filter(StringUtils::isNotBlank)
.toList();
}
/**
* 统一补齐路径前导斜杠,避免配置成 app/** 时无法命中。
*
* @param path 路径规则
* @return 规范化后的路径规则
*/
private String normalizeAccessPath(String path) {
if (StringUtils.isBlank(path)) {
return null;
}
String accessPath = StringUtils.trim(path);
if (StringUtils.isBlank(accessPath)) {
return null;
}
if (StringUtils.equals(accessPath, "*") || StringUtils.equals(accessPath, "/**")) {
return "/**";
}
return accessPath.startsWith(StringUtils.SLASH) ? accessPath : StringUtils.SLASH + accessPath;
}
} }

View File

@@ -1211,6 +1211,8 @@ create table sys_client (
client_secret varchar2(255) default null, client_secret varchar2(255) default null,
grant_type varchar2(255) default null, grant_type varchar2(255) default null,
device_type varchar2(32) default null, device_type varchar2(32) default null,
access_path varchar2(2000) default null,
ip_whitelist varchar2(1000) default null,
active_timeout number(11) default 1800, active_timeout number(11) default 1800,
timeout number(11) default 604800, timeout number(11) default 604800,
status char(1) default '0', status char(1) default '0',
@@ -1231,6 +1233,8 @@ comment on column sys_client.client_key is '客户端key';
comment on column sys_client.client_secret is '客户端秘钥'; comment on column sys_client.client_secret is '客户端秘钥';
comment on column sys_client.grant_type is '授权类型'; comment on column sys_client.grant_type is '授权类型';
comment on column sys_client.device_type is '设备类型'; comment on column sys_client.device_type is '设备类型';
comment on column sys_client.access_path is '允许访问路径';
comment on column sys_client.ip_whitelist is 'IP白名单';
comment on column sys_client.active_timeout is 'token活跃超时时间'; comment on column sys_client.active_timeout is 'token活跃超时时间';
comment on column sys_client.timeout is 'token固定超时'; comment on column sys_client.timeout is 'token固定超时';
comment on column sys_client.status is '状态0正常 1停用'; comment on column sys_client.status is '状态0正常 1停用';
@@ -1241,8 +1245,8 @@ comment on column sys_client.create_time is '创建时间';
comment on column sys_client.update_by is '更新者'; comment on column sys_client.update_by is '更新者';
comment on column sys_client.update_time is '更新时间'; comment on column sys_client.update_time is '更新时间';
insert into sys_client values (1762000000000000001, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate, 1761100000000000001, sysdate); insert into sys_client values (1762000000000000001, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', null, null, 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate, 1761100000000000001, sysdate);
insert into sys_client values (1762000000000000002, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate, 1761100000000000001, sysdate); insert into sys_client values (1762000000000000002, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', '/app/**', null, 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate, 1761100000000000001, sysdate);
create table test_demo ( create table test_demo (
id number(20) not null, id number(20) not null,

View File

@@ -1206,6 +1206,8 @@ create table sys_client (
client_secret varchar(255) default ''::varchar, client_secret varchar(255) default ''::varchar,
grant_type varchar(255) default ''::varchar, grant_type varchar(255) default ''::varchar,
device_type varchar(32) default ''::varchar, device_type varchar(32) default ''::varchar,
access_path varchar(2000) default ''::varchar,
ip_whitelist varchar(1000) default ''::varchar,
active_timeout int4 default 1800, active_timeout int4 default 1800,
timeout int4 default 604800, timeout int4 default 604800,
status char(1) default '0'::bpchar, status char(1) default '0'::bpchar,
@@ -1225,6 +1227,8 @@ comment on column sys_client.client_key is '客户端key';
comment on column sys_client.client_secret is '客户端秘钥'; comment on column sys_client.client_secret is '客户端秘钥';
comment on column sys_client.grant_type is '授权类型'; comment on column sys_client.grant_type is '授权类型';
comment on column sys_client.device_type is '设备类型'; comment on column sys_client.device_type is '设备类型';
comment on column sys_client.access_path is '允许访问路径';
comment on column sys_client.ip_whitelist is 'IP白名单';
comment on column sys_client.active_timeout is 'token活跃超时时间'; comment on column sys_client.active_timeout is 'token活跃超时时间';
comment on column sys_client.timeout is 'token固定超时'; comment on column sys_client.timeout is 'token固定超时';
comment on column sys_client.status is '状态0正常 1停用'; comment on column sys_client.status is '状态0正常 1停用';
@@ -1235,8 +1239,8 @@ comment on column sys_client.create_time is '创建时间';
comment on column sys_client.update_by is '更新者'; comment on column sys_client.update_by is '更新者';
comment on column sys_client.update_time is '更新时间'; comment on column sys_client.update_time is '更新时间';
insert into sys_client values (1762000000000000001, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, now(), 1761100000000000001, now()); insert into sys_client values (1762000000000000001, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', '', '', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, now(), 1761100000000000001, now());
insert into sys_client values (1762000000000000002, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, now(), 1761100000000000001, now()); insert into sys_client values (1762000000000000002, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', '/app/**', '', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, now(), 1761100000000000001, now());
create table if not exists test_demo create table if not exists test_demo
( (

View File

@@ -852,6 +852,8 @@ create table sys_client (
client_secret varchar(255) default null comment '客户端秘钥', client_secret varchar(255) default null comment '客户端秘钥',
grant_type varchar(255) default null comment '授权类型', grant_type varchar(255) default null comment '授权类型',
device_type varchar(32) default null comment '设备类型', device_type varchar(32) default null comment '设备类型',
access_path varchar(2000) default null comment '允许访问路径',
ip_whitelist varchar(1000) default null comment 'IP白名单',
active_timeout int(11) default 1800 comment 'token活跃超时时间', active_timeout int(11) default 1800 comment 'token活跃超时时间',
timeout int(11) default 604800 comment 'token固定超时', timeout int(11) default 604800 comment 'token固定超时',
status char(1) default '0' comment '状态0正常 1停用', status char(1) default '0' comment '状态0正常 1停用',
@@ -864,8 +866,8 @@ create table sys_client (
primary key (id) primary key (id)
) engine=innodb comment='系统授权表'; ) engine=innodb comment='系统授权表';
insert into sys_client values (1762000000000000001, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate(), 1761100000000000001, sysdate()); insert into sys_client values (1762000000000000001, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', null, null, 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate(), 1761100000000000001, sysdate());
insert into sys_client values (1762000000000000002, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate(), 1761100000000000001, sysdate()); insert into sys_client values (1762000000000000002, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', '/app/**', null, 1800, 604800, 0, 0, 1761000000000000103, 1761100000000000001, sysdate(), 1761100000000000001, sysdate());
CREATE TABLE test_demo CREATE TABLE test_demo

View File

@@ -3060,6 +3060,8 @@ CREATE TABLE sys_client
client_secret nvarchar(255) DEFAULT '' NULL, client_secret nvarchar(255) DEFAULT '' NULL,
grant_type nvarchar(255) DEFAULT '' NULL, grant_type nvarchar(255) DEFAULT '' NULL,
device_type nvarchar(32) DEFAULT '' NULL, device_type nvarchar(32) DEFAULT '' NULL,
access_path nvarchar(2000) DEFAULT '' NULL,
ip_whitelist nvarchar(1000) DEFAULT '' NULL,
active_timeout int DEFAULT ((1800)) NULL, active_timeout int DEFAULT ((1800)) NULL,
timeout int DEFAULT ((604800)) NULL, timeout int DEFAULT ((604800)) NULL,
status nchar(1) DEFAULT ('0') NULL, status nchar(1) DEFAULT ('0') NULL,
@@ -3112,6 +3114,18 @@ EXEC sp_addextendedproperty
'TABLE', N'sys_client', 'TABLE', N'sys_client',
'COLUMN', N'device_type' 'COLUMN', N'device_type'
GO GO
EXEC sp_addextendedproperty
'MS_Description', N'允许访问路径',
'SCHEMA', N'dbo',
'TABLE', N'sys_client',
'COLUMN', N'access_path'
GO
EXEC sp_addextendedproperty
'MS_Description', N'IP白名单',
'SCHEMA', N'dbo',
'TABLE', N'sys_client',
'COLUMN', N'ip_whitelist'
GO
EXEC sp_addextendedproperty EXEC sp_addextendedproperty
'MS_Description', N'token活跃超时时间', 'MS_Description', N'token活跃超时时间',
'SCHEMA', N'dbo', 'SCHEMA', N'dbo',
@@ -3172,9 +3186,9 @@ EXEC sp_addextendedproperty
'TABLE', N'sys_client' 'TABLE', N'sys_client'
GO GO
INSERT INTO sys_client VALUES (1762000000000000001, N'e5cd7e4891bf95d1d19206ce24a7b32e', N'pc', N'pc123', N'password,social', N'pc', 1800, 604800, N'0', N'0', 1761000000000000103, 1761100000000000001, getdate(), 1761100000000000001, getdate()); INSERT INTO sys_client VALUES (1762000000000000001, N'e5cd7e4891bf95d1d19206ce24a7b32e', N'pc', N'pc123', N'password,social', N'pc', N'', N'', 1800, 604800, N'0', N'0', 1761000000000000103, 1761100000000000001, getdate(), 1761100000000000001, getdate());
GO GO
INSERT INTO sys_client VALUES (1762000000000000002, N'428a8310cd442757ae699df5d894f051', N'app', N'app123', N'password,sms,social', N'android', 1800, 604800, N'0', N'0', 1761000000000000103, 1761100000000000001, getdate(), 1761100000000000001, getdate()); INSERT INTO sys_client VALUES (1762000000000000002, N'428a8310cd442757ae699df5d894f051', N'app', N'app123', N'password,sms,social', N'android', N'/app/**', N'', 1800, 604800, N'0', N'0', 1761000000000000103, 1761100000000000001, getdate(), 1761100000000000001, getdate());
GO GO
CREATE TABLE test_demo CREATE TABLE test_demo