JAVA development (external interface call authorization problem record summary)

1. Technical background:

Now many web projects or small programs need to conduct cross-drainage, cross-business cooperation, data transmission, and cooperation with other companies, websites, and apps after they go online. Then you need to call the interface data. Then making external system interface calls and interface calls between self-developed microservices are obviously different. The most obvious feature is that calls between systems first require authorization. This means that you need to log in first. If there is no authorized interface call, obviously all information has been exposed to the Internet environment. The second is that the transmitted data needs to be encrypted and decrypted; if there is no encryption, you can also see what information is transmitted between the systems through packet capture. Therefore, when making some external interface calls, these two parts will be involved. However, the authorization and encryption and decryption methods of many systems are not the same. A similar thing with minor differences always makes us keep making wheels. Sometimes the interface interaction between system A and system B is a set of authorization and encryption and decryption algorithms, and system A and system C are another set of authorization and encryption and decryption algorithms. The blogger thinks that we only need a few sets of general authorization and encryption and decryption algorithms, instead of constantly reinventing the wheel. We put more experience in business development.

2. Types of external system interface call authorization

1. Obtain the token, and within the validity period of the token, carry the token as a parameter to have the authority to call all interfaces

2. Carry the signature every time to call the interface.

3. Introduction and production of token

There are two commonly used authorization standards in the industry. One is to use auth2. This method is more suitable for third-party authorized logins, such as WeChat, Weibo, and QQ trust login services. The other is oauth, that is, a third party can apply for authorization to obtain the resource without knowing the user and password, which is more suitable for verifying user permissions and assigning access permissions, such as the common allocation of visible resources after login (buttons, menus, etc. ) type site.

 

Flow Description:

Login
The server verifies the password, returns access_token and refresh_token after success, and the client records the above token.
To access the API,
parse the access_token before accessing the API, and check whether it is expired. If it is not expired, request the API. If it expires, refresh the token and request the API.
The refresh token
carries a refresh_token with an expiration date to exchange for a valid token. If the refresh_token expires, the user needs to log in again.
Logout
Request to log out of the api, the server and the client should delete the storage of the token at the same time. 

 The client requests the API
to carry the access_token information. If the generation environment does not directly carry the access_token, the encrypted signature will be used for verification. See below for the anti-replay mechanism.
Obtaining tokens
There are different ways to obtain tokens depending on the environment.
Parse the token
through the JWT tool to parse the token.
Read the token by redis
and read the access_token according to the uid splicing key. If the token of this user does not exist, it means that the user has logged out.
Verify the token
to determine whether the secondary token belongs to this uid, determine whether the token has expired, and if it expires, perform the following process of refreshing the token.
Injection permissions
If the token verification is successful, generate permissions based on user information and inject them into the spring security context.

The client requests the API
to carry the refresh_token. If the production environment does not directly carry the refresh_token information, see the following anti-replay attack for details.
Obtaining tokens
There are different ways to obtain tokens depending on the environment.
Parse the token
through the JWT tool to parse the token.
Token reading
Read the access_token according to the uid splicing key. If the user's token does not exist, it means that the user has logged out.
Verify the token
to determine whether the token belongs to the uid, and determine whether the token has expired. If it expires, it will return a refresh_token expired error, and the user needs to log in again.
Refresh token
If the refresh_token verification is successful, the access_token and refresh_token will be regenerated. The validity period above is calculated backwards from the current time, and the user's token in redis will be replaced, and the token will be returned to the client. 

4. Obtain token instance

1. Obtain token parameters

	/**
	 * 组装获取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;
	}

Obtaining token is actually injecting user name and secret key to obtain a credential, which is actually a login process.

2. Carry the token to request the interface instance:

	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;
		
	}

In fact, it is to add the token parameter on the basis of the original required transmission parameters. Let the other party verify the permissions.

5. Introduction of signature

Signing is equivalent to generating a lock and a key by myself, then locking the content I want to publish with my lock to form a signature, publishing the content and signature together, and telling everyone what my key is. People can get the key to open the content in the signature to verify that it is consistent with the published content. Everyone in the world can get the key to verify the consistency of the signature and the content, but only I have the lock with the signature.

Signature instance:

	/**
	 * 根据密钥和消息生成签名
	 * 
	 * @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;
	}

The secret key here is agreed upon by the two systems. So I also think that both parties have a key, and only if the signature is passed, there will be authority.

Call the interface instance through signature verification:

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;
	}

Token generation tool class

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;
        }
    }
    
 
}

Guess you like

Origin blog.csdn.net/dongjing991/article/details/131438932