中文 | English
A library that easily implement API signature verification.
This Repository contains the following:
- Source code for signature verification
- Web example based on Spring Boot
Due to the need to open the API for third parties to call, and use signature verification to ensure security, this project was created.
The project uses an AOP to verify signatures, and the API itself only needs to care about the processing of business logic.
At the same time, it prevents replay attacks and also supports the customization of encryption rules and parameter fields.
1. Add maven dependency
<dependency>
<groupId>cn.oever</groupId>
<artifactId>api-signed</artifactId>
<version>0.0.1</version>
</dependency>
YAML format:
oever:
signature:
time-diff-max: 300
algorithm: HmacSHA1
redis:
host: 127.0.0.1
port: 6379
If you use the properties format configuration file, it should look like this:
oever.signature.time-diff-max=300
oever.signature.algorithm=HmacSHA1
redis.host=127.0.0.1
redis.port=6379
Parameter | Description |
---|---|
time-diff-max |
The maximum allowable difference between the caller and server timestamps |
algorithm |
The standard name of the MAC algorithm, you can refer to appendix A in the Java Cryptography Architecture Reference Guide |
In addition, this project uses Redis as a cache implementation.
@SpringBootApplication
@SignedScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Add the @SignedScan
annotation to the startup class to introduce related implementations.
So far, all the configurations have been completed, and you can try to implement an API for signature verification.
@RestController
@RequestMapping("api")
@SignedMapping
public class TestController {
@RequestMapping("test")
public String test(@RequestBody SignedParam signedParam) {
// the request data is signedParam.getData() in JSON
// then do something in service
return "SUCCESS";
}
}
As above, you only need to use the @SignedMapping
annotation on the API, and the request parameter is the SignedParam
class, then the signature verification can be achieved.
@SignedMapping
annotation can be applied to the class or methods.
Of course, for some requirements, the parameters in the default SignedParam
class may not be satisfied, and you may even need to implement a set of encryption rules yourself, see Custom.
Name | Value |
---|---|
Key | APP_ID_TEST |
appSecret | APP_SECRET_TEST |
POST
Name | Type | Description |
---|---|---|
data | String | The value is related to the specific request API, and the format is a string of JSON body |
appId | String | ID assigned to the caller |
timestamp | Long | 10 digits timestamp, if the time difference between the caller and the server is too large, the request will be rejected |
nonce | Integer | Random integer, used in conjunction with timestamp to prevent replay attacks |
signature | String | The signature used to verify the legitimacy of this request |
The request parameters are sorted in ascending order according to the ASCII order of the parameter name (the first letter is lowercase), and the parameters involved in the sorting include all request parameters except signature.
When the data of the business request parameter is a JSON object, it needs to be converted into a string to participate in sorting and signature calculation.
For example, the request parameter is:
{
"userId": "test"
}
Then, the complete list of request parameters:
Key | Value | Description |
---|---|---|
data | "{\"userId\":\"test\"}" |
Request parameter |
appId | APP_ID_TEST | Test ID |
nonce | -2028703096 | Random integer |
timestamp | 1597415679 | Timestamp when the request was initiated |
signature | To be calculated | Signature value |
The sorted result is:
{
"appId": "APP_ID_TEST",
"data": "{\"userId\":\"test\"}",
"nonce": -2028703096,
"timestamp": 1597415679
}
Format the sorted request parameters according to the form of key=value
, and then splice each parameter together with an &
in order to obtain the string plainText to be signed
appId=APP_ID_TEST&data={"userId":"test"}&nonce=-2028703096×tamp=1597415679
Take the HMAC-SHA1 algorithm as an example to encrypt plainText, and then use Base64 to encode the encrypted byte stream, and get the final signature signature
signature=base64_encode(hash_hmac('sha1', $plainText, $appSecret, true));
curl -v -X POST "127.0.0.1:8080/example/base" -H "Accept: application/json" -H "Content-Type: application/json; charset=utf-8" -d '{"data":"{\"userId\":\"test\"}","signature":"tFACzWpdduGputwIzxffmkJwij8=","appId":"APP_ID_TEST","nonce":-2028703096,"timestamp":1597415679}'
Take the request parameter class that we implement by default as an example:
@SignedEntity
public class SignedParam {
@SignedAppId
private String appId;
private String data;
@SignedTimestamp
private long timestamp;
@SignedNonce
private int nonce;
@Signature
private String signature;
// getter and setter...
}
Annotation | Description |
---|---|
@SignedEntity |
The class is marked as a request parameter class that requires signature calculation |
@SignedAppId |
The field is marked as the caller’s appId, we will use the value of this field to get the corresponding appSecret for signature calculation |
@SignedTimestamp |
The field is marked as the timestamp of the request to check the time difference between the caller and the server |
@SignedNonce |
The field is marked as a random number, used in conjunction with the timestamp to prevent replay attacks |
@SignedIgnore |
It is marked that the field does not participate in the calculation of the signature |
@Signature |
It is marked that this field is the signature calculated by the caller. This field will not participate in the calculation of the signature, but compared with this field after the signature is calculated by other fields |
Among them, the data
field without any annotations is divided into JSON formatted strings and processed by business logic. This segment is still involved in the signature calculation.
Extends the BaseSignedService
and override the method that needs to be modified
Method | Parameters | Parameter description | Return | Method description |
---|---|---|---|---|
getAppSecret |
String appId |
appId | String appSecret |
Get the corresponding appSecret through appId, the default implementation uses Redis, if you use other caches, you can override this method |
isTimeDiffLarge |
long timestamp |
Timestamp | void |
Determine whether the time difference between the caller and the server exceeds the maximum value set, if it exceeds the maximum value, an exception will be thrown |
isReplayAttack |
String appId, long timestamp, int nonce, String signature |
appId, Timestamp, random number, signature | void |
With appId + timestamp + random number as the key and signature as the value, each request is judged and cached. If it exists, an exception will be thrown |
getSignature |
String appId, Map map |
appId,Parameters involved in calculating the signature | String signature |
Calculate the signature through the appId and the parameter list that needs to be calculated. If you need to customize the encryption rules, you can override this method |
entry |
Object obj |
The entity class of the request parameter | void |
Entry method, which sequentially calls the non-empty judgment of the parameters, the time difference, whether to replay the attack, and the calculation of the signature. If you need to add or modify additional verification steps, you can override this method. If you only need one or a few of the above steps, it is better to override the unnecessary method and leave it blank |
Then, where you use the annotation of @SignedMapping
, add a custom class as a parameter, such as:
@RequestMapping("test")
-@SignedMapping
+@SignedMapping(CustomizeSignedService.class)
public String test(@RequestBody SignedParam signedParam) {
return "SUCCESS";
}
At this time, the signature verification will be taken over by the overridden method in the custom class.
In the example, you can see three examples of default implementation, custom parameters, and custom implementation.
And each example provides two APIs for signature generation and signature verification, so you can try it yourself.
MIT License
Copyright (c) 2020 刘思宁
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.