A java helper utilities that form HTTP security header for authentication and verification
Include this helper class in your java project to perform API Security operations
This project use Maven or Gradle as its build and management tools
- Download and Install Maven (3.5.0 or above)
- Java (1.8)
Option 1: Compile and package into JAR
mvn package
The compiled jar file will be located in the target folder
- java-apex-api-security--SNAPSHOT.jar
- java-apex-api-security--SNAPSHOT-jar-with-dependencies.jar (this includes log4j libraries)
Import this jar file into your java classpath to use the utility class
Option 2: Compile and install the package into your local maven repository
mvn install
-
The compiled package file will be installed under com.api.util.ApiSecurity in the .m2 repo
-
For Maven Client Project setup, add the following dependencies to your pom.xml file :
<dependency>
<groupId>com.api.util</groupId>
<artifactId>ApiSecurity</artifactId>
<version>1.3.2-SNAPSHOT</version>
</dependency>
Note:
- This project is leveraging on slf4j-log4j12 framework for the logging. If you are using logging implementation other than log4j, you can change to other type of implementation such as nop,simple,jdk14,logback. You could replace the following xml in pom.xml.
- If your are using Log4j Version2, please refer to Log4j2-SLF4J Binding
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
Pull centralised Unit Test-cases from the following Github url: https://github.com/GovTechSG/test-suites-apex-api-security/tree/master with Mavem command (in project root folder)
mvn scm:checkout -D checkoutDirectory=src/main/resources/test-suites
To execute unit-test with Maven command (in project root folder)
mvn test
- Download and Install Gradle (4.0 or above)
- Java (1.8)
As some of the test cases contains UTF-8 characters, you have to set following property before the JVM execute the Gradle Daemon.
export GRADLE_OPTS="-Dfile.encoding=utf-8"
Option 1: Compile and package into JAR
gradle clean build
To test with Jacoco and publish a html report
gradle test jacocoTestReport
The compiled jar file will be located in the build/libs folder
- java-apex-api-security-1.0-SNAPSHOT.jar
Import this jar into your java classpath to use the utility class
Option 2: Compile and install the package into your local maven repository
-
Refer to Maven Guide > Build > Option 2, for the maven repo installation (excluding Maven Client Project setup)
-
For Gradle Client Project setup, add the following dependencies to your build.gradle file :
repositories {
mavenLocal()
}
dependencies {
compile group: 'com.api.util', name: 'ApiSecurity', version: '1.3.1-SNAPSHOT'
}
Append this signature token into the Authorization header of the HTTP request
Params:
- realm
- authPrefix - Authorization Header scheme prefix , i.e 'Apex_l2_eg'
- httpMethod
- urlPath - Signing URL, remember to append <>.e.api.gov.sg or <>-pvt.i.api.gov.sg in <>
- appId - App ID created in Gateway
- secret - set to null for REST L2 SHA256WITHRSA
- formData - to support parameter for form data if any
- password
- alias
- fileName
- nonce - set to null for random generated number
- timestamp - set to null for current timestamp
String realm = "<<your_client_host_url>>"
String authPrefix = "<<authPrefix>>
String httpMethod = "get"
//Append the query param in the url or else add as ApiList
String signingUrl = "https://<<URL>>/api/v1/?param1=first¶m2=123";
String certFileName = "certificates/ssc.alpha.example.com.p12";
String password = "<<passphrase>>";
String alias = "alpha";
String appId = "<<appId>>";
String secret = null;
//only needed for Content-Type: application/x-www-form-urlencoded, else null
ApiList formData = null;
String nonce = null;
String timestamp = null;
//optional for QueryParam - in-case not append the query parameters in the signingUrl
//Sring signingUrl = "https://<<tenant>>-pvt.i.api.gov.sg/api/v1"
ApiList queryParam = new ApiList();
queryParam.add("query1","value1");
//optional for formData
formData = new ApiList();
formData.add("param1", "data1");
//If queryParam and formData are both available, combine the list before submitting
formData.addAll(queryParam);
try {
String signature = ApiSigning.getSignatureToken(authPrefix, authPrefix, httpMethod, signingUrl, appId, secret, formData, password, alias, certFileName, nonce, timestamp);
} catch (ApiUtilException e) {
e.printStackTrace();
}
NOTE
For formData parameter used for Signature generation, the key value parameters do not need to be URL encoded, When your client program is making the actual HTTP POST call, the key value parameters has to be URL encoded (refer to formPostData)
Method:
- getBaseString
Params:
- authPrefix - Authorization Header scheme prefix , i.e 'Apex_l2_eg'
- signatureMethod
- appId - App ID created in Gateway
- urlPath
- httpMethod
- formData - only needed for Content-Type: application/x-www-form-urlencoded
- nonce - set to null for random generated number
- timestamp - set to null for current timestamp
String signingUrl = "https://<<URL>>/api/v1/?param1=first¶m2=123";
ApiList formData = new ApiList();
formData.add("param1", "data1");
String baseString;
try {
baseString = ApiSigning.getBaseString(
"<<authPrefix>>",
"HMACSHA256",
"<<appId>>",
signingUrl,
"post",
formData,
"6584351262900708156",
"1502184161702"
);
System.out.println(baseString);
} catch (ApiUtilException e) {
e.printStackTrace();
}
Method:
- getHMACSignature
Params:
- baseString
- secret
String baseString = "GET&https://<<URL>>/api/v1/&ap=裕廊坊 心邻坊&<<authPrefix>>_app_id=<<appID>&<<authPrefix>>_nonce=2851111144329605674&<<authPrefix>>_signature_method=HMACSHA256&<<authPrefix>>_timestamp=1502163903712&<<authPrefix>>_version=1.0";
String secret = "<<appSecret>>";
String L1Sig;
try {
L1Sig = ApiSigning.getHMACSignature(baseString, secret);
System.out.println(L1Sig);
} catch (ApiUtilException e) {
e.printStackTrace();
}
Method:
- getRSASignature
Params:
- baseString
- privateKey
String baseString = "GET&https://<<URL>/api/v1/&ap=裕廊坊 心邻坊&<<authPrefix>>_app_id=<<appId>>&<<authPrefix>>_nonce=7231415196459608363&<<authPrefix>>_signature_method=SHA256withRSA&<<authPrefix>>_timestamp=1502164219425&<<authPrefix>>_version=1.0&oq=123&q=abc";
String alias = "alpha";
String password = "<<passphrase>>";
String keyStoreFileName = "certificates/ssc.alpha.example.com.p12";
String publicCertFileName = "certificates/ssc.alpha.example.com.cer";
try {
PrivateKey privateKey = ApiSigning.getPrivateKeyFromKeyStore(keyStoreFileName, password, alias);
String signature = ApiSigning.getRSASignature(baseString, privateKey);
System.out.println(expectedSignature);
System.out.println(signature);
PublicKey publicKey = ApiSigning.getPublicKeyFromX509Certificate(publicCertFileName);
} catch (ApexUtilLibException e) {
e.printStackTrace();
}
@Test
public void Http_Call_Test() throws ApiUtilException, IOException
{
String httpMethod = "POST";
//URL for actual HTTP API call
String url = "https://tenant.api.gov.sg:443/api14021live/resource";
//URL for passing as parameter for APEX Signature Token generation
String signUrl = "https://tenant.e.api.gov.sg:443/api14021live/resource";
String appId = "tenant-1X2w7NQPzjO2azDu904XI5AE";
String secret = "s0m3s3cr3t";
ApiList formData = new ApiList();
formData.add("key1", "value1");
formData.add("key2","value2");
String authorizationToken = ApiSigning.getSignatureToken(
realm
, authPrefixL1
, httpMethod
, signUrl
, appId
, secret
, formData
, null
, null
, null
, null
, null
);
System.out.println("authorizationToken : "+authorizationToken);
try {
//ignore SSL
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, getTrustManager(), new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
con.setDoOutput(true);
con.setDoInput(true);
con.setRequestMethod(httpMethod);
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setRequestProperty("charset", "utf-8");
con.setRequestProperty("Authorization", authorizationToken);
con.setUseCaches(false);
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
DataOutputStream out = new DataOutputStream(con.getOutputStream());
ApiList formPostData = new ApiList();
formPostData.add("key1",URLEncoder.encode("value1", "UTF-8"));
formPostData.add("key2",URLEncoder.encode("value2", "UTF-8"));
out.writeBytes(formPostData.toString(false));
out.flush();
out.close();
System.out.println("Start http call ...");
int status = -1;
status = con.getResponseCode();
System.out.println("HTTP Status:" + status);
System.out.println("End http call ...");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
System.out.println("Content:" + content);
in.close();
con.disconnect();
}catch(Exception e){
System.out.println("Error executing Http_Call_Test() : " + e);
}
//force to true to pass the test case
assertTrue(true);
}
For more information about contributing PRs and issues, see CONTRIBUTING.md.
See CHANGELOG.md.