こんにちは、みんな!私は、第一線の低レベル コード ファーマーの Sum Mo です。普段は、技術的な問題について勉強したり考えたりして、記事にまとめるのが好きです。私のレベルには限界があります。記事内に不適切な表現があった場合は、とコード、お気軽に教えてください。
以下本文です!
記事の背景
私たちは最近多くのプロジェクトに取り組んでおり、その中には私たちが全体的に責任を負うものもあれば、協力して行うものもあります。これらのプロジェクトにはさまざまなシステムが関係していますが、基本的に「オープンプラットフォーム」を率先してやる企業はありません。これは、投入量と産出量が比較的少なく、プロジェクトは完了したら終了であり、入札書類にオープンプラットフォームであることの要件がないためです。これらのプロジェクトはすべてビジネス システムであり、一般的に公開できる機能はありませんが、同じプロジェクト内には常に公開する必要があるものがいくつかあり、軽量で安全な対話方法が依然として必要とされています。
シーン分類
(1) シングルサインオン
シングル サインオンは、ポータル Web サイトやアプレット ジャンプなど、さまざまなシナリオに適用できる便利なログイン方法です。ユーザーは、ポータル Web サイトにログインするときにユーザー名とパスワードを入力するだけで、ログイン情報を繰り返し入力することなく、他の関連サブシステムに簡単にアクセスできます。これにより、ユーザーが容易になるだけでなく、IT 管理者がシステムをより適切に管理できるようになります。
百度を例に挙げます。
これは典型的なシングル サインオンのケースですが、シングル サインオン機能を実装するにはどうすればよいでしょうか?
アイデア分析
从『系统A门户页』点击导航进入『系统B』,用户信息是怎么同步的呢?把信息放在跳转链接上传给系统A肯定不合适,这相当于泄漏了用户信息,方案不可行。我们的做法是:
- ユーザーはアカウントのパスワードを入力してシステム A のポータル ページに入ります。
- ユーザーがクリックしてシステム B に移動すると、システム A は現在のユーザーの一意の識別子 (通常は一意の文字列の文字列) を生成します。これを一時的な認証コードと呼び、userToken という名前を付けます。
- このロゴは、パラメータとしてシステム B のジャンプ リンクに接続されます。例: https://systemB.com/index?userToken=xxxx;
- システム A は、userToken に基づいて現在のユーザーをクエリするためのインターフェイスを提供します (例: https://systemA.com/queryUserByToken?userToken=xxx)。
システム B のホームページに入った後、システム B はシステム A の queryUserByToken インターフェイスを呼び出して取得します。情報。 - 安全のため、この userToken は通常、時間制限があり、1 時間を超えると使用できなくなり、使用できるのは 1 回だけで、使い果たされたら破棄されます。
ロジックを説明するためにシーケンス図を描いてみましょう
(2) インターフェース呼び出し
一般に、インターフェイスを呼び出すには、http インターフェイスと rpc インターフェイスの 2 つの方法があります。
1.httpインターフェース
http インターフェイスが何であるかは誰もが知っており、Java を使用して Get リクエストと Post リクエストを簡単に呼び出すことができます。ただし、http インターフェイスのデータ セキュリティの問題を考慮する必要があります。ブラウザーのインターフェイスまたはポストマン ツールを呼び出すと、データは認証や復号化を行わずにプレーン テキストで返されますが、これは明らかに安全ではありません。開発プロセス中に、携帯電話番号などの機密情報を含むデータをプレーン テキストで直接返すためにパートナーが提供するインターフェイスによく遭遇します。この方法は便利で迅速ですが、一般に安全性や信頼性があまり高くありません。
比較的安全な http インターフェイスを実装するには、通常 2 つの方法があります。
-
最初のタイプでは、呼び出し元は認証を実行してトークンを取得する必要があり、インターフェイスを呼び出すときにトークンをリクエスト ヘッダーまたは Cookie に配置する必要があります。プロセッサーはフィルターを通じてトークンの正当性をチェックします。
-
次に、プロセッサは一意の appId と対応する appSecret を生成し、呼び出し元に提供する必要があります。呼び出し元はこの appId を使用してインターフェイスを呼び出し、プロセッサは appId と appSecret を使用してデータを暗号化します。呼び出し元はデータを取得した後、同じ appId と appSecret を使用してデータを復号化します。
使用请求头或Cookie的方式将token放置于请求中的优点是安全性高,因为token不易被窃取或篡改。而使用appId和appSecret进行加密和解密的方式的优点是方便性高,因为appId和appSecret可以在接口文档或其他途径中公开,调用方只需要使用这些信息即可进行加解密操作,无需每次都进行认证获取token。
2 つのメソッドの選択は、特定の状況に応じて決定する必要があります。一般に、セキュリティがより重要なシナリオではトークン メソッドを使用でき、利便性がより重要なシナリオでは appId メソッドと appSecret メソッドを使用できます。
ここでは、個人的なテストに利用できる、利用可能なコード ツール クラスを提供します。
必要な依存関係
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.56</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
暗号化および復号化ツール
import java.io.IOException;
import java.security.Security;
import java.text.ParseException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@Slf4j
public class EncryptUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String CipherMode = "AES/CBC/PKCS7Padding";
private static final String EncryptAlg = "AES";
private static final String Encode = "UTF-8";
/**
* 加密随机盐
*/
private static final String AESIV = "ff465fdecc764337";
/**
* 加密:有向量16位,结果转base64
*
* @param context
* @return
*/
public static String encrypt(String context, String sk) {
try {
// 下面这行在进行PKCS7Padding加密时必须加上,否则报错
Security.addProvider(new BouncyCastleProvider());
byte[] content = context.getBytes(Encode);
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(
Cipher.ENCRYPT_MODE,
new SecretKeySpec(sk.getBytes(Encode), EncryptAlg),
new IvParameterSpec(AESIV.getBytes(Encode)));
byte[] data = cipher.doFinal(content);
String result = Base64.encodeBase64String(data);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解密
*
* @param context
* @return
*/
public static String decrypt(String context, String sk) {
try {
byte[] data = Base64.decodeBase64(context);
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(
Cipher.DECRYPT_MODE,
new SecretKeySpec(sk.getBytes(Encode), EncryptAlg),
new IvParameterSpec(AESIV.getBytes(Encode)));
byte[] content = cipher.doFinal(data);
String result = new String(content, Encode);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String sendPost(String url, JSONObject jsonObject, String encoding)
throws ParseException, IOException {
String body = "";
//创建httpclient对象
CloseableHttpClient client = HttpClients.createDefault();
//创建post方式请求对象
HttpPost httpPost = new HttpPost(url);
//装填参数
StringEntity s = new StringEntity(jsonObject.toString(), "utf-8");
s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
"application/json"));
//设置参数到请求对象中
httpPost.setEntity(s);
log.info("请求地址:" + url);
// System.out.println("请求参数:"+nvps.toString());
//设置header信息
//指定报文头【Content-type】、【User-Agent】
// httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
httpPost.setHeader("Content-type", "application/json");
httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
//执行请求操作,并拿到结果(同步阻塞)
CloseableHttpResponse response = client.execute(httpPost);
//获取结果实体
HttpEntity entity = response.getEntity();
if (entity != null) {
//按指定编码转换结果实体为String类型
body = EntityUtils.toString(entity, encoding);
}
EntityUtils.consume(entity);
//释放链接
response.close();
return body;
}
public static void main(String[] args) {
String appId = "appId";
//AES算法支持的密钥长度有128位、192位和256位,其中128位密钥是最常用的。
//因此,如果使用AES算法进行加密和解密,必须确保密钥长度是128位、192位或256位。
//如果使用的是AES-128算法,则密钥长度应该是128位,也就是16个字节;
//如果使用的是AES-192算法,则密钥长度应该是192位,也就是24个字节;
//如果使用的是AES-256算法,则密钥长度应该是256位,也就是32个字节
String appKey = UUIDUtil.generateString(32);
//参数加密
JSONObject jsonObject = new JSONObject();
jsonObject.put("appId", appId);
jsonObject.put("appKey", appKey);
jsonObject.put("data", "我是内容");
String encrypt = EncryptUtil.encrypt(jsonObject.toJSONString(), appKey);
System.out.println("加密后内容=" + encrypt);
//参数界面
System.out.println("解密后内容=" + EncryptUtil.decrypt(encrypt, appKey));
}
}
ランダム文字列ジェネレータークラス
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
public class UUIDUtil {
public static final String allChar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static final String letterChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String numberChar = "0123456789";
public static String[] chars =
new String[]{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
};
/**
* 用于生成8位唯一标识字符串
*/
public static String generateShortUuid() {
StringBuffer shortBuffer = new StringBuffer();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 8; i++) {
String str = uuid.substring(i * 4, i * 4 + 4);
int x = Integer.parseInt(str, 16);
shortBuffer.append(chars[x % 36]);
}
return shortBuffer.toString();
}
/**
* 生成指定长度纯数字唯一标识字符串
*
* @param length
* @return
*/
public static String generatePureNumberUuid(int length) {
StringBuffer shortBuffer = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
shortBuffer.append(numberChar.charAt(random.nextInt(10)));
}
return shortBuffer.toString();
}
/**
* 由大小写字母、数字组成的随机字符串
*
* @param length
* @return
*/
public static String generateString(int length) {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(allChar.charAt(random.nextInt(allChar.length())));
}
return sb.toString();
}
/**
* 由大小写字母组成的随机字符串
*
* @param length
* @return
*/
public static String generateMixString(int length) {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(letterChar.charAt(random.nextInt(letterChar.length())));
}
return sb.toString();
}
/**
* 由小字字母组成的随机字符串
*
* @param length
* @return
*/
public static String generateLowerString(int length) {
return generateMixString(length).toLowerCase();
}
/**
* 由大写字母组成的随机字符串
*
* @param length
* @return
*/
public static String generateUpperString(int length) {
return generateMixString(length).toUpperCase();
}
/**
* 产生指字个数的0组成的字符串
*
* @param length
* @return
*/
public static String generateZeroString(int length) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
sb.append('0');
}
return sb.toString();
}
/**
* 将数字转化成指字长度的字符串
*
* @param num
* @param fixdlenth
* @return
*/
public static String toFixdLengthString(long num, int fixdlenth) {
StringBuffer sb = new StringBuffer();
String strNum = String.valueOf(num);
if (fixdlenth - strNum.length() >= 0) {
sb.append(generateZeroString(fixdlenth - strNum.length()));
} else {
throw new RuntimeException("将数字" + num + "转化为长度为" + fixdlenth + "的字符串发生异常!");
}
sb.append(strNum);
return sb.toString();
}
/**
* 将数字转化成指字长度的字符串
*
* @param num
* @param fixdlenth
* @return
*/
public static String toFixdLengthString(int num, int fixdlenth) {
StringBuffer sb = new StringBuffer();
String strNum = String.valueOf(num);
if (fixdlenth - strNum.length() >= 0) {
sb.append(generateZeroString(fixdlenth - strNum.length()));
} else {
throw new RuntimeException("将数字" + num + "转化为长度为" + fixdlenth + "的字符串发生异常!");
}
sb.append(strNum);
return sb.toString();
}
/**
* 生成订单编号,时间戳+后8位随机字符串
*
* @return
*/
public static String getOrderNo() {
String orderNo = "";
String sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
orderNo = sdf + generateShortUuid();
return orderNo;
}
/**
* 这个方法只支持最大长度为32的随机字符串,如要支持更大长度的,可以适当修改此方法,如前面补、后面补,或者多个uuid相连接
*
* @param length
* @return
*/
private static String toFixedLengthStringByUUID(int length) {
// 也可以通过UUID来随机生成
UUID uuid = UUID.randomUUID();
return uuid.toString().replace("-", "").substring(0, length);
}
/**
* 生成订单编号,时间戳+后8位随机字符串
*
* @return
*/
public static String getBarCode() {
String barCode = "";
String sdf = new SimpleDateFormat("yyyyMMdd").format(new Date());
barCode = sdf + generatePureNumberUuid(4);
return barCode;
}
}
2.RPCインターフェース
RPC (リモート プロシージャ コール) リモート プロシージャ コールは、プロセス間通信の方法であり、これにより、異なるシステムがネットワークを介して通信および対話できるようになります。しかし、RPCインターフェースはインターフェースのパラメータ、戻り値、例外などを事前に定義する必要があり、多者連携の開発フレームワークもほぼ同じである必要があるため、その適用シナリオは比較的限られています。さらに、異なるシステム間の RPC インターフェイスは互換性を維持する必要があり、互換性を維持しないと、インターフェイスの不一致やデータ送信エラーなどの問題が発生する可能性があります。したがって、RPC インターフェースを使用する場合は、インターフェースの正確性と信頼性を確保するために十分な考慮と設計が必要です。RPC インターフェイスのアプリケーション シナリオは限られていますが、特定のシナリオでは、RPC インターフェイスは分散アーキテクチャ内のシステム間のサービス コールなど、効率的で信頼性の高い通信方法を提供できます。
職場で RPC 呼び出しを行ったのは 1 回だけです。当時、私は会社のさまざまな部門と協力していました。同じフレームワーク セットを使用していました。彼らは RPC インターフェイスを提供していました。jar パッケージをインポートするだけで、サービスを簡単に呼び出すことができました。ただし、内部以外で RPC 呼び出しを使用している組織や企業に出会ったことはほとんどありません。通常、ほとんどの外部インターフェイス サービスは HTTP インターフェイスを通じて実装されます。
(3) ミドルウェア連携
ここでChatGPTの答えを引用します。
そして、私が遭遇した状況: あるとき、パーティ A はパーティ B にデータを積極的にプッシュする必要があるため、メッセージ キューを使用する計画を提案しました。それを聞いた後、双方が分離されていて便利であると感じ、行動を開始しました。当事者 A は自身のサーバーにメッセージ キューを展開しましたが、予期せずすべての当事者のサーバー環境が分離され、ネットワークがブロックされたため、当事者 B は当事者 A のメッセージ キューにまったく接続できなくなりました。そこで彼らはプライベート クラウドの運用保守担当者を見つけて、ポート開放や IP ホワイトニングなどの多くの操作を実行できるかどうか尋ねましたが、何らかの理由で実行できませんでした。最終的には、HTTP インターフェイスを提供するためにパーティ B に変更する必要があり、パーティ A は問題を解決するためにそのインターフェイスを積極的に呼び出してデータを送信しました。。。
結論は
マルチシステム連携のシナリオでは、システム間の相互作用が非常に重要です。システム間の安定した信頼性の高い対話を確保するには、対話プロトコルの一貫性、データ形式の一貫性、セキュリティ保証、エラー処理メカニズム、対話頻度、監視とログなどすべてに特別な注意が必要です。
-
対話プロトコルの一貫性はシステム間のデータ伝送の基礎であり、要求および応答のメッセージ形式、データ型、処理規則などを明確に定義する必要があります。データ形式の一貫性も非常に重要であり、形式の不一致によるデータ解析例外を避けるために、データ交換の形式とエンコード方法を決定する必要があります。
-
セキュリティの保証は、システムへの不正アクセスやデータ漏洩を防ぐ重要な手段であり、システムのセキュリティを確保するためには、さまざまなセキュリティ対策が必要です。
-
エラー処理メカニズムは、システム内で発生する可能性のあるさまざまな異常状況を考慮し、情報がタイムリーにユーザーにフィードバックされるように、さまざまな異常状況を分類して処理する必要があります。
-
頻繁な通話によるシステムへの過剰な負荷を避けるために、対話の頻度は実際の状況に応じて策定する必要があります。
-
監視とログには、システムのリアルタイム監視、問題のタイムリーな発見と処理、トラブルシューティングと分析のためのログの記録が必要です。
まとめると、多者間協力においては、システム間の相互作用を安定かつ信頼性高く確保し、協力を円滑に進めるためには、システム間の相互作用を十分に考慮する必要がある。
最后温馨提醒大家一下,多方合作少不了开会对齐,在沟通的时候还是要耐心和主动一些,都是干活的应该互帮互助才对,齐心协力才能少加班嘛!