1: Background
Recently, we are doing service docking with a third party. The third party needs to call our service interface to query data. For the exposed interface, security is very important, so we must add a signature when doing interface verification, which can effectively protect our interface security
2: Encryption
It is impossible to let the data run naked in the network, so we need to encrypt the interface parameters or the returned data, and we must use the state secret SM4 to encrypt the requirements of the requester (Party A)
3: Signature
In addition to the request parameters, we also need to include the signature in the data sent. After we receive the request, we judge whether the signature is legal. If it is not legal, we will not deal with it, but there will be problems. If the hacker intercepts the request and resends it again and again, your server will pass. Because your signature has not been changed, I just send the request repeatedly
4: Time stamp
The signature is not just an agreed string, we also need to add a timestamp. Add the time stamp when the request is sent to the sender's signature. When the server receives the request, it decrypts and obtains the time stamp. If the time stamp in the signature is more than 1 minute or 2 minutes from the current time (this time is defined by itself), the request will not be processed or the request failure information will be returned
client: //The agreed string in the signature String sign = "adnchgsnsb"; //Gets the current timestamp long timeMillis = System.currentTimeMillis(); //Splicing signature StringBuilder sb = new StringBuilder(); sb.append(sign).append('-').append(timeMillis); //Encrypt signature SM4.decode(sb.toString); //Call remote service
Server: //Get signature from parameter String sign = sign; //Gets the timestamp and agreed signature string from the signature String[] strs = sign.split('-'); //Gets the signed string String singStr = strs.[0]; //Judge whether the signature string is agreed //Get timestamp String time = strs[1]; //Get current timestamp long timeMillis = System.currentTimeMillis(); //Judge whether the time of both exceeds the limit if(Beyond) { return "request timeout"; }else{ //Processing requests }
It seems that it does meet our requirements, but there will still be problems in practice. For example, how much is the timeout appropriate?? At the beginning, I set it to 10s, but then there were a lot of request timeout exceptions, which led the customer to keep saying,
There's no way to do this later. I can only set the time longer, but the time is set longer, and the customer's response timeout exception is almost gone. But now there's another problem. Suppose you set the time to 5 minutes (yes, I set it for so long), you still can't solve the problem of requesting replay, because it's too long, After you send a request, others get your request and have enough time to operate
5: Add Redis
At this time, our signature consists of signature string + timestamp. Now we add a random number,
client: //The agreed string in the signature String sign = "adnchgsnsb"; //Gets the current timestamp long timeMillis = System.currentTimeMillis(); //Get random number String uuid = UUID.randomUUID().toString(); //Splicing signature StringBuilder sb = new StringBuilder(); sb.append(sign).append('-').append(timeMillis).append('-').append(uuid); //Encrypt signature SM4.decode(sb.toString); //Call remote service
Server: //Get signature from parameter String sign = sign; //Gets the timestamp and agreed signature string from the signature String[] strs = sign.split('-'); //Gets the signed string String singStr = strs.[0]; //Judge whether the signature string is agreed //Get timestamp String time = strs[1]; //Get current timestamp long timeMillis = System.currentTimeMillis(); //Get random number String uuid = strs[2]; //Judge whether the time of both exceeds the limit if(Beyond) { return "request timeout"; }else{ //Determine whether the request has been processed before Boolean uuid = redisUtil.get(uuid); //If it exists, it indicates that the request has been processed before, so it will not be processed if(uuid) { return "The request has been processed"; }else{ //If it does not exist, it can be handled normally //Processing requests //Put the random number into redis redisUtil.set(uuid,uuid); //Do not set expiration time } }
At this time, we can see that the key in redis does not set the expiration time, which will lead to higher and higher memory consumption in redis on the server
6: Optimize
Let's think about it. The key in redis can actually set the expiration time, and the size of this time is just the time to judge the timestamp. If we set the request time to 5 minutes, I just need to ensure that I don't receive the request within these 5 minutes. After 5 minutes, if it's still the request, In fact, it can be filtered when judging where the timestamp is, so the server code is modified as follows
Server: //Get signature from parameter String sign = sign; //Gets the timestamp and agreed signature string from the signature String[] strs = sign.split('-'); //Gets the signed string String singStr = strs.[0]; //Judge whether the signature string is agreed //Get timestamp String time = strs[1]; //Get current timestamp long timeMillis = System.currentTimeMillis(); //Get random number String uuid = strs[2]; //Judge whether the time of both exceeds the limit if(Beyond) { return "request timeout"; }else{ //Determine whether the request has been processed before Boolean uuid = redisUtil.get(uuid); //If it exists, it indicates that the request has been processed before, so it will not be processed if(uuid) { return "The request has been processed"; }else{ //If it does not exist, it can be handled normally //Processing requests //Put the random number into redis redisUtil.set(uuid,uuid,5); //Set the expiration time to 5 minutes } }