JAVA開発(外部インターフェース呼び出し認可問題記録まとめ)

1. 技術的背景:

現在、多くの Web プロジェクトや小規模プログラムは、オンラインになった後、クロスドレイン、クロスビジネス連携、データ送信、他の企業、Web サイト、アプリとの連携を行う必要があります。次に、インターフェースデータを呼び出す必要があります。次に、外部システムのインターフェイス呼び出しと、自社開発マイクロサービス間のインターフェイス呼び出しは明らかに異なりますが、最も明らかな特徴は、システム間の呼び出しには最初に承認が必要であることです。これは、最初にログインする必要があることを意味します。許可されたインターフェイス呼び出しがない場合、明らかにすべての情報がインターネット環境に公開されます。2 つ目は、送信データを暗号化および復号化する必要があることです。暗号化がない場合は、パケット キャプチャを通じてシステム間でどのような情報が送信されているかを確認することもできます。したがって、外部インターフェイスの呼び出しを行う場合には、これら 2 つの部分が関係しますが、多くのシステムの認証方法と暗号化および復号化方法は同じではなく、小さな違いはあるものの似たようなものを常に作り続けなければなりません。場合によっては、システム A とシステム B の間のインターフェイスの対話が、認可、暗号化および復号化アルゴリズムのセットであり、システム A とシステム C が別の認可、暗号化および復号化アルゴリズムのセットである場合があります。ブロガーは、常に車輪を再発明する必要はなく、一般的な認証と暗号化および復号化アルゴリズムのセットが数セットあれば十分だと考えています。私たちは事業開発により多くの経験を積んでいきます。

2. 外部システムインタフェース呼び出し認可の種類

1. トークンを取得し、トークンの有効期間内に、パラメータとしてトークンを保持し、すべてのインターフェースを呼び出す権限を持ちます。

2. インターフェースを呼び出すたびに署名を実行します。

3. トークンの導入と生成

業界では一般的に使用されている認証標準が 2 つあります。1 つは auth2 を使用する方法です。この方法は、WeChat、Weibo、QQ トラスト ログイン サービスなどのサードパーティの認証ログインに適しています。もう 1 つは oauth です。つまり、第三者がユーザーとパスワードを知らなくてもリソースを取得するための承認を申請できます。これは、ユーザー権限の検証や、ログイン後の表示リソースの共通割り当てなどのアクセス権限の割り当てに適しています (ボタン、メニューなど) サイトを入力します。

 

フローの説明:

ログイン
サーバーはパスワードを検証し、成功すると access_token と fresh_token を返し、クライアントは上記のトークンを記録します。
API にアクセスするには、
API にアクセスする前に access_token を解析し、期限切れかどうかを確認し、期限切れでない場合は API をリクエストし、期限切れの場合はトークンを更新して API をリクエストします。
リフレッシュ トークンには、
有効なトークンと交換するための有効期限が設定された refresh_token が含まれており、refresh_token の有効期限が切れた場合、ユーザーは再度ログインする必要があります。
Logout
API からログアウトするリクエストを行うと、サーバーとクライアントはトークンのストレージを同時に削除する必要があります。 

 クライアントは、API
に access_token 情報を伝達するように要求します。生成環境が access_token を直接伝達しない場合は、暗号化された署名が検証に使用されます。アンチリプレイメカニズムについては以下を参照してください。
トークンの取得
トークンを取得するには、環境に応じてさまざまな方法があります。
JWT ツールを使用してトークンを解析し
、トークンを解析します。
redis でトークンを読み取り
、uid スプライシング キーに従って access_token を読み取ります。このユーザーのトークンが存在しない場合は、ユーザーがログアウトしていることを意味します。
トークンを検証して
、セカンダリ トークンがこの uid に属しているかどうかを判断し、トークンの有効期限が切れているかどうかを確認し、有効期限が切れている場合は、次のトークンを更新するプロセスを実行します。
アクセス許可の注入
トークンの検証が成功したら、ユーザー情報に基づいてアクセス許可を生成し、Spring セキュリティ コンテキストに注入します。


クライアントは、refresh_token を運ぶようにAPI を要求します。実稼働環境が直接、refresh_token 情報を運んでいない場合、詳細については、次のアンチリプレイ攻撃を参照してください。
トークンの取得
トークンを取得するには、環境に応じてさまざまな方法があります。
JWT ツールを使用してトークンを解析し
、トークンを解析します。
トークンの読み取り
uid スプライシング キーに従って access_token を読み取り、ユーザーのトークンが存在しない場合は、ユーザーがログアウトしたことを意味します。
トークン
を検証して、トークンが uid に属しているかどうか、およびトークンの有効期限が切れているかどうかを確認します。有効期限が切れている場合は、refresh_token の期限切れエラーが返され、ユーザーは再度ログインする必要があります。
リフレッシュトークン
refresh_tokenの検証が成功するとaccess_tokenとrefresh_tokenが再生成され、上記の有効期間を現在時刻から逆算してredis上のユーザーのトークンを置き換えてクライアントに返却します。 

4. トークンインスタンスの取得

1. トークンパラメータの取得

	/**
	 * 组装获取Access Token参数
	 */
	public Map<String, Object> getACTokenParam() {
		Map<String, Object> map = new HashMap<>();
		String timestamp = DateTimeUtils.getDateStr();
		// client_secret+timestamp+client_id+username+password+grant_type+scope+client_secret
		// username使用原文;
		// client_secret(双方约定)、password需要md5加密后的转大写;
		// 将上述拼接的字符串使用MD5加密,加密后的值再转为大写
		String clientSecM = MD5Util.getMd5(clientSecret).toUpperCase();
		String passwordM = MD5Util.getMd5(password).toUpperCase();
		String subString = (clientSecM + timestamp + clientId + userName + passwordM + grantType + scope + clientSecM);
		String sign = MD5Util.getMd5(subString).toUpperCase();
		map.put("grant_type", grantType);
		map.put("client_id", clientId);
		map.put("timestamp", timestamp);
		map.put("username", userName);
		map.put("password", passwordM);
		map.put("scope", scope);
		map.put("sign", sign);
		return map;
	}

トークンの取得は、実際にはユーザー名と秘密キーを挿入して資格情報を取得することであり、実際にはログイン プロセスです。

2. トークンを運び、インターフェイス インスタンスをリクエストします。

	public Map<String, Object> getSendParam(Map<String, Object> inputParam){
		
		Map<String, Object> params = new LinkedHashMap<>();
		String token = getACToken();
		String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
		String input = JSONObject.toJSONString(inputParam);
		log.info("本次接口发送参数:"+input);
		String inputDES = Des3Utils.get3DESEncryptECB(input, desKey);
		byte[] decoded = Base64.getDecoder().decode(inputDES);
		String inputHex = Des3Utils.byteToHexString(decoded);
		String subString = subSign(input, timestamp, token);
		String sign = MD5Util.getMd5(subString).toUpperCase();
		params.put("token", token);
		params.put("sign", sign);
		params.put("timestamp", timestamp);
		params.put("clientId", clientId);
		params.put("jsonData", inputHex);
		return params;
		
	}

実際には、本来必要な送信パラメータに基づいてトークンパラメータを追加することになります。相手に権限を確認してもらいます。

5.署名の導入

署名は、自分でロックとキーを生成し、公開したいコンテンツを自分のロックでロックして署名を形成し、コンテンツと署名を一緒に公開して、自分のキーが何であるかを全員に伝えることと同じです。ユーザーは、署名内のコンテンツを開くためのキーを取得して、公開されたコンテンツと一致していることを確認できます。世界中の誰もが署名と内容の一貫性を検証するためのキーを取得できますが、署名付きのロックを持っているのは私だけです。

署名インスタンス:

	/**
	 * 根据密钥和消息生成签名
	 * 
	 * @param secretKey
	 * @param message
	 * @return
	 * @throws Exception
	 */
	private static String generateSignature(String secretKey, String message) throws Exception {
		String algorithm = "HmacSHA256";
		Mac mac = Mac.getInstance(algorithm);
		mac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), algorithm));
		byte[] hash = mac.doFinal(message.getBytes("UTF-8"));
		String signature = Base64.getEncoder().encodeToString(hash);
		return signature;
	}

ここでの秘密鍵は 2 つのシステムによって合意されます。したがって、双方が鍵を持っていて、署名が通過した場合にのみ権限が生じると私も思います。

署名検証を通じてインターフェイス インスタンスを呼び出します。

public String ltApi(ObjectNode json,URL url) throws Exception{
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		connection.setRequestMethod("POST");
		connection.setRequestProperty("Content-Type", "application/json");
		// 添加认证信息
		String message = connection.getRequestMethod() + connection.getURL().getPath();
		String signature = generateSignature(secretKey, message);
		String timestamp = String.valueOf(System.currentTimeMillis());
		connection.setRequestProperty("Authorization", signature + " " + timestamp);
		// 创建一个ObjectMapper对象
		ObjectMapper mapper = new ObjectMapper();
		// 将Json对象转换为Json字符串
		String jsonStr = mapper.writeValueAsString(json);
		// 设置请求正文
		connection.setDoOutput(true);
		try (OutputStream os = connection.getOutputStream()) {
			os.write(jsonStr.getBytes());
			os.flush();
		}
		if (connection.getResponseCode() != 200) {
			log.info("Failed : HTTP error code:"+connection.getResponseCode()+",信息:"+connection.getResponseMessage());
			throw new RuntimeException("Failed : HTTP error code " + connection.getResponseCode());
		}else {
			log.info(json+"上链成功");
		}
		// 获取响应体
		StringBuilder sb = new StringBuilder();
		try (InputStream is = connection.getInputStream();
				InputStreamReader isr = new InputStreamReader(is);
				BufferedReader reader = new BufferedReader(isr)) {
			String line;
			while ((line = reader.readLine()) != null) {
				sb.append(line);
			}
		}
		String responseBody = sb.toString();
		log.info(responseBody);
		connection.disconnect();
		return responseBody;
	}

トークン生成ツールクラス

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Component;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
 

/**
 * token工具类
*/
@Component
public class TokenUtil {
	
	/**
     * token过期时间
     */
    private static final long EXPIRE_TIME = 30 * 60 * 1000;

    /**
                * 生成签名,30分钟过期
     * @param username 用户名
     * @param password 密码
     * @param loginTime 登录时间
     * @param tokenSecret 秘钥 
     * @return 生成的token
     */
    public static String createToken(String username, String password, String loginTime ,String tokenSecret) {
        try {
            // 设置过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(tokenSecret);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");
            // 返回token字符串
            return JWT.create()
                    .withHeader(header)
                    .withClaim("loginName", username)
                    .withClaim("password", password)
                    .withClaim("loginTime", loginTime)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 检验token是否正确
     * @param token 需要校验的token
     * @return 校验是否成功
     */
    public static boolean verifyToken(String token ,String tokenSecret){
        try {
            //设置签名的加密算法:HMAC256
            Algorithm algorithm = Algorithm.HMAC256(tokenSecret);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e){
            return false;
        }
    }
    
 
}

おすすめ

転載: blog.csdn.net/dongjing991/article/details/131438932