forked from github/cool-admin-java
feat: SaySS代理模块后端集成 - 登录token存库/白名单增删同步SaySS/添加者用户ID
- AgentInfoEntity 新增 sayssAuthData 字段存储 SaySS auth_data token - AgentWhitelistEntity 新增 sayssTrustedIpId、addedByUserId 字段,addedByName 虚拟字段 - SayssService 实现 AES-CBC 加密中间件调用,支持 loginAndSave/checkTokenValid/addTrustedIp/deleteTrustedIp - AdminAgentInfoController 新增 loginSayss、checkToken 接口 - AdminAgentWhitelistController 删除时先从 SaySS 删除成功后再删本地,分页关联查出 agentName 和 addedByName - AppAgentInfoController addWhitelist 先调 SaySS 添加成功后保存本地,记录当前 App 用户 ID - application.yml 新增 sayss 配置
This commit is contained in:
@@ -3,19 +3,45 @@ package com.cool.modules.agent.controller.admin;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.cool.core.annotation.CoolRestController;
|
||||
import com.cool.core.base.BaseController;
|
||||
import com.cool.core.request.R;
|
||||
import com.cool.modules.agent.entity.AgentInfoEntity;
|
||||
import com.cool.modules.agent.service.AgentInfoService;
|
||||
import com.cool.modules.agent.service.SayssService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestAttribute;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "代理管理", description = "代理信息管理")
|
||||
@CoolRestController(api = {"add", "delete", "update", "page", "info"})
|
||||
public class AdminAgentInfoController extends BaseController<AgentInfoService, AgentInfoEntity> {
|
||||
|
||||
private final SayssService sayssService;
|
||||
|
||||
@Override
|
||||
protected void init(HttpServletRequest request, JSONObject requestParams) {
|
||||
setPageOption(createOp());
|
||||
}
|
||||
|
||||
@Operation(summary = "登录SaySS")
|
||||
@PostMapping("/loginSayss")
|
||||
public R loginSayss(@RequestAttribute JSONObject requestParams) {
|
||||
Long id = requestParams.getLong("id");
|
||||
String authData = sayssService.loginAndSave(id);
|
||||
if (authData == null) {
|
||||
return R.error("登录SaySS失败,请检查用户名密码");
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "验证SaySS Token")
|
||||
@PostMapping("/checkToken")
|
||||
public R checkToken(@RequestAttribute JSONObject requestParams) {
|
||||
Long id = requestParams.getLong("id");
|
||||
boolean valid = sayssService.checkTokenValid(id);
|
||||
return R.ok(new JSONObject().set("valid", valid));
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -1,15 +1,25 @@
|
||||
package com.cool.modules.agent.controller.admin;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.cool.core.annotation.CoolRestController;
|
||||
import com.cool.core.base.BaseController;
|
||||
import com.cool.core.request.R;
|
||||
import com.cool.modules.agent.entity.AgentInfoEntity;
|
||||
import com.cool.modules.agent.entity.AgentWhitelistEntity;
|
||||
import com.cool.modules.agent.service.AgentInfoService;
|
||||
import com.cool.modules.agent.service.AgentWhitelistService;
|
||||
import com.cool.modules.agent.service.SayssService;
|
||||
import com.cool.modules.user.entity.UserInfoEntity;
|
||||
import com.cool.modules.user.service.UserInfoService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestAttribute;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "白名单管理", description = "代理白名单管理")
|
||||
@@ -17,6 +27,8 @@ import lombok.RequiredArgsConstructor;
|
||||
public class AdminAgentWhitelistController extends BaseController<AgentWhitelistService, AgentWhitelistEntity> {
|
||||
|
||||
private final AgentInfoService agentInfoService;
|
||||
private final UserInfoService userInfoService;
|
||||
private final SayssService sayssService;
|
||||
|
||||
@Override
|
||||
protected void init(HttpServletRequest request, JSONObject requestParams) {
|
||||
@@ -26,6 +38,34 @@ public class AdminAgentWhitelistController extends BaseController<AgentWhitelist
|
||||
if (agent != null) {
|
||||
entity.setAgentName(agent.getName());
|
||||
}
|
||||
if (entity.getAddedByUserId() != null) {
|
||||
UserInfoEntity user = (UserInfoEntity) userInfoService.info(entity.getAddedByUserId());
|
||||
if (user != null) {
|
||||
entity.setAddedByName(user.getNickName());
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除", description = "先从SaySS删除白名单,成功后再从本地删除")
|
||||
@PostMapping("/delete")
|
||||
protected R delete(HttpServletRequest request, @RequestBody Map<String, Object> params,
|
||||
@RequestAttribute() JSONObject requestParams) {
|
||||
Long[] ids = Convert.toLongArray(getIds(params));
|
||||
for (Long id : ids) {
|
||||
AgentWhitelistEntity entity = service.getById(id);
|
||||
if (entity == null) continue;
|
||||
boolean sayssDeleted;
|
||||
if (entity.getSayssTrustedIpId() != null) {
|
||||
sayssDeleted = sayssService.deleteTrustedIp(entity.getAgentInfoId(), entity.getSayssTrustedIpId());
|
||||
} else {
|
||||
sayssDeleted = sayssService.deleteTrustedIpByIp(entity.getAgentInfoId(), entity.getIpAddress());
|
||||
}
|
||||
if (!sayssDeleted) {
|
||||
return R.error("从SaySS删除白名单失败,IP: " + entity.getIpAddress() + ",请检查代理账号是否正确");
|
||||
}
|
||||
}
|
||||
service.delete(requestParams, ids);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,24 @@ package com.cool.modules.agent.controller.app;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.cool.core.annotation.CoolRestController;
|
||||
import com.cool.core.request.R;
|
||||
import com.cool.core.util.CoolSecurityUtil;
|
||||
import com.cool.modules.agent.entity.AgentWhitelistEntity;
|
||||
import com.cool.modules.agent.service.AgentInfoService;
|
||||
import com.cool.modules.agent.service.AgentWhitelistService;
|
||||
import com.cool.modules.agent.service.SayssService;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestAttribute;
|
||||
|
||||
import static com.cool.modules.agent.entity.table.AgentWhitelistEntityTableDef.AGENT_WHITELIST_ENTITY;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "代理", description = "App端代理")
|
||||
@CoolRestController(api = {"list"})
|
||||
@@ -24,6 +28,7 @@ public class AppAgentInfoController {
|
||||
|
||||
private final AgentInfoService agentInfoService;
|
||||
private final AgentWhitelistService agentWhitelistService;
|
||||
private final SayssService sayssService;
|
||||
|
||||
@Operation(summary = "代理列表")
|
||||
@GetMapping("/list")
|
||||
@@ -58,13 +63,23 @@ public class AppAgentInfoController {
|
||||
|
||||
@Operation(summary = "添加白名单IP")
|
||||
@PostMapping("/addWhitelist")
|
||||
public R addWhitelist(@RequestAttribute JSONObject requestParams) {
|
||||
public R addWhitelist(@RequestAttribute JSONObject requestParams, HttpServletRequest request) {
|
||||
Long agentInfoId = requestParams.getLong("agentInfoId");
|
||||
String ipAddress = requestParams.getStr("ipAddress");
|
||||
|
||||
Long sayssTrustedIpId = sayssService.addTrustedIp(agentInfoId, ipAddress);
|
||||
if (sayssTrustedIpId == null) {
|
||||
log.warn("SaySS添加白名单失败,agentInfoId={}, ip={}", agentInfoId, ipAddress);
|
||||
return R.error("添加白名单到SaySS失败,请检查代理账号是否正确");
|
||||
}
|
||||
|
||||
Long addedByUserId = CoolSecurityUtil.getCurrentUserId();
|
||||
|
||||
AgentWhitelistEntity entity = new AgentWhitelistEntity();
|
||||
entity.setAgentInfoId(agentInfoId);
|
||||
entity.setIpAddress(ipAddress);
|
||||
entity.setAddedByUserId(addedByUserId);
|
||||
entity.setSayssTrustedIpId(sayssTrustedIpId);
|
||||
agentWhitelistService.save(entity);
|
||||
|
||||
return R.ok(entity);
|
||||
|
||||
@@ -28,4 +28,7 @@ public class AgentInfoEntity extends TenantEntity<AgentInfoEntity> {
|
||||
|
||||
@ColumnDefine(comment = "Token状态 0:失效 1:生效", defaultValue = "0")
|
||||
private Integer tokenStatus;
|
||||
|
||||
@ColumnDefine(comment = "SaySS认证Token(auth_data)")
|
||||
private String sayssAuthData;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,15 @@ public class AgentWhitelistEntity extends TenantEntity<AgentWhitelistEntity> {
|
||||
@ColumnDefine(comment = "IP地址", notNull = true)
|
||||
private String ipAddress;
|
||||
|
||||
@ColumnDefine(comment = "添加者用户ID")
|
||||
private Long addedByUserId;
|
||||
|
||||
@ColumnDefine(comment = "SaySS白名单记录ID")
|
||||
private Long sayssTrustedIpId;
|
||||
|
||||
@com.mybatisflex.annotation.Column(ignore = true)
|
||||
private String agentName;
|
||||
|
||||
@com.mybatisflex.annotation.Column(ignore = true)
|
||||
private String addedByName;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.cool.modules.agent.service;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.Mode;
|
||||
import cn.hutool.crypto.Padding;
|
||||
import cn.hutool.crypto.symmetric.AES;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.cool.modules.agent.entity.AgentInfoEntity;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@ConfigurationProperties(prefix = "sayss")
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
public class SayssService {
|
||||
|
||||
private String middlewareUrl;
|
||||
private String middlewareKey;
|
||||
private String middlewarePath;
|
||||
private boolean enabled;
|
||||
|
||||
private final AgentInfoService agentInfoService;
|
||||
|
||||
private String encryptPath(String path, String iv) {
|
||||
AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, middlewareKey.getBytes(), iv.getBytes());
|
||||
return aes.encryptBase64(path);
|
||||
}
|
||||
|
||||
private String[] buildEncryptedUrlWithIv(String path) {
|
||||
String iv = RandomUtil.randomString("0123456789abcdef", 16);
|
||||
String encrypted = encryptPath(path, iv);
|
||||
String encoded = Base64.encode(encrypted);
|
||||
return new String[]{middlewareUrl + middlewarePath + "/" + encoded, iv};
|
||||
}
|
||||
|
||||
private String login(String email, String password) {
|
||||
if (!enabled) return null;
|
||||
try {
|
||||
String[] urlAndIv = buildEncryptedUrlWithIv("/passport/auth/login");
|
||||
JSONObject body = new JSONObject();
|
||||
body.set("email", email);
|
||||
body.set("password", password);
|
||||
HttpResponse resp = HttpRequest.post(urlAndIv[0])
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("X-IV", urlAndIv[1])
|
||||
.body(body.toString())
|
||||
.timeout(10000).execute();
|
||||
JSONObject json = JSONUtil.parseObj(resp.body());
|
||||
if ("success".equals(json.getStr("status")) && json.get("data") != null) {
|
||||
return json.getJSONObject("data").getStr("auth_data");
|
||||
}
|
||||
log.error("SaySS登录失败: {}", json.getStr("message"));
|
||||
} catch (Exception e) {
|
||||
log.error("SaySS登录异常: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String loginAndSave(Long agentInfoId) {
|
||||
if (!enabled) return null;
|
||||
AgentInfoEntity agent = (AgentInfoEntity) agentInfoService.info(agentInfoId);
|
||||
if (agent == null || StrUtil.isBlank(agent.getUsername()) || StrUtil.isBlank(agent.getPassword())) {
|
||||
return null;
|
||||
}
|
||||
String authData = login(agent.getUsername(), agent.getPassword());
|
||||
if (authData != null) {
|
||||
agent.setSayssAuthData(authData);
|
||||
agent.setTokenStatus(1);
|
||||
agentInfoService.update(agent);
|
||||
}
|
||||
return authData;
|
||||
}
|
||||
|
||||
public boolean checkTokenValid(Long agentInfoId) {
|
||||
if (!enabled) return false;
|
||||
AgentInfoEntity agent = (AgentInfoEntity) agentInfoService.info(agentInfoId);
|
||||
if (agent == null || StrUtil.isBlank(agent.getSayssAuthData())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
String[] urlAndIv = buildEncryptedUrlWithIv("/user/info");
|
||||
HttpResponse resp = HttpRequest.get(urlAndIv[0])
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("X-IV", urlAndIv[1])
|
||||
.header("Authorization", agent.getSayssAuthData())
|
||||
.timeout(10000).execute();
|
||||
JSONObject json = JSONUtil.parseObj(resp.body());
|
||||
if ("未登录或登陆已过期".equals(json.getStr("message"))) {
|
||||
agent.setSayssAuthData(null);
|
||||
agent.setTokenStatus(0);
|
||||
agentInfoService.update(agent);
|
||||
return false;
|
||||
}
|
||||
boolean valid = "success".equals(json.getStr("status"));
|
||||
if (!valid) {
|
||||
agent.setTokenStatus(0);
|
||||
agentInfoService.update(agent);
|
||||
}
|
||||
return valid;
|
||||
} catch (Exception e) {
|
||||
log.error("SaySS验证Token异常: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject sayssGet(String authData, String path) {
|
||||
if (!enabled) return new JSONObject().set("status", "error").set("message", "SaySS未启用");
|
||||
try {
|
||||
String[] urlAndIv = buildEncryptedUrlWithIv(path);
|
||||
HttpResponse resp = HttpRequest.get(urlAndIv[0])
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("X-IV", urlAndIv[1])
|
||||
.header("Authorization", authData)
|
||||
.timeout(10000).execute();
|
||||
return JSONUtil.parseObj(resp.body());
|
||||
} catch (Exception e) {
|
||||
log.error("SaySS GET请求失败: {}", e.getMessage());
|
||||
return new JSONObject().set("status", "error").set("message", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject sayssPost(String authData, String path, Object body) {
|
||||
if (!enabled) return new JSONObject().set("status", "error").set("message", "SaySS未启用");
|
||||
try {
|
||||
String[] urlAndIv = buildEncryptedUrlWithIv(path);
|
||||
HttpResponse resp = HttpRequest.post(urlAndIv[0])
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("X-IV", urlAndIv[1])
|
||||
.header("Authorization", authData)
|
||||
.body(body instanceof String ? (String) body : body.toString())
|
||||
.timeout(10000).execute();
|
||||
return JSONUtil.parseObj(resp.body());
|
||||
} catch (Exception e) {
|
||||
log.error("SaySS POST请求失败: {}", e.getMessage());
|
||||
return new JSONObject().set("status", "error").set("message", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject sayssDelete(String authData, String path) {
|
||||
if (!enabled) return new JSONObject().set("status", "error").set("message", "SaySS未启用");
|
||||
try {
|
||||
String[] urlAndIv = buildEncryptedUrlWithIv(path);
|
||||
HttpResponse resp = HttpRequest.delete(urlAndIv[0])
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("X-IV", urlAndIv[1])
|
||||
.header("Authorization", authData)
|
||||
.timeout(10000).execute();
|
||||
return JSONUtil.parseObj(resp.body());
|
||||
} catch (Exception e) {
|
||||
log.error("SaySS DELETE请求失败: {}", e.getMessage());
|
||||
return new JSONObject().set("status", "error").set("message", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getAuthData(Long agentInfoId) {
|
||||
AgentInfoEntity agent = (AgentInfoEntity) agentInfoService.info(agentInfoId);
|
||||
if (agent == null || StrUtil.isBlank(agent.getUsername()) || StrUtil.isBlank(agent.getPassword())) {
|
||||
return null;
|
||||
}
|
||||
if (StrUtil.isNotBlank(agent.getSayssAuthData())) {
|
||||
return agent.getSayssAuthData();
|
||||
}
|
||||
String authData = login(agent.getUsername(), agent.getPassword());
|
||||
if (authData != null) {
|
||||
agent.setSayssAuthData(authData);
|
||||
agent.setTokenStatus(1);
|
||||
agentInfoService.update(agent);
|
||||
}
|
||||
return authData;
|
||||
}
|
||||
|
||||
public Long addTrustedIp(Long agentInfoId, String ip) {
|
||||
if (!enabled) {
|
||||
log.warn("SaySS未启用,跳过添加白名单到SaySS");
|
||||
return null;
|
||||
}
|
||||
String authData = getAuthData(agentInfoId);
|
||||
if (authData == null) {
|
||||
log.error("无法获取SaySS认证信息,agentInfoId={}", agentInfoId);
|
||||
return null;
|
||||
}
|
||||
JSONObject body = new JSONObject();
|
||||
body.set("ip", ip);
|
||||
body.set("ip_type", "home");
|
||||
JSONObject result = sayssPost(authData, "/user/subscribe-guard/trusted-ips", body);
|
||||
if ("success".equals(result.getStr("status")) && result.get("data") != null) {
|
||||
Object data = result.get("data");
|
||||
if (data instanceof JSONObject) {
|
||||
return ((JSONObject) data).getLong("id");
|
||||
}
|
||||
}
|
||||
log.error("SaySS添加白名单失败: {}", result.getStr("message"));
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean deleteTrustedIp(Long agentInfoId, Long sayssTrustedIpId) {
|
||||
if (!enabled) {
|
||||
log.warn("SaySS未启用,跳过从SaySS删除白名单");
|
||||
return true;
|
||||
}
|
||||
String authData = getAuthData(agentInfoId);
|
||||
if (authData == null) {
|
||||
log.error("无法获取SaySS认证信息,agentInfoId={}", agentInfoId);
|
||||
return false;
|
||||
}
|
||||
if (sayssTrustedIpId == null) {
|
||||
log.warn("SaySS白名单记录ID为空,无法从SaySS删除");
|
||||
return false;
|
||||
}
|
||||
JSONObject result = sayssDelete(authData, "/user/subscribe-guard/trusted-ips/" + sayssTrustedIpId);
|
||||
if ("success".equals(result.getStr("status"))) {
|
||||
return true;
|
||||
}
|
||||
log.error("SaySS删除白名单失败: {}", result.getStr("message"));
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean deleteTrustedIpByIp(Long agentInfoId, String ip) {
|
||||
if (!enabled) {
|
||||
log.warn("SaySS未启用,跳过从SaySS删除白名单");
|
||||
return true;
|
||||
}
|
||||
Long trustedIpId = findTrustedIpId(agentInfoId, ip);
|
||||
if (trustedIpId == null) {
|
||||
log.warn("在SaySS中未找到IP: {},可能已被删除", ip);
|
||||
return true;
|
||||
}
|
||||
return deleteTrustedIp(agentInfoId, trustedIpId);
|
||||
}
|
||||
|
||||
public Long findTrustedIpId(Long agentInfoId, String ip) {
|
||||
if (!enabled) return null;
|
||||
String authData = getAuthData(agentInfoId);
|
||||
if (authData == null) return null;
|
||||
JSONObject result = sayssGet(authData, "/user/subscribe-guard/trusted-ips");
|
||||
if ("success".equals(result.getStr("status")) && result.get("data") != null) {
|
||||
Object data = result.get("data");
|
||||
JSONArray list = data instanceof JSONArray ? (JSONArray) data : new JSONArray().put(data);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
JSONObject item = list.getJSONObject(i);
|
||||
if (ip.equals(item.getStr("ip_address"))) {
|
||||
return item.getLong("id");
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -173,4 +173,11 @@ qinglong:
|
||||
admin-username: wol
|
||||
admin-password: "123456"
|
||||
admin-token:
|
||||
token:
|
||||
token:
|
||||
|
||||
# SaySS配置
|
||||
sayss:
|
||||
enabled: true
|
||||
middleware-url: https://cluster.epayudt.com
|
||||
middleware-key: 96a6ad9f5e7bf48b
|
||||
middleware-path: /newsay
|
||||
Reference in New Issue
Block a user