Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2d390f5

Browse files
author
Thomasr
committed
Encrypt payload to the node-server and add header "X-ENCRYPTED"
1 parentd82fa15 commit2d390f5

File tree

8 files changed

+158
-24
lines changed

8 files changed

+158
-24
lines changed

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ public interface EncryptionService {
44

55
StringencryptString(Stringplaintext);
66

7+
StringencryptStringForNodeServer(Stringplaintext);
8+
79
StringdecryptString(StringencryptedText);
810

911
StringencryptPassword(Stringplaintext);

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
importorg.lowcoder.sdk.config.CommonConfig;
66
importorg.lowcoder.sdk.config.CommonConfig.Encrypt;
77
importorg.springframework.beans.factory.annotation.Autowired;
8+
importorg.springframework.beans.factory.annotation.Value;
89
importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
910
importorg.springframework.security.crypto.encrypt.Encryptors;
1011
importorg.springframework.security.crypto.encrypt.TextEncryptor;
@@ -14,13 +15,20 @@
1415
publicclassEncryptionServiceImplimplementsEncryptionService {
1516

1617
privatefinalTextEncryptortextEncryptor;
18+
privatefinalTextEncryptortextEncryptorForNodeServer;
1719
privatefinalBCryptPasswordEncoderbCryptPasswordEncoder =newBCryptPasswordEncoder();
1820

1921
@Autowired
20-
publicEncryptionServiceImpl(CommonConfigcommonConfig) {
22+
publicEncryptionServiceImpl(
23+
CommonConfigcommonConfig,
24+
@Value("${lowcoder.node-server.password}")Stringpassword,
25+
@Value("${lowcoder.node-server.salt}")Stringsalt
26+
) {
2127
Encryptencrypt =commonConfig.getEncrypt();
2228
StringsaltInHex =Hex.encodeHexString(encrypt.getSalt().getBytes());
2329
this.textEncryptor =Encryptors.text(encrypt.getPassword(),saltInHex);
30+
StringsaltInHexForNodeServer =Hex.encodeHexString(salt.getBytes());
31+
this.textEncryptorForNodeServer =Encryptors.text(password,saltInHexForNodeServer);
2432
}
2533

2634
@Override
@@ -30,6 +38,13 @@ public String encryptString(String plaintext) {
3038
}
3139
returntextEncryptor.encrypt(plaintext);
3240
}
41+
@Override
42+
publicStringencryptStringForNodeServer(Stringplaintext) {
43+
if (StringUtils.isEmpty(plaintext)) {
44+
returnplaintext;
45+
}
46+
returntextEncryptorForNodeServer.encrypt(plaintext);
47+
}
3348

3449
@Override
3550
publicStringdecryptString(StringencryptedText) {

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java‎

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
importorg.apache.commons.collections4.CollectionUtils;
66
importorg.apache.commons.collections4.MapUtils;
77
importorg.apache.commons.lang3.StringUtils;
8+
importorg.lowcoder.domain.encryption.EncryptionService;
89
importorg.lowcoder.domain.plugin.client.dto.DatasourcePluginDefinition;
910
importorg.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO;
1011
importorg.lowcoder.infra.js.NodeServerClient;
@@ -30,6 +31,8 @@
3031

3132
importstaticorg.lowcoder.sdk.constants.GlobalContext.REQUEST;
3233

34+
importcom.fasterxml.jackson.databind.ObjectMapper;
35+
3336
@Slf4j
3437
@RequiredArgsConstructor
3538
@Component
@@ -46,12 +49,15 @@ public class DatasourcePluginClient implements NodeServerClient {
4649

4750
privatefinalCommonConfigHelpercommonConfigHelper;
4851
privatefinalNodeServerHelpernodeServerHelper;
52+
privatefinalEncryptionServiceencryptionService;
4953

5054
privatestaticfinalStringPLUGINS_PATH ="plugins";
5155
privatestaticfinalStringRUN_PLUGIN_QUERY ="runPluginQuery";
5256
privatestaticfinalStringVALIDATE_PLUGIN_DATA_SOURCE_CONFIG ="validatePluginDataSourceConfig";
5357
privatestaticfinalStringGET_PLUGIN_DYNAMIC_CONFIG ="getPluginDynamicConfig";
5458

59+
privatestaticfinalObjectMapperOBJECT_MAPPER =newObjectMapper();
60+
5561
publicMono<List<Object>>getPluginDynamicConfigSafely(List<GetPluginDynamicConfigRequestDTO>getPluginDynamicConfigRequestDTOS) {
5662
returngetPluginDynamicConfig(getPluginDynamicConfigRequestDTOS)
5763
.onErrorResume(throwable -> {
@@ -119,21 +125,37 @@ public Flux<DatasourcePluginDefinition> getDatasourcePluginDefinitions() {
119125
@SuppressWarnings("unchecked")
120126
publicMono<QueryExecutionResult>executeQuery(StringpluginName,ObjectqueryDsl,List<Map<String,Object>>context,ObjectdatasourceConfig) {
121127
returngetAcceptLanguage()
122-
.flatMap(language ->WEB_CLIENT
123-
.post()
124-
.uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY))
125-
.header(HttpHeaders.ACCEPT_LANGUAGE,language)
126-
.bodyValue(Map.of("pluginName",pluginName,"dsl",queryDsl,"context",context,"dataSourceConfig",datasourceConfig))
127-
.exchangeToMono(response -> {
128-
if (response.statusCode().is2xxSuccessful()) {
129-
returnresponse.bodyToMono(Map.class)
130-
.map(map ->map.get("result"))
131-
.map(QueryExecutionResult::success);
132-
}
133-
returnresponse.bodyToMono(Map.class)
134-
.map(map ->MapUtils.getString(map,"message"))
135-
.map(QueryExecutionResult::errorWithMessage);
136-
}));
128+
.flatMap(language -> {
129+
try {
130+
Map<String,Object>body =Map.of(
131+
"pluginName",pluginName,
132+
"dsl",queryDsl,
133+
"context",context,
134+
"dataSourceConfig",datasourceConfig
135+
);
136+
Stringjson =OBJECT_MAPPER.writeValueAsString(body);
137+
Stringencrypted =encryptionService.encryptStringForNodeServer(json);
138+
returnWEB_CLIENT
139+
.post()
140+
.uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY))
141+
.header(HttpHeaders.ACCEPT_LANGUAGE,language)
142+
.header("X-Encrypted","true")// Optional: custom header to indicate encryption
143+
.bodyValue(encrypted)
144+
.exchangeToMono(response -> {
145+
if (response.statusCode().is2xxSuccessful()) {
146+
returnresponse.bodyToMono(Map.class)
147+
.map(map ->map.get("result"))
148+
.map(QueryExecutionResult::success);
149+
}
150+
returnresponse.bodyToMono(Map.class)
151+
.map(map ->MapUtils.getString(map,"message"))
152+
.map(QueryExecutionResult::errorWithMessage);
153+
});
154+
}catch (Exceptione) {
155+
log.error("Encryption error",e);
156+
returnMono.error(newServerException("Encryption error"));
157+
}
158+
});
137159
}
138160

139161
@SuppressWarnings("unchecked")

‎server/api-service/lowcoder-server/src/main/resources/application-debug.yaml‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,9 @@ logging:
6161
org.lowcoder:debug
6262

6363
default:
64-
query-timeout:${LOWCODER_DEFAULT_QUERY_TIMEOUT:10s}
64+
query-timeout:${LOWCODER_DEFAULT_QUERY_TIMEOUT:10s}
65+
66+
lowcoder:
67+
node-server:
68+
password:${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd}
69+
salt:${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt}

‎server/api-service/lowcoder-server/src/main/resources/application.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,8 @@ management:
130130
enabled:true
131131
diskspace:
132132
enabled:false
133+
134+
lowcoder:
135+
node-server:
136+
password:${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd}
137+
salt:${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt}

‎server/node-service/src/controllers/plugins.ts‎

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@ import { Request, Response } from "express";
33
import_from"lodash";
44
import{Config}from"lowcoder-sdk/dataSource";
55
import*aspluginServicesfrom"../services/plugin";
6+
// Add import for decryption utility
7+
import{decryptString}from"../utils/encryption";// <-- implement this utility as needed
8+
9+
asyncfunctiongetDecryptedBody(req:Request):Promise<any>{
10+
if(req.headers["x-encrypted"]){
11+
// Assume body is a raw encrypted string, decrypt and parse as JSON
12+
constencrypted=typeofreq.body==="string" ?req.body :req.body?.toString?.();
13+
if(!encrypted)throwbadRequest("Missing encrypted body");
14+
constdecrypted=awaitdecryptString(encrypted);
15+
try{
16+
returnJSON.parse(decrypted);
17+
}catch(e){
18+
throwbadRequest("Failed to parse decrypted body as JSON");
19+
}
20+
}
21+
returnreq.body;
22+
}
623

724
exportasyncfunctionlistPlugins(req:Request,res:Response){
825
letids=req.query["id"]||[];
@@ -15,12 +32,10 @@ export async function listPlugins(req: Request, res: Response) {
1532
}
1633

1734
exportasyncfunctionrunPluginQuery(req:Request,res:Response){
18-
const{ pluginName, dsl, context, dataSourceConfig}=req.body;
35+
constbody=awaitgetDecryptedBody(req);
36+
const{ pluginName, dsl, context, dataSourceConfig}=body;
1937
constctx=pluginServices.getPluginContext(req);
2038

21-
22-
// console.log("pluginName: ", pluginName, "dsl: ", dsl, "context: ", context, "dataSourceConfig: ", dataSourceConfig, "ctx: ", ctx);
23-
2439
constresult=awaitpluginServices.runPluginQuery(
2540
pluginName,
2641
dsl,
@@ -32,7 +47,8 @@ export async function runPluginQuery(req: Request, res: Response) {
3247
}
3348

3449
exportasyncfunctionvalidatePluginDataSourceConfig(req:Request,res:Response){
35-
const{ pluginName, dataSourceConfig}=req.body;
50+
constbody=awaitgetDecryptedBody(req);
51+
const{ pluginName, dataSourceConfig}=body;
3652
constctx=pluginServices.getPluginContext(req);
3753
constresult=awaitpluginServices.validatePluginDataSourceConfig(
3854
pluginName,
@@ -50,10 +66,11 @@ type GetDynamicDefReqBody = {
5066

5167
exportasyncfunctiongetDynamicDef(req:Request,res:Response){
5268
constctx=pluginServices.getPluginContext(req);
53-
if(!Array.isArray(req.body)){
69+
constbody=awaitgetDecryptedBody(req);
70+
if(!Array.isArray(body)){
5471
throwbadRequest("request body is not a valid array");
5572
}
56-
constfields=req.bodyasGetDynamicDefReqBody;
73+
constfields=bodyasGetDynamicDefReqBody;
5774
constresult:Config[]=[];
5875
for(constitemoffields){
5976
constdef=awaitpluginServices.getDynamicConfigDef(

‎server/node-service/src/server.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { collectDefaultMetrics } from "prom-client";
99
importapiRouterfrom"./routes/apiRouter";
1010
importsystemRouterfrom"./routes/systemRouter";
1111
importcors,{CorsOptions}from"cors";
12+
importbodyParserfrom"body-parser";
1213
collectDefaultMetrics();
1314

1415
constprefix="/node-service";
@@ -32,6 +33,15 @@ router.use(morgan("dev"));
3233
/** Parse the request */
3334
router.use(express.urlencoded({extended:false}));
3435

36+
/** Custom middleware: use raw body for encrypted requests */
37+
router.use((req,res,next)=>{
38+
if(req.headers["x-encrypted"]){
39+
bodyParser.text({type:"*/*"})(req,res,next);
40+
}else{
41+
bodyParser.json()(req,res,next);
42+
}
43+
});
44+
3545
/** Takes care of JSON data */
3646
router.use(
3747
express.json({
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import{createDecipheriv,createHash}from"crypto";
2+
import{badRequest}from"../common/error";
3+
4+
// Spring's Encryptors.text uses AES-256-CBC with a key derived from password and salt (hex).
5+
// The encrypted string format is: hex(salt) + encryptedBase64
6+
// See: https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/encrypt/Encryptors.html
7+
8+
constALGORITHM="aes-256-cbc";
9+
constKEY_LENGTH=32;// 256 bits
10+
constIV_LENGTH=16;// 128 bits
11+
12+
// You must set these to match your Java config:
13+
constPASSWORD=process.env.LOWCODER_NODE_SERVICE_SECRET||"lowcoderpwd";
14+
constSALT_HEX=process.env.LOWCODER_NODE_SERVICE_SECRET_SALT||"lowcodersalt";
15+
16+
/**
17+
* Convert a string to its binary representation, then to a hex string.
18+
*/
19+
functionstringToHexFromBinary(str:string):string{
20+
// Convert string to binary (Buffer), then to hex string
21+
returnBuffer.from(str,"utf8").toString("hex");
22+
}
23+
24+
/**
25+
* Derive key from password and salt using SHA-256 (Spring's default).
26+
*/
27+
functionderiveKey(password:string,saltHex:string):Buffer{
28+
// Convert salt string to binary, then to hex string
29+
constsaltHexFromBinary=stringToHexFromBinary(saltHex);
30+
constsalt=Buffer.from(saltHexFromBinary,"hex");
31+
consthash=createHash("sha256");
32+
hash.update(password);
33+
hash.update(salt);
34+
returnhash.digest();
35+
}
36+
37+
/**
38+
* Decrypt a string encrypted by Spring's Encryptors.text.
39+
*/
40+
exportasyncfunctiondecryptString(encrypted:string):Promise<string>{
41+
try{
42+
// Spring's format: hex(salt) + encryptedBase64
43+
// But if you know salt, encrypted is just Base64(IV + ciphertext)
44+
constkey=deriveKey(PASSWORD,SALT_HEX);
45+
46+
// Spring's Encryptors.text prepends a random IV (16 bytes) to the ciphertext, all base64 encoded.
47+
constencryptedBuf=Buffer.from(encrypted,"base64");
48+
constiv=encryptedBuf.slice(0,IV_LENGTH);
49+
constciphertext=encryptedBuf.slice(IV_LENGTH);
50+
51+
constdecipher=createDecipheriv(ALGORITHM,key,iv);
52+
letdecrypted=decipher.update(ciphertext,undefined,"utf8");
53+
decrypted+=decipher.final("utf8");
54+
returndecrypted;
55+
}catch(e){
56+
throwbadRequest("Failed to decrypt string");
57+
}
58+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp