DingTalk エンタープライズ アプリケーション ゲートウェイ アクセス (ナニー レベルのチュートリアル)

バックグラウンド

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 サイトに慣れている場合は、エンタープライズ アプリケーション ゲートウェイの構成プロセスを参照してビルドできます。

ピットを構築して踏むプロセス

ビルドプロセス

準備

  1. エンタープライズ アプリケーション ゲートウェイがアクティブ化されました。アクティブ化されていない場合は、DingTalk を使用して以下の QR コードをスキャンするか、このページに入って QR コードをスキャンし
    DingTalk エンタープライズ アプリケーション ゲートウェイ アプリケーションをインストールする必要があります。
    ここに画像の説明を挿入
  2. スキャン後、次のページが表示されます.
    会社がこの機能を有効にする場合、アカウント レベルは管理者である必要があります (サブ管理者は許可されません)。
    ここに画像の説明を挿入

エンタープライズ ゲートウェイ アプリケーションの表示

  1. クライアント側との通信に成功し、上記の QR コードをスキャンした後、クライアント側は、次の図からアクティベーションが成功したことを示すプロンプト メッセージを取得できます。
    ここに画像の説明を挿入

  2. DingTalk の管理背景 -> ワークベンチ -> サードパーティ アプリケーションでは、DingTalk セキュリティ ゲートウェイの入り口が表示されます。クリックすると、セキュリティ ゲートウェイの背景に入ります エンタープライズ ゲートウェイのホームページに入ります エンタープライズ ゲートウェイのホームページに
    ここに画像の説明を挿入
    入り
    ここに画像の説明を挿入
    ます
    ここに画像の説明を挿入

コネクタの構成

  1. 前提条件
    Linux サーバーにデプロイする必要がある場合は、次の条件を満たす必要があることに注意してください (Windows も次の条件を満たす必要があります)。
    ここに画像の説明を挿入

  2. コネクタの作成
    [新しいコネクタ] を選択し、展開の種類を選択して [続行] をクリックし、次のコマンドを生成してから、次の Linux コマンドをコピーします。
    ここに画像の説明を挿入

  3. Linux サーバーでコピー コマンドを実行すると、コネクタが正常に起動されていることがわかります。
    ここに画像の説明を挿入

  4. コネクタグループを新規作成
    コネクタグループに新規作成したコネクタを追加 コネクタグループの機能:コネクタを一括管理し、後で管理適用する際に利用
    ここに画像の説明を挿入

補足 - コネクタのセルフスタート設定

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. 新しいアプリケーションを作成する
    方法 1:管理バックグラウンドでサードパーティ アプリケーションを作成する- ワークベンチ
    ここに画像の説明を挿入

方法2:利用シーンに合わせてDingTalkオープンプラットフォーム上でアプリを作成する

ここに画像の説明を挿入

  1. 必要に応じてアプリケーション情報を入力します
    ここに画像の説明を挿入

  2. 構成が成功すると、AgentId、AppKey、および AppSecret が自動的に生成され、インターフェイス接続で使用されます。
    ここに画像の説明を挿入

  3. アプリを公開する(開発プログラムが完成した後に使用)
    .アプリがリリースされた後、他のユーザーが見ることができる(ここでは、管理者のワークベンチではなく、自分のワークベンチで見ることを指す.エクスペリエンス バージョンはグレースケール リリースに属します。クリックして、アプリケーションリリースの紹介を表示します。
    ここに画像の説明を挿入

構成アプリケーション

  1. アプリケーションを作成したら、アプリケーション管理でテスト アプリケーションを構成し、未構成のアプリケーションをクリックして構成します。
    ここに画像の説明を挿入

  2. アプリケーション管理 - 基本構成
    コネクタ オプションの "+" をクリックし、展開されたコネクタ (またはコネクタ グループ) を選択して、テスト アプリケーションとの接続を確立します。構成するドメイン名を選択します。ここでのドメイン名はイントラネット アドレスです
    +ポート (複数のマッピングを構成可能)構成を保存すると、イントラネットへの侵入が可能になります!!!

ここに画像の説明を挿入

詳細設定

知らせ:

  • 次に、高度な設定について説明します.特別な要件がない場合は、高度な設定をスキップできます (次の手順に直接進みます)。
  • ここでの構成の前提条件は、次のステップでアクセス ポリシーを構成し構成する前に外部ネットワーク アクセス ドメイン名を取得する必要があることです。

バックグラウンドの導入により、DingTalk オープン プラットフォームは、作成されたアプリケーションにイベント コールバックを実装する必要があります (このインターフェイスは、会社の組織構造が変更されたときに呼び出されます). ただし、アクセス ポリシーを構成すると、DingTalk コールバック インターフェイスは引き続き表示され
ますurl 地址访问异常, 不允许3xxx跳转
。当社の管理プラットフォーム - アプリケーション管理で高度な設定を行う必要があります. 設定がない前に, URL を入力した後にエラーが報告されます. 内容は下の図に示されています.

ここに画像の説明を挿入

  1. 高度な構成
    追加して匿名アクセスを許可する URL: 形式は次のとおりです。https://生成的外网访问域名/事件回调sdk接口地址.*
    ここに画像の説明を挿入
  2. 構成が成功したら、
    テストの前にイントラネット サーバーにイベント コールバック SDK を展開する必要があります。
    ここに画像の説明を挿入

アクセス ポリシーを構成する

ポリシー管理は、ポリシーの登録、ポリシーの変更、ポリシーの削除、ポリシーの非アクティブ化、ポリシーの有効化、優先順位付けなどを含め、エンタープライズ管理者によって操作されます。

  1. 次に、[ポリシーの作成] ボタンをクリックして、ポリシーの作成インターフェイスに入ります。
    ここに画像の説明を挿入
  2. ポリシー ページで、ポリシー情報を入力します。設定項目は次のとおりです。
    ここに画像の説明を挿入
    ここに画像の説明を挿入
  3. [完了] をクリックしてポリシーを作成します
    ここに画像の説明を挿入

外部ネットワーク アクセス用のドメイン名を取得する (ピットを踏む)

  1. アクセス ポリシーを構成した後、コネクタによって表示されるパブリック ネットワーク IP を介してアクセスする代わりに、アプリケーション管理を介して新しいドメイン名を取得できます。
    ここに画像の説明を挿入

  2. 毎回構成する内部ネットワークの ip+port は、外部ネットワークからアクセスできる一意のドメイン名にマッピングされ、必要に応じて新しいドメイン名をコピーできることがわかります。
    ここに画像の説明を挿入

  3. エンタープライズ ゲートウェイ イントラネット侵入効果のテスト
    イントラネット アクセス
    ここに画像の説明を挿入
    パブリック ネットワーク アクセス
    ここに画像の説明を挿入

添付:DingTalkイベントコールバックSDK

イベントコールバック紹介アドレス:https://open.dingtalk.com/document/org/configure-event-subcription

  1. 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));
        }
    
    }
    
    
  2. コントローラー層インターフェース

      /**
         * 事件回调方法
         *
         * @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;
        }
    
  3. DingTalk が提供するイベント コールバックのリスト
    その他のタイプについては、この青いリンクをクリックして取得してください
    ここに画像の説明を挿入
    ここに画像の説明を挿入

補足:フロントエンド連携デバッグ時、フロントエンド用のステップを設定する必要があります

DingTalkのログイン機能を再利用した後、フロントエンドがローカルデバッグを行う際に、ゲートウェイイントラネットを貫通し、外部ネットワークがアクセスをテストできるようにします.もちろん、イントラネットのQRコードログインURLを直接構築することも可能です
.ここでは、最初の A の状況に焦点を当てます。

  1. DingTalk エンタープライズ アプリケーション ゲートウェイ プラットフォーム、対応するアプリケーションの内部ネットワーク IP+ポートから外部ネットワークへのマッピングを構成します。
    ここに画像の説明を挿入

  2. マッピングされた外部ネットワーク マッピングを見つけてコピーする
    ここに画像の説明を挿入

  3. DingTalk オープン プラットフォームのエンタープライズ アプリケーション開発では、独自のアプリケーションを選択します. ログイン コールバックを構成します
    ここで、内部ネットワーク アドレス、または前の手順でマッピングされた外部ネットワーク アドレスを直接構成できます.
    ここに画像の説明を挿入
    この手順が構成されていない場合は、フロントend がこのページにアクセスすると、次のような問題が発生します。
    ここに画像の説明を挿入


2023-01-03

補足:ゲートウェイコネクタを自動起動する設定方法

Ubuntu

全体的なアイデアは、この大物のブログに従って、Ubuntu で自動起動ソフトウェアをセットアップすることです.私が使用するサーバーは ubuntu 18.04 です

  1. ステップ 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
    

    ここに画像の説明を挿入

  2. 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
    	```
    
    
  3. /etc/rc.local スクリプト ファイルを作成し、実行するスクリプト コマンドを記述します。

    vim /etc/rc.local
    

    ここに画像の説明を挿入

  4. rc.local に実行権限を与える

    sudo chmod +x /etc/rc.local
    
  5. サービスを有効にする

    sudo systemctl enable rc-local
    sudo systemctl start rc-local.service
    sudo systemctl status rc-local.service
    

    ここに画像の説明を挿入

  6. パソコンを再起動して効果を確認する 再起動
    後、スクリプトで起動したアプリケーションが正常に動作するか確認する
    ここに画像の説明を挿入

セントス オペレーティング システム

自動起動を設定する Linux の 3 つの方法を参照してください。

  1. システム ファイルの構成に従って、対応する起動スクリプトとシステム ファイルの場所を見つけます。/lib/systemd/system/rc-local.service
    ここに画像の説明を挿入

  2. 起動スクリプトを編集vim /etc/rc.d/rc.local
    ここに画像の説明を挿入

  3. スクリプトに実行権限を付与 chmod +x /etc/rc.d/rc.local

  4. 再起動後にソフトウェアが起動するかどうかをテストする
    ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_43371556/article/details/126600171