バックグラウンド
DingTalk オープン プラットフォームとドッキングする場合、DingTalk オープン プラットフォームが私たちのプロジェクトをコールバックする必要がある場合があります. 通常、プロジェクトは会社のイントラネット上に展開されます. したがって、イントラネットへの侵入を実行する必要があります. 一般的に使用されるイントラネットへの侵入の
比較
この記事では、DingTalk エンタープライズアプリケーション ゲートウェイのドッキングと構築の全体的なプロセスを詳しく紹介します。
一般的に使用されるイントラネット侵入ツールの侵入比較
エンタープライズ アプリケーション ゲートウェイ
実は、以前はDingTalkもイントラネット侵入ツールを提供していましたが、セキュリティコンプライアンス、サービスリソース、保守費用などの理由により、DingTalkは2022年7月21日のサービスから、イントラネット侵入が必要な場合、イントラネット侵入ツールを提供しなくなりました。ローカルまたは開発およびテスト環境のデバッグでは,ドキュメントで自分で構築したfrp イントラネット侵入サービスを参照してください.したがって, 企業がこの要求を持っている場合, セキュリティと安定性に沿って, シンプルさの点で, DingTalk Enterprise Application Gatewayも良い選択です. また、ビジネスに連絡して、トライアルプラットフォームを構築することもできます.クリックして公式ウェブサイトのアドレスを入力してください.
とは
DingTalk ゼロ トラスト ゲートウェイとも呼ばれる Enterprise Application Gateway は、企業が外部ネットワーク上のイントラネット アプリケーションに安全にアクセスできるようにするために使用され、従来の VPN ソリューションを置き換え、Alibaba Cloud のネットワーク アクセラレーション機能に基づいてアプリケーション アクセス速度を向上させることができます。製品 ゼロトラストの概念に基づいて、継続的かつ動的なアクセス検証を提供し、企業のデジタル情報のセキュリティを最大限に確保します
ユーザーの問題点
企業は通常、コア アプリケーション システムを企業のイントラネットまたは DMZ エリアに配置し、ファイアウォールを介してネットワーク境界の分離を確立します. 企業の
従業員がインターネット側のモバイル デバイスまたは PC デバイスを介して企業のイントラネット アプリケーションにアクセスする場合、次の 2 つの方法があります: VPN ダイヤル
ただし、VPN デバイスの脆弱性または外部ネットワーク IP または URL へのポート マッピングが原因で、ハッカーはエンタープライズ アプリケーションに直接アクセスしたり攻撃したりして、エンタープライズ コア データの漏えいを引き起こす可能性があります。
以下の図に示すように、企業の元のイントラネット アプリケーション アクセス方法。
DingTalk エンタープライズ アプリケーション ゲートウェイの全体的なアーキテクチャは、下の図に示されています.
次の 5 つのソリューションは、エンタープライズ ユーザーによるイントラネット アプリケーション アクセスに推奨されます。
使い方
公式 Web サイトに慣れている場合は、エンタープライズ アプリケーション ゲートウェイの構成プロセスを参照してビルドできます。
ピットを構築して踏むプロセス
ビルドプロセス
準備
- エンタープライズ アプリケーション ゲートウェイがアクティブ化されました。アクティブ化されていない場合は、DingTalk を使用して以下の QR コードをスキャンするか、このページに入って QR コードをスキャンし、
DingTalk エンタープライズ アプリケーション ゲートウェイ アプリケーションをインストールする必要があります。
- スキャン後、次のページが表示されます.
会社がこの機能を有効にする場合、アカウント レベルは管理者である必要があります (サブ管理者は許可されません)。
エンタープライズ ゲートウェイ アプリケーションの表示
-
クライアント側との通信に成功し、上記の QR コードをスキャンした後、クライアント側は、次の図からアクティベーションが成功したことを示すプロンプト メッセージを取得できます。
-
DingTalk の管理背景 -> ワークベンチ -> サードパーティ アプリケーションでは、DingTalk セキュリティ ゲートウェイの入り口が表示されます。クリックすると、セキュリティ ゲートウェイの背景に入ります エンタープライズ ゲートウェイのホームページに入ります エンタープライズ ゲートウェイのホームページに
入り
ます
コネクタの構成
-
前提条件
Linux サーバーにデプロイする必要がある場合は、次の条件を満たす必要があることに注意してください (Windows も次の条件を満たす必要があります)。
-
コネクタの作成
[新しいコネクタ] を選択し、展開の種類を選択して [続行] をクリックし、次のコマンドを生成してから、次の Linux コマンドをコピーします。
-
Linux サーバーでコピー コマンドを実行すると、コネクタが正常に起動されていることがわかります。
-
コネクタグループを新規作成
コネクタグループに新規作成したコネクタを追加 コネクタグループの機能:コネクタを一括管理し、後で管理適用する際に利用
補足 - コネクタのセルフスタート設定
2022.12.12 補足 会社でネットワークの調整をしていたところ、ネットワークの切断でコネクタが切れてしまい、サーバーの都合で再起動したのですが、再起動してもコネクタが再起動しないという不具合が相次ぎました。 . 再起動後に自動起動の設定が必要/etc/rc.local
. 現在のサーバーはubuntuなので、以下の自動起動項目を設定する必要があります。
# 进入rc.local
vim /etc/rc.local
# 配置启动名称
## cd 后面跟的是连通器脚本所在目录, 就是执行第2步创建连通器是所在创建的目录
## ;(分号)后面执行的命令就是图2圈出来的参数!!!
cd /home/dingding-getway/connector ;./start.sh -a endpoint.ztna-dingtalk.com:8021 -k 6cc96011442d42149bdfbf95a1c61343 -s 42e4d95397dd4a75bf8b51e1ac655026df1786927f77f4a788e951b9d79fc55b &
図 1
図 2
アプリケーション管理の構成
前提条件
- 新しいアプリケーションを作成する
方法 1:管理バックグラウンドでサードパーティ アプリケーションを作成する- ワークベンチ
方法2:利用シーンに合わせてDingTalkオープンプラットフォーム上でアプリを作成する
-
必要に応じてアプリケーション情報を入力します
-
構成が成功すると、AgentId、AppKey、および AppSecret が自動的に生成され、インターフェイス接続で使用されます。
-
アプリを公開する(開発プログラムが完成した後に使用)
.アプリがリリースされた後、他のユーザーが見ることができる(ここでは、管理者のワークベンチではなく、自分のワークベンチで見ることを指す.エクスペリエンス バージョンはグレースケール リリースに属します。クリックして、アプリケーションリリースの紹介を表示します。
構成アプリケーション
-
アプリケーションを作成したら、アプリケーション管理でテスト アプリケーションを構成し、未構成のアプリケーションをクリックして構成します。
-
アプリケーション管理 - 基本構成
コネクタ オプションの "+" をクリックし、展開されたコネクタ (またはコネクタ グループ) を選択して、テスト アプリケーションとの接続を確立します。構成するドメイン名を選択します。ここでのドメイン名はイントラネット アドレスです
+ポート (複数のマッピングを構成可能)構成を保存すると、イントラネットへの侵入が可能になります!!!
詳細設定
知らせ:
- 次に、高度な設定について説明します.特別な要件がない場合は、高度な設定をスキップできます (次の手順に直接進みます)。
- ここでの構成の前提条件は、次のステップでアクセス ポリシーを構成し、構成する前に外部ネットワーク アクセス ドメイン名を取得する必要があることです。
バックグラウンドの導入により、DingTalk オープン プラットフォームは、作成されたアプリケーションにイベント コールバックを実装する必要があります (このインターフェイスは、会社の組織構造が変更されたときに呼び出されます). ただし、アクセス ポリシーを構成すると、DingTalk コールバック インターフェイスは引き続き表示され
ますurl 地址访问异常, 不允许3xxx跳转
。当社の管理プラットフォーム - アプリケーション管理で高度な設定を行う必要があります. 設定がない前に, URL を入力した後にエラーが報告されます. 内容は下の図に示されています.
- 高度な構成
追加して匿名アクセスを許可する URL: 形式は次のとおりです。https://生成的外网访问域名/事件回调sdk接口地址.*
- 構成が成功したら、
テストの前にイントラネット サーバーにイベント コールバック SDK を展開する必要があります。
アクセス ポリシーを構成する
ポリシー管理は、ポリシーの登録、ポリシーの変更、ポリシーの削除、ポリシーの非アクティブ化、ポリシーの有効化、優先順位付けなどを含め、エンタープライズ管理者によって操作されます。
- 次に、[ポリシーの作成] ボタンをクリックして、ポリシーの作成インターフェイスに入ります。
- ポリシー ページで、ポリシー情報を入力します。設定項目は次のとおりです。
- [完了] をクリックしてポリシーを作成します
外部ネットワーク アクセス用のドメイン名を取得する (ピットを踏む)
-
アクセス ポリシーを構成した後、コネクタによって表示されるパブリック ネットワーク IP を介してアクセスする代わりに、アプリケーション管理を介して新しいドメイン名を取得できます。
-
毎回構成する内部ネットワークの ip+port は、外部ネットワークからアクセスできる一意のドメイン名にマッピングされ、必要に応じて新しいドメイン名をコピーできることがわかります。
-
エンタープライズ ゲートウェイ イントラネット侵入効果のテスト
イントラネット アクセス
パブリック ネットワーク アクセス
添付:DingTalkイベントコールバックSDK
イベントコールバック紹介アドレス:https://open.dingtalk.com/document/org/configure-event-subcription
-
DingTalk オープン プラットフォームの暗号化および復号化方式
import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.Permission; import java.security.PermissionCollection; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.security.Security; import java.lang.reflect.Field; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import com.alibaba.fastjson.JSON; import org.apache.commons.codec.binary.Base64; /** * 钉钉开放平台加解密方法 * 在ORACLE官方网站下载JCE无限制权限策略文件 * JDK6的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html * JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html * JDK8的下载地址 https://www.oracle.com/java/technologies/javase-jce8-downloads.html * @Author caoHaiYang * @Date 2022/8/18 10:15 */ public class DingCallbackCrypto { private static final Charset CHARSET = Charset.forName("utf-8"); private static final Base64 base64 = new Base64(); private byte[] aesKey; private String token; private String corpId; /** * ask getPaddingBytes key固定长度 **/ private static final Integer AES_ENCODE_KEY_LENGTH = 43; /** * 加密随机字符串字节长度 **/ private static final Integer RANDOM_LENGTH = 16; /** * 构造函数 * * @param token 钉钉开放平台上,开发者设置的token * @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey * @param corpId 企业自建应用-事件订阅, 使用appKey * 企业自建应用-注册回调地址, 使用corpId * 第三方企业应用, 使用suiteKey * * @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息 */ public DingCallbackCrypto(String token, String encodingAesKey, String corpId) throws DingTalkEncryptException { if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) { throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL); } this.token = token; this.corpId = corpId; aesKey = Base64.decodeBase64(encodingAesKey + "="); } public Map<String, String> getEncryptedMap(String plaintext) throws DingTalkEncryptException { return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16)); } /** * 将和钉钉开放平台同步的消息体加密,返回加密Map * * @param plaintext 传递的消息体明文 * @param timeStamp 时间戳 * @param nonce 随机字符串 * @return * @throws DingTalkEncryptException */ public Map<String, String> getEncryptedMap(String plaintext, Long timeStamp, String nonce) throws DingTalkEncryptException { if (null == plaintext) { throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL); } if (null == timeStamp) { throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL); } if (null == nonce) { throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL); } // 加密 String encrypt = encrypt(Utils.getRandomStr(RANDOM_LENGTH), plaintext); String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt); Map<String, String> resultMap = new HashMap<String, String>(); resultMap.put("msg_signature", signature); resultMap.put("encrypt", encrypt); resultMap.put("timeStamp", String.valueOf(timeStamp)); resultMap.put("nonce", nonce); return resultMap; } /** * 密文解密 * * @param msgSignature 签名串 * @param timeStamp 时间戳 * @param nonce 随机串 * @param encryptMsg 密文 * @return 解密后的原文 * @throws DingTalkEncryptException */ public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg) throws DingTalkEncryptException { //校验签名 String signature = getSignature(token, timeStamp, nonce, encryptMsg); if (!signature.equals(msgSignature)) { throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR); } // 解密 String result = decrypt(encryptMsg); return result; } /* * 对明文加密. * @param text 需要加密的明文 * @return 加密后base64编码的字符串 */ private String encrypt(String random, String plaintext) throws DingTalkEncryptException { try { byte[] randomBytes = random.getBytes(CHARSET); byte[] plainTextBytes = plaintext.getBytes(CHARSET); byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length); byte[] corpidBytes = corpId.getBytes(CHARSET); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); byteStream.write(randomBytes); byteStream.write(lengthByte); byteStream.write(plainTextBytes); byteStream.write(corpidBytes); byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size()); byteStream.write(padBytes); byte[] unencrypted = byteStream.toByteArray(); byteStream.close(); Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); byte[] encrypted = cipher.doFinal(unencrypted); String result = base64.encodeToString(encrypted); return result; } catch (Exception e) { throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR); } } /* * 对密文进行解密. * @param text 需要解密的密文 * @return 解密得到的明文 */ private String decrypt(String text) throws DingTalkEncryptException { byte[] originalArr; try { // 设置解密模式为AES的CBC模式 Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); // 使用BASE64对密文进行解码 byte[] encrypted = Base64.decodeBase64(text); // 解密 originalArr = cipher.doFinal(encrypted); } catch (Exception e) { throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR); } String plainText; String fromCorpid; try { // 去除补位字符 byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr); // 分离16位随机字符串,网络字节序和corpId byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); int plainTextLegth = Utils.bytes2int(networkOrder); plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET); fromCorpid = new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET); } catch (Exception e) { throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR); } // corpid不相同的情况 if (!fromCorpid.equals(corpId)) { throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR); } return plainText; } /** * 数字签名 * * @param token isv token * @param timestamp 时间戳 * @param nonce 随机串 * @param encrypt 加密文本 * @return * @throws DingTalkEncryptException */ public String getSignature(String token, String timestamp, String nonce, String encrypt) throws DingTalkEncryptException { try { String[] array = new String[] { token, timestamp, nonce, encrypt}; Arrays.sort(array); //System.out.println(JSON.toJSONString(array)); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 4; i++) { sb.append(array[i]); } String str = sb.toString(); System.out.println(str); MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(str.getBytes()); byte[] digest = md.digest(); StringBuffer hexstr = new StringBuffer(); String shaHex = ""; for (int i = 0; i < digest.length; i++) { shaHex = Integer.toHexString(digest[i] & 0xFF); if (shaHex.length() < 2) { hexstr.append(0); } hexstr.append(shaHex); } return hexstr.toString(); } catch (Exception e) { throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR); } } public static class Utils { public Utils() { } public static String getRandomStr(int count) { String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < count; ++i) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } public static byte[] int2Bytes(int count) { byte[] byteArr = new byte[] { (byte)(count >> 24 & 255), (byte)(count >> 16 & 255), (byte)(count >> 8 & 255), (byte)(count & 255)}; return byteArr; } public static int bytes2int(byte[] byteArr) { int count = 0; for (int i = 0; i < 4; ++i) { count <<= 8; count |= byteArr[i] & 255; } return count; } } public static class PKCS7Padding { private static final Charset CHARSET = Charset.forName("utf-8"); private static final int BLOCK_SIZE = 32; public PKCS7Padding() { } public static byte[] getPaddingBytes(int count) { int amountToPad = 32 - count % 32; if (amountToPad == 0) { amountToPad = 32; } char padChr = chr(amountToPad); String tmp = new String(); for (int index = 0; index < amountToPad; ++index) { tmp = tmp + padChr; } return tmp.getBytes(CHARSET); } public static byte[] removePaddingBytes(byte[] decrypted) { int pad = decrypted[decrypted.length - 1]; if (pad < 1 || pad > 32) { pad = 0; } return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); } private static char chr(int a) { byte target = (byte)(a & 255); return (char)target; } } public static class DingTalkEncryptException extends Exception { public static final int SUCCESS = 0; public static final int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001; public static final int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002; public static final int ENCRYPTION_NONCE_ILLEGAL = 900003; public static final int AES_KEY_ILLEGAL = 900004; public static final int SIGNATURE_NOT_MATCH = 900005; public static final int COMPUTE_SIGNATURE_ERROR = 900006; public static final int COMPUTE_ENCRYPT_TEXT_ERROR = 900007; public static final int COMPUTE_DECRYPT_TEXT_ERROR = 900008; public static final int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009; public static final int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010; private static Map<Integer, String> msgMap = new HashMap(); private Integer code; static { msgMap.put(0, "成功"); msgMap.put(900001, "加密明文文本非法"); msgMap.put(900002, "加密时间戳参数非法"); msgMap.put(900003, "加密随机字符串参数非法"); msgMap.put(900005, "签名不匹配"); msgMap.put(900006, "签名计算失败"); msgMap.put(900004, "不合法的aes key"); msgMap.put(900007, "计算加密文字错误"); msgMap.put(900008, "计算解密文字错误"); msgMap.put(900009, "计算解密文字长度不匹配"); msgMap.put(900010, "计算解密文字corpid不匹配"); } public Integer getCode() { return this.code; } public DingTalkEncryptException(Integer exceptionCode) { super((String)msgMap.get(exceptionCode)); this.code = exceptionCode; } } static { try { Security.setProperty("crypto.policy", "limited"); RemoveCryptographyRestrictions(); } catch (Exception var1) { } } private static void RemoveCryptographyRestrictions() throws Exception { Class<?> jceSecurity = getClazz("javax.crypto.JceSecurity"); Class<?> cryptoPermissions = getClazz("javax.crypto.CryptoPermissions"); Class<?> cryptoAllPermission = getClazz("javax.crypto.CryptoAllPermission"); if (jceSecurity != null) { setFinalStaticValue(jceSecurity, "isRestricted", false); PermissionCollection defaultPolicy = (PermissionCollection)getFieldValue(jceSecurity, "defaultPolicy", (Object)null, PermissionCollection.class); if (cryptoPermissions != null) { Map<?, ?> map = (Map)getFieldValue(cryptoPermissions, "perms", defaultPolicy, Map.class); map.clear(); } if (cryptoAllPermission != null) { Permission permission = (Permission)getFieldValue(cryptoAllPermission, "INSTANCE", (Object)null, Permission.class); defaultPolicy.add(permission); } } } private static Class<?> getClazz(String className) { Class clazz = null; try { clazz = Class.forName(className); } catch (Exception var3) { } return clazz; } private static void setFinalStaticValue(Class<?> srcClazz, String fieldName, Object newValue) throws Exception { Field field = srcClazz.getDeclaredField(fieldName); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & -17); field.set((Object)null, newValue); } private static <T> T getFieldValue(Class<?> srcClazz, String fieldName, Object owner, Class<T> dstClazz) throws Exception { Field field = srcClazz.getDeclaredField(fieldName); field.setAccessible(true); return dstClazz.cast(field.get(owner)); } }
-
コントローラー層インターフェース
/** * 事件回调方法 * * @param msg_signature * @param timeStamp * @param nonce * @param json * @return */ @RequestMapping("/callback") public Map<String, String> callBack( @RequestParam(value = "msg_signature", required = false) String msg_signature, @RequestParam(value = "timestamp", required = false) String timeStamp, @RequestParam(value = "nonce", required = false) String nonce, @RequestBody(required = false) JSONObject json) { try { // 1. 从http请求中获取加解密参数 // 2. 使用加解密类型 // Constant.OWNER_KEY 说明: // 1、开发者后台配置的订阅事件为应用级事件推送,此时OWNER_KEY为应用的APP_KEY。 // 2、调用订阅事件接口订阅的事件为企业级事件推送, // 此时OWNER_KEY为:企业的appkey(企业内部应用)或SUITE_KEY(三方应用) DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(dingTalkConfig.getToken(), dingTalkConfig.getAesKey(), dingTalkConfig.getAppkey()); String encryptMsg = json.getString("encrypt"); String decryptMsg = callbackCrypto.getDecryptMsg(msg_signature, timeStamp, nonce, encryptMsg); // 3. 反序列化回调事件json数据 JSONObject eventJson = JSON.parseObject(decryptMsg); log.info("反序列化回调事件json数据:" + eventJson); String eventType = eventJson.getString("EventType"); // 4. 根据EventType分类处理 if ("check_url".equals(eventType)) { // 测试回调url的正确性 log.info("测试回调url的正确性"); } else if ("user_add_org".equals(eventType)) { // 处理通讯录用户增加事件 log.info("发生了:" + eventType + "事件"); } else { // 添加其他已注册的 log.info("发生了:" + eventType + "事件"); } // 5. 返回success的加密数据 Map<String, String> successMap = callbackCrypto.getEncryptedMap("success"); return successMap; } catch (DingCallbackCrypto.DingTalkEncryptException e) { e.printStackTrace(); } return null; }
-
DingTalk が提供するイベント コールバックのリスト
その他のタイプについては、この青いリンクをクリックして取得してください
補足:フロントエンド連携デバッグ時、フロントエンド用のステップを設定する必要があります
DingTalkのログイン機能を再利用した後、フロントエンドがローカルデバッグを行う際に、ゲートウェイイントラネットを貫通し、外部ネットワークがアクセスをテストできるようにします.もちろん、イントラネットのQRコードログインURLを直接構築することも可能です
.ここでは、最初の A の状況に焦点を当てます。
-
DingTalk エンタープライズ アプリケーション ゲートウェイ プラットフォームで、対応するアプリケーションの内部ネットワーク IP+ポートから外部ネットワークへのマッピングを構成します。
-
マッピングされた外部ネットワーク マッピングを見つけてコピーする
-
DingTalk オープン プラットフォームのエンタープライズ アプリケーション開発では、独自のアプリケーションを選択します. ログイン コールバックを構成します
ここで、内部ネットワーク アドレス、または前の手順でマッピングされた外部ネットワーク アドレスを直接構成できます.
この手順が構成されていない場合は、フロントend がこのページにアクセスすると、次のような問題が発生します。
2023-01-03
補足:ゲートウェイコネクタを自動起動する設定方法
Ubuntu
全体的なアイデアは、この大物のブログに従って、Ubuntu で自動起動ソフトウェアをセットアップすることです.私が使用するサーバーは ubuntu 18.04 です
-
ステップ 1: システム ディレクトリ /lib/systemd/system/rc-local.service を確認します。自分で作成していない場合、ファイルの内容は次のとおりです (ファイルが存在する場合、[Install] 項目はありません。追加する必要があります)。それは自分で)
# SPDX-License-Identifier: LGPL-2.1+ # # This file is part of systemd. # # systemd is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This unit gets pulled automatically into multi-user.target by # systemd-rc-local-generator if /etc/rc.local is executable. [Unit] Description=/etc/rc.local Compatibility Documentation=man:systemd-rc-local-generator(8) ConditionFileIsExecutable=/etc/rc.local After=network.target [Service] Type=forking ExecStart=/etc/rc.local start TimeoutSec=0 RemainAfterExit=yes GuessMainPID=no [Install] WantedBy=multi-user.target Alias=rc-local.service
-
etc ディレクトリ内のファイルも上記のように変更する必要があります。/etc/systemd/system/rc-local.service を確認し、そのようなファイルがない場合は、このファイルを追加します。
# SPDX-License-Identifier: LGPL-2.1+ # # This file is part of systemd. # # systemd is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This unit gets pulled automatically into multi-user.target by # systemd-rc-local-generator if /etc/rc.local is executable. [Unit] Description=/etc/rc.local Compatibility Documentation=man:systemd-rc-local-generator(8) ConditionFileIsExecutable=/etc/rc.local After=network.target [Service] Type=forking ExecStart=/etc/rc.local start TimeoutSec=0 RemainAfterExit=yes GuessMainPID=no [Install] WantedBy=multi-user.target Alias=rc-local.service ```
-
/etc/rc.local スクリプト ファイルを作成し、実行するスクリプト コマンドを記述します。
vim /etc/rc.local
-
rc.local に実行権限を与える
sudo chmod +x /etc/rc.local
-
サービスを有効にする
sudo systemctl enable rc-local sudo systemctl start rc-local.service sudo systemctl status rc-local.service
-
パソコンを再起動して効果を確認する 再起動
後、スクリプトで起動したアプリケーションが正常に動作するか確認する
セントス オペレーティング システム
自動起動を設定する Linux の 3 つの方法を参照してください。
-
システム ファイルの構成に従って、対応する起動スクリプトとシステム ファイルの場所を見つけます。
/lib/systemd/system/rc-local.service
-
起動スクリプトを編集
vim /etc/rc.d/rc.local
-
スクリプトに実行権限を付与 chmod +x /etc/rc.d/rc.local
-
再起動後にソフトウェアが起動するかどうかをテストする