1. iwork 二次适配
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
package com.zt.plat.module.system.controller.admin.integration.iwork;
|
package com.zt.plat.module.system.controller.admin.integration.iwork;
|
||||||
|
|
||||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterRespVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDepartmentQueryReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkJobTitleQueryReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
|
||||||
@@ -39,6 +43,18 @@ public class IWorkIntegrationController {
|
|||||||
private final IWorkIntegrationService integrationService;
|
private final IWorkIntegrationService integrationService;
|
||||||
private final IWorkOrgRestService orgRestService;
|
private final IWorkOrgRestService orgRestService;
|
||||||
|
|
||||||
|
@PostMapping("/auth/register")
|
||||||
|
@Operation(summary = "注册 iWork 凭证,获取服务端公钥与 secret")
|
||||||
|
public CommonResult<IWorkAuthRegisterRespVO> register(@Valid @RequestBody IWorkAuthRegisterReqVO reqVO) {
|
||||||
|
return success(integrationService.registerSession(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/auth/token")
|
||||||
|
@Operation(summary = "申请 iWork Token(独立接口)")
|
||||||
|
public CommonResult<IWorkAuthTokenRespVO> acquireToken(@Valid @RequestBody IWorkAuthTokenReqVO reqVO) {
|
||||||
|
return success(integrationService.acquireToken(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/user/resolve")
|
@PostMapping("/user/resolve")
|
||||||
@Operation(summary = "根据外部标识获取 iWork 用户编号")
|
@Operation(summary = "根据外部标识获取 iWork 用户编号")
|
||||||
public CommonResult<IWorkUserInfoRespVO> resolveUser(@Valid @RequestBody IWorkUserInfoReqVO reqVO) {
|
public CommonResult<IWorkUserInfoRespVO> resolveUser(@Valid @RequestBody IWorkUserInfoReqVO reqVO) {
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求重新向 iWork 注册以换取服务端公钥与 secret。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkAuthRegisterReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "是否强制刷新注册信息", example = "false")
|
||||||
|
private Boolean forceRefreshRegistration;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回 iWork 注册后的公钥与密钥信息。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkAuthRegisterRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "使用的 iWork 应用编号", example = "iwork-app")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
@Schema(description = "本地配置的客户端公钥(Base64)")
|
||||||
|
private String clientPublicKey;
|
||||||
|
|
||||||
|
@Schema(description = "自动生成的客户端私钥(Base64),仅在未配置固定公钥时返回")
|
||||||
|
private String clientPrivateKey;
|
||||||
|
|
||||||
|
@Schema(description = "iWork 返回的 server public key(Base64)")
|
||||||
|
private String serverPublicKey;
|
||||||
|
|
||||||
|
@Schema(description = "iWork 返回的 secret,用于后续申请 token")
|
||||||
|
private String secret;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申请 iWork token 的请求参数。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class IWorkAuthTokenReqVO extends IWorkBaseReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "是否强制重新执行注册流程")
|
||||||
|
private Boolean forceRefreshRegistration;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申请 token 的返回结果。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class IWorkAuthTokenRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "使用的 iWork appId", example = "iwork-app")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
@Schema(description = "作为操作人的 iWork 用户编号", example = "1")
|
||||||
|
private String operatorUserId;
|
||||||
|
|
||||||
|
@Schema(description = "iWork 返回的访问 token")
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Schema(description = "与 token 匹配的加密 userId,供 header 直接使用")
|
||||||
|
private String encryptedUserId;
|
||||||
|
|
||||||
|
@Schema(description = "token 预计过期时间(Epoch 秒)")
|
||||||
|
private Long expiresAtEpochSecond;
|
||||||
|
|
||||||
|
@Schema(description = "当前会话对应的 server public key")
|
||||||
|
private String serverPublicKey;
|
||||||
|
}
|
||||||
@@ -85,12 +85,12 @@ public class IWorkProperties {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public static class Headers {
|
public static class Headers {
|
||||||
private final String appId = "app-id";
|
private final String appId = "appid";
|
||||||
private final String clientPublicKey = "client-public-key";
|
private final String clientPublicKey = "cpk";
|
||||||
private final String secret = "secret";
|
private final String secret = "secret";
|
||||||
private final String token = "token";
|
private final String token = "token";
|
||||||
private final String time = "time";
|
private final String time = "time";
|
||||||
private final String userId = "user-id";
|
private final String userId = "userid";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public interface IWorkIntegrationErrorCodeConstants {
|
|||||||
|
|
||||||
ErrorCode IWORK_BASE_URL_MISSING = new ErrorCode(1_010_200_001, "iWork 集成未配置网关地址");
|
ErrorCode IWORK_BASE_URL_MISSING = new ErrorCode(1_010_200_001, "iWork 集成未配置网关地址");
|
||||||
ErrorCode IWORK_CONFIGURATION_INVALID = new ErrorCode(1_010_200_002,
|
ErrorCode IWORK_CONFIGURATION_INVALID = new ErrorCode(1_010_200_002,
|
||||||
"iWork 集成缺少必填配置(appId/clientPublicKey/userId/workflowId)");
|
"iWork 集成缺少必填配置(appId/userId/workflowId)或配置无效");
|
||||||
ErrorCode IWORK_REGISTER_FAILED = new ErrorCode(1_010_200_003, "iWork 注册授权失败");
|
ErrorCode IWORK_REGISTER_FAILED = new ErrorCode(1_010_200_003, "iWork 注册授权失败");
|
||||||
ErrorCode IWORK_APPLY_TOKEN_FAILED = new ErrorCode(1_010_200_004, "iWork 令牌申请失败");
|
ErrorCode IWORK_APPLY_TOKEN_FAILED = new ErrorCode(1_010_200_004, "iWork 令牌申请失败");
|
||||||
ErrorCode IWORK_REMOTE_REQUEST_FAILED = new ErrorCode(1_010_200_005, "iWork 接口请求失败");
|
ErrorCode IWORK_REMOTE_REQUEST_FAILED = new ErrorCode(1_010_200_005, "iWork 接口请求失败");
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package com.zt.plat.module.system.service.integration.iwork;
|
package com.zt.plat.module.system.service.integration.iwork;
|
||||||
|
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthRegisterRespVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkAuthTokenRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoReqVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoRespVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoRespVO;
|
||||||
@@ -11,6 +15,16 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWork
|
|||||||
*/
|
*/
|
||||||
public interface IWorkIntegrationService {
|
public interface IWorkIntegrationService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主动触发注册流程,获取 iWork 返回的服务端公钥与 secret。
|
||||||
|
*/
|
||||||
|
IWorkAuthRegisterRespVO registerSession(IWorkAuthRegisterReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主动向 iWork 申请访问 token,并返回相关会话信息。
|
||||||
|
*/
|
||||||
|
IWorkAuthTokenRespVO acquireToken(IWorkAuthTokenReqVO reqVO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据外部标识解析 iWork 内部用户编号。
|
* 根据外部标识解析 iWork 内部用户编号。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,27 +9,14 @@ import com.github.benmanes.caffeine.cache.Caffeine;
|
|||||||
import com.zt.plat.framework.common.exception.ErrorCode;
|
import com.zt.plat.framework.common.exception.ErrorCode;
|
||||||
import com.zt.plat.framework.common.exception.ServiceException;
|
import com.zt.plat.framework.common.exception.ServiceException;
|
||||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDetailRecordVO;
|
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDetailTableVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFormFieldVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoReqVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoRespVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCreateReqVO;
|
|
||||||
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowVoidReqVO;
|
|
||||||
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
|
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
|
||||||
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
|
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.FormBody;
|
import okhttp3.*;
|
||||||
import okhttp3.Headers;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
@@ -39,16 +26,10 @@ import org.springframework.util.StringUtils;
|
|||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.KeyFactory;
|
import java.security.*;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Base64;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.*;
|
import static com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationErrorCodeConstants.*;
|
||||||
|
|
||||||
@@ -71,21 +52,68 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
.maximumSize(256)
|
.maximumSize(256)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
private final Cache<String, RegistrationState> registrationCache = Caffeine.newBuilder()
|
||||||
|
.maximumSize(32)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final Cache<String, ClientKeyPair> clientKeyCache = Caffeine.newBuilder()
|
||||||
|
.maximumSize(32)
|
||||||
|
.build();
|
||||||
|
|
||||||
private final Cache<String, PublicKey> publicKeyCache = Caffeine.newBuilder()
|
private final Cache<String, PublicKey> publicKeyCache = Caffeine.newBuilder()
|
||||||
.maximumSize(64)
|
.maximumSize(64)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private volatile OkHttpClient cachedHttpClient;
|
private volatile OkHttpClient cachedHttpClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IWorkAuthRegisterRespVO registerSession(IWorkAuthRegisterReqVO reqVO) {
|
||||||
|
assertConfigured();
|
||||||
|
String appId = resolveAppId();
|
||||||
|
boolean forceRefresh = reqVO != null && Boolean.TRUE.equals(reqVO.getForceRefreshRegistration());
|
||||||
|
ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, forceRefresh);
|
||||||
|
RegistrationState registration = obtainRegistration(appId, clientKeyPair, forceRefresh);
|
||||||
|
|
||||||
|
IWorkAuthRegisterRespVO respVO = new IWorkAuthRegisterRespVO();
|
||||||
|
respVO.setAppId(appId);
|
||||||
|
respVO.setClientPublicKey(clientKeyPair.publicKey());
|
||||||
|
respVO.setClientPrivateKey(clientKeyPair.privateKey());
|
||||||
|
respVO.setServerPublicKey(registration.spk());
|
||||||
|
respVO.setSecret(registration.secret());
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IWorkAuthTokenRespVO acquireToken(IWorkAuthTokenReqVO reqVO) {
|
||||||
|
assertConfigured();
|
||||||
|
String appId = resolveAppId();
|
||||||
|
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||||
|
boolean forceRegistration = Boolean.TRUE.equals(reqVO.getForceRefreshRegistration());
|
||||||
|
boolean forceToken = Boolean.TRUE.equals(reqVO.getForceRefreshToken());
|
||||||
|
boolean force = forceRegistration || forceToken;
|
||||||
|
|
||||||
|
ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, forceRegistration);
|
||||||
|
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, force);
|
||||||
|
|
||||||
|
IWorkAuthTokenRespVO respVO = new IWorkAuthTokenRespVO();
|
||||||
|
respVO.setAppId(appId);
|
||||||
|
respVO.setOperatorUserId(operatorUserId);
|
||||||
|
respVO.setToken(session.getToken());
|
||||||
|
respVO.setEncryptedUserId(session.getEncryptedUserId());
|
||||||
|
respVO.setExpiresAtEpochSecond(session.getExpiresAt().getEpochSecond());
|
||||||
|
respVO.setServerPublicKey(session.getSpk());
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IWorkUserInfoRespVO resolveUserId(IWorkUserInfoReqVO reqVO) {
|
public IWorkUserInfoRespVO resolveUserId(IWorkUserInfoReqVO reqVO) {
|
||||||
assertConfigured();
|
assertConfigured();
|
||||||
String appId = resolveAppId();
|
String appId = resolveAppId();
|
||||||
String clientPublicKey = resolveClientPublicKey();
|
ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||||
ensureIdentifier(reqVO.getIdentifierKey(), reqVO.getIdentifierValue());
|
ensureIdentifier(reqVO.getIdentifierKey(), reqVO.getIdentifierValue());
|
||||||
|
|
||||||
IWorkSession session = ensureSession(appId, clientPublicKey, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||||
Map<String, Object> payload = buildUserPayload(reqVO);
|
Map<String, Object> payload = buildUserPayload(reqVO);
|
||||||
String responseBody = executeJsonRequest(properties.getPaths().getUserInfo(), reqVO.getQueryParams(), appId, session, payload);
|
String responseBody = executeJsonRequest(properties.getPaths().getUserInfo(), reqVO.getQueryParams(), appId, session, payload);
|
||||||
|
|
||||||
@@ -96,9 +124,9 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
public IWorkOperationRespVO createWorkflow(IWorkWorkflowCreateReqVO reqVO) {
|
public IWorkOperationRespVO createWorkflow(IWorkWorkflowCreateReqVO reqVO) {
|
||||||
assertConfigured();
|
assertConfigured();
|
||||||
String appId = resolveAppId();
|
String appId = resolveAppId();
|
||||||
String clientPublicKey = resolveClientPublicKey();
|
ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||||
IWorkSession session = ensureSession(appId, clientPublicKey, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||||
|
|
||||||
MultiValueMap<String, String> formData = buildCreateForm(reqVO);
|
MultiValueMap<String, String> formData = buildCreateForm(reqVO);
|
||||||
appendFormExtras(formData, reqVO.getFormExtras());
|
appendFormExtras(formData, reqVO.getFormExtras());
|
||||||
@@ -110,12 +138,12 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
public IWorkOperationRespVO voidWorkflow(IWorkWorkflowVoidReqVO reqVO) {
|
public IWorkOperationRespVO voidWorkflow(IWorkWorkflowVoidReqVO reqVO) {
|
||||||
assertConfigured();
|
assertConfigured();
|
||||||
String appId = resolveAppId();
|
String appId = resolveAppId();
|
||||||
String clientPublicKey = resolveClientPublicKey();
|
ClientKeyPair clientKeyPair = resolveClientKeyPair(appId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||||
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
String operatorUserId = resolveOperatorUserId(reqVO.getOperatorUserId());
|
||||||
if (!StringUtils.hasText(reqVO.getRequestId())) {
|
if (!StringUtils.hasText(reqVO.getRequestId())) {
|
||||||
throw ServiceExceptionUtil.exception(IWORK_USER_IDENTIFIER_MISSING);
|
throw ServiceExceptionUtil.exception(IWORK_USER_IDENTIFIER_MISSING);
|
||||||
}
|
}
|
||||||
IWorkSession session = ensureSession(appId, clientPublicKey, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
IWorkSession session = ensureSession(appId, clientKeyPair, operatorUserId, Boolean.TRUE.equals(reqVO.getForceRefreshToken()));
|
||||||
|
|
||||||
MultiValueMap<String, String> formData = buildVoidForm(reqVO);
|
MultiValueMap<String, String> formData = buildVoidForm(reqVO);
|
||||||
appendFormExtras(formData, reqVO.getFormExtras());
|
appendFormExtras(formData, reqVO.getFormExtras());
|
||||||
@@ -127,7 +155,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
if (!StringUtils.hasText(properties.getBaseUrl())) {
|
if (!StringUtils.hasText(properties.getBaseUrl())) {
|
||||||
throw ServiceExceptionUtil.exception(IWORK_BASE_URL_MISSING);
|
throw ServiceExceptionUtil.exception(IWORK_BASE_URL_MISSING);
|
||||||
}
|
}
|
||||||
if (!StringUtils.hasText(properties.getAppId()) || !StringUtils.hasText(properties.getClientPublicKey())) {
|
if (!StringUtils.hasText(properties.getAppId())) {
|
||||||
throw ServiceExceptionUtil.exception(IWORK_CONFIGURATION_INVALID);
|
throw ServiceExceptionUtil.exception(IWORK_CONFIGURATION_INVALID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,12 +178,33 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
return StringUtils.trimWhitespace(value);
|
return StringUtils.trimWhitespace(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveClientPublicKey() {
|
private ClientKeyPair resolveClientKeyPair(String appId, boolean forceRefresh) {
|
||||||
String value = properties.getClientPublicKey();
|
String configured = properties.getClientPublicKey();
|
||||||
if (!StringUtils.hasText(value)) {
|
if (StringUtils.hasText(configured)) {
|
||||||
throw ServiceExceptionUtil.exception(IWORK_CONFIGURATION_INVALID);
|
return new ClientKeyPair(StringUtils.trimWhitespace(configured), null);
|
||||||
|
}
|
||||||
|
if (!forceRefresh) {
|
||||||
|
ClientKeyPair cached = clientKeyCache.getIfPresent(appId);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientKeyPair generated = generateClientKeyPair();
|
||||||
|
clientKeyCache.put(appId, generated);
|
||||||
|
return generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientKeyPair generateClientKeyPair() {
|
||||||
|
try {
|
||||||
|
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
generator.initialize(2048);
|
||||||
|
KeyPair pair = generator.generateKeyPair();
|
||||||
|
String publicKey = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded());
|
||||||
|
String privateKey = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded());
|
||||||
|
return new ClientKeyPair(publicKey, privateKey);
|
||||||
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
|
throw new ServiceException(IWORK_CONFIGURATION_INVALID.getCode(), "生成客户端 RSA 公钥失败: " + ex.getMessage());
|
||||||
}
|
}
|
||||||
return StringUtils.trimWhitespace(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureIdentifier(String identifierKey, String identifierValue) {
|
private void ensureIdentifier(String identifierKey, String identifierValue) {
|
||||||
@@ -164,7 +213,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IWorkSession ensureSession(String appId, String clientPublicKey, String operatorUserId, boolean forceRefresh) {
|
private IWorkSession ensureSession(String appId, ClientKeyPair clientKeyPair, String operatorUserId, boolean forceRefresh) {
|
||||||
SessionKey key = new SessionKey(appId, operatorUserId);
|
SessionKey key = new SessionKey(appId, operatorUserId);
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
if (!forceRefresh) {
|
if (!forceRefresh) {
|
||||||
@@ -178,14 +227,14 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
if (!forceRefresh && cached != null && cached.isValid(now, properties.getToken().getRefreshAheadSeconds())) {
|
if (!forceRefresh && cached != null && cached.isValid(now, properties.getToken().getRefreshAheadSeconds())) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
IWorkSession session = createSession(appId, clientPublicKey, operatorUserId);
|
IWorkSession session = createSession(appId, clientKeyPair, operatorUserId, forceRefresh);
|
||||||
sessionCache.put(key, session);
|
sessionCache.put(key, session);
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IWorkSession createSession(String appId, String clientPublicKey, String operatorUserId) {
|
private IWorkSession createSession(String appId, ClientKeyPair clientKeyPair, String operatorUserId, boolean forceRefreshRegistration) {
|
||||||
RegistrationResult registration = register(appId, clientPublicKey);
|
RegistrationState registration = obtainRegistration(appId, clientKeyPair, forceRefreshRegistration);
|
||||||
String encryptedSecret = encryptWithPublicKey(registration.secret(), registration.spk());
|
String encryptedSecret = encryptWithPublicKey(registration.secret(), registration.spk());
|
||||||
String encryptedUserId = encryptWithPublicKey(operatorUserId, registration.spk());
|
String encryptedUserId = encryptWithPublicKey(operatorUserId, registration.spk());
|
||||||
String token = applyToken(appId, encryptedSecret);
|
String token = applyToken(appId, encryptedSecret);
|
||||||
@@ -193,11 +242,32 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
return new IWorkSession(token, encryptedUserId, expiresAt, registration.spk());
|
return new IWorkSession(token, encryptedUserId, expiresAt, registration.spk());
|
||||||
}
|
}
|
||||||
|
|
||||||
private RegistrationResult register(String appId, String clientPublicKey) {
|
private RegistrationState obtainRegistration(String appId, ClientKeyPair clientKeyPair, boolean forceRefresh) {
|
||||||
|
if (!forceRefresh) {
|
||||||
|
RegistrationState cached = registrationCache.getIfPresent(appId);
|
||||||
|
if (cached != null && Objects.equals(cached.clientKeyPair().publicKey(), clientKeyPair.publicKey())) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String lockKey = ("iwork-registration::" + appId).intern();
|
||||||
|
synchronized (lockKey) {
|
||||||
|
if (!forceRefresh) {
|
||||||
|
RegistrationState cached = registrationCache.getIfPresent(appId);
|
||||||
|
if (cached != null && Objects.equals(cached.clientKeyPair().publicKey(), clientKeyPair.publicKey())) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RegistrationState registration = register(appId, clientKeyPair);
|
||||||
|
registrationCache.put(appId, registration);
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RegistrationState register(String appId, ClientKeyPair clientKeyPair) {
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(resolveUrl(properties.getPaths().getRegister()))
|
.url(resolveUrl(properties.getPaths().getRegister()))
|
||||||
.header(properties.getHeaders().getAppId(), appId)
|
.header(properties.getHeaders().getAppId(), appId)
|
||||||
.header(properties.getHeaders().getClientPublicKey(), clientPublicKey)
|
.header(properties.getHeaders().getClientPublicKey(), clientKeyPair.publicKey())
|
||||||
.post(RequestBody.create(null, new byte[0]))
|
.post(RequestBody.create(null, new byte[0]))
|
||||||
.build();
|
.build();
|
||||||
String responseBody = executeRequest(request, IWORK_REGISTER_FAILED);
|
String responseBody = executeRequest(request, IWORK_REGISTER_FAILED);
|
||||||
@@ -207,7 +277,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
if (!StringUtils.hasText(secret) || !StringUtils.hasText(spk)) {
|
if (!StringUtils.hasText(secret) || !StringUtils.hasText(spk)) {
|
||||||
throw ServiceExceptionUtil.exception(IWORK_REGISTER_FAILED, "返回缺少 secret 或 spk");
|
throw ServiceExceptionUtil.exception(IWORK_REGISTER_FAILED, "返回缺少 secret 或 spk");
|
||||||
}
|
}
|
||||||
return new RegistrationResult(secret, spk);
|
return new RegistrationState(secret, spk, clientKeyPair);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String applyToken(String appId, String encryptedSecret) {
|
private String applyToken(String appId, String encryptedSecret) {
|
||||||
@@ -575,7 +645,10 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record RegistrationResult(String secret, String spk) {
|
private record RegistrationState(String secret, String spk, ClientKeyPair clientKeyPair) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ClientKeyPair(String publicKey, String privateKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
|||||||
@@ -107,10 +107,11 @@ easy-trans:
|
|||||||
|
|
||||||
iwork:
|
iwork:
|
||||||
base-url: http://172.16.36.233:8080
|
base-url: http://172.16.36.233:8080
|
||||||
app-id:
|
# app-id: f47ac10b-58cc-4372-a567-0e02b2c3d479
|
||||||
|
app-id: f47ac10b-58cc-4372-a567-0e02b2c3d479
|
||||||
client-public-key:
|
client-public-key:
|
||||||
user-id:
|
user-id: 9869
|
||||||
workflow-id:
|
workflow-id: 1753
|
||||||
paths:
|
paths:
|
||||||
register: /api/ec/dev/auth/regist
|
register: /api/ec/dev/auth/regist
|
||||||
apply-token: /api/ec/dev/auth/applytoken
|
apply-token: /api/ec/dev/auth/applytoken
|
||||||
@@ -169,6 +170,8 @@ xxl:
|
|||||||
job:
|
job:
|
||||||
executor:
|
executor:
|
||||||
appname: ${spring.application.name} # 执行器 AppName
|
appname: ${spring.application.name} # 执行器 AppName
|
||||||
|
port: 0
|
||||||
|
ip: 172.16.234.132
|
||||||
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
|
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
|
||||||
accessToken: default_token # 执行器通讯TOKEN
|
accessToken: default_token # 执行器通讯TOKEN
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user