Java-SDK
Java-SDK
概述
差旅Java-SDK是差旅平台面向IT系统java开发者提供的服务端开发工具包,其中包含签名、验签、加密以及解密相关工具。 通过Java-SDK,开发者可以高效的完成差旅系统对接,无需关心具体的接口鉴权细节,同时提供了接口的定义,提升开发者的对接效率。
本sdk 支持 jdk1.8+,并且对应版本需要支持第三方开源组件Bouncy Castle。
使用说明
步骤一:引入SDK包
下载Java-SDK文件,并将库文件导入到Java项目中,导入方法:
1、在idea上点击file-->Project Structure
2、选择Libraries--->点击+号-->选择Java
3、找到放jar文件的位置,选中jar文件,点击OK,然后下一步一直到最后选择OK就可以了
或者使用maven管理三方依赖,在pom文件中引入(需要将jar包下载后发布到内部仓库中)
<dependency>
<groupId>com.huawei.itravel.trip.sdk</groupId>
<artifactId>java-sdk</artifactId>
<version>2.0.10</version>
</dependency>
步骤二:准备
公钥私钥文件(公钥提供给差旅平台)
生成方法: SDK支持导入openssl生成的密钥文件以及java keytool 生成的密钥文件。 openssl 或 keytool 二选一
openssl:
私钥:(企业可自行修改口令值123456为其它值)
openssl genrsa -out rsaprivatekey_pass.pem -passout pass:123456 -aes256 4096
公钥:
openssl req -new -x509 -key rsaprivatekey_pass.pem -out rsapublic_cert.cert -days 1095 -passin pass:123456
密钥对加载方法:
// 慧通私钥,不对外提供
PrivateKey serverPrivate = CipherUtil.loadPrivateKey(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/server/private.pem"), "pem",
"123456");
// 该公钥由慧通提供
PublicKey serverPublic = CipherUtil.loadCER(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/server/public.cert")).getPublicKey();
keytool:
生成密钥文件:(企业可自行修改别名、密码等为其他值)
keytool -genkeypair -alias CLIENT -keyalg RSA -storetype JKS -sigalg SHA256withRSA -storepass 123456 -keystore D://CLIENT.keystore
提取公钥:
keytool -export -alias CLIENT -file D://client_rfc.crt -keystore d://CLIENT.keystore -rfc
密钥对加载方法:
// 私钥由客户自己生成,对应的公钥需要在慧通系统里配置(可以联系企业管理员在前台配置)
PrivateKey clientPrivate = CipherUtil.loadKeyStore(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/client/CLIENT.keystore"),
"CLIENT", "JKS", "123456", "123456").getPrivate();
// 客户公钥,提供给慧通
PublicKey clientPublic = CipherUtil.loadKeyStore(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/client/CLIENT.keystore"),
"CLIENT", "JKS", "123456", "123456").getPublic();
企业密码(差旅平台分配)
如:ADADADADADADADADADADADADADADADAD
对接账号Id(差旅平台分配)
如:OpenApiCompanyNum
步骤三:使用示例
下面的实例代码中均提供了两种密钥加载方法,根据步骤二选择的密钥生成工具选择对应的加载方法即可 初始化api client
public class ApiCall {
// 企业对接账号,可以联系企业管理员获取
protected static final String AUTH_ID = "OpenApiCompanyNum";
// 企业密码,可以联系企业管理员在前台配置
private static final String AES_KEY = "ADADADADADADADADADADADADADADADAD";
// 对接的环境域名
private static final String SERVER_ADDRESS = "https://openapi-uat.hwht.com";
protected static final ApiClient API_CLIENT;
protected static final ClientAuth clientAuth;
private static final Logger logger = LoggerFactory.getLogger(ApiTest.class);
static {
PrivateKey clientPrivate;
PublicKey serverPublic;
try {
// 私钥由客户自己生成,对应的公钥需要在慧通系统里配置(可以联系企业管理员在前台配置)
clientPrivate = CipherUtil.loadPrivateKey(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/private.pem"), "pem",
"123456");
// 该公钥由慧通提供
serverPublic = CipherUtil.loadCER(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/public.cert")).getPublicKey();
} catch (Exception ex) {
SdkUtil.logError("init cert fail {}", ex);
throw new RuntimeException(ex);
}
clientAuth = new ClientAuth(AUTH_ID, AES_KEY, clientPrivate, serverPublic);
// 创建一个调用客户端,可以通过实现ApiHttpClient接口来封装自定义的client,该Demo采用默认的client
API_CLIENT = SdkFactory.createClient(clientAuth, SERVER_ADDRESS, new DefaultHttpClient());
// 根据需求决定是否放开日志打印
SdkFactory.funcLogInfo = logger::info;
SdkFactory.funcLogDebug = logger::debug;
SdkFactory.funcLogError = logger::error;
}
}
接口调用
public class App extends ApiCall {
public static void main(String[] args) {
// 测试单点登录
testSSO() ;
// 查询审批单列表
testTrListQuery() ;
}
public static void testSSO() {
OpenApiUrlLoginReq req = new OpenApiUrlLoginReq();
req.setCorpCode(AUTH_ID);
req.setLoginName("GL_99999");
try {
String queryString = clientAuth.getQueryStringByRequest("GET", "/v3/urllogin", SdkUtil.toJson(req));
SdkUtil.logInfo("https://openapi-uat.hwht.com/v3/urllogin?" + queryString);
//SdkUtil.logInfo("https://m-itravel-uat.hwht.com/openapi/v3/urllogin?" + queryString);
} catch (Exception e) {
SdkUtil.logInfo(e.toString());
}
}
public static void testTrListQuery() {
OpenApiQueryApproveListReq req = new OpenApiQueryApproveListReq();
req.setVersion("3.0");
req.setTimestamp(SdkUtil.toYYYYMMddHHmmss(LocalDateTime.now()));
req.setMsgID(AUTH_ID+SdkUtil.toYYYYMMddHHmmssSSS(LocalDateTime.now()));
req.setCorpCode(AUTH_ID);
req.setLoginName("sysadmin");
req.setStartCreateTime("20230701");
req.setEndCreateTime("20230731");
req.setPageIndex("1");
req.setSize("10");
// 客户端调用同步接口
OpenApiQueryApproveListRsp resp = API_CLIENT.createApi(TrApi.class).queryTrList(req);
// 打印返回结果
SdkUtil.logInfo("respose is {}", resp);
SdkUtil.logInfo("respose is {}", resp.isSuccess());
}
}
纯JSON格式调用OpenApi接口
SDK中提供的工具大多都是public的,可以自行组合使用 如下面这个例子,可以直接将json消息,签名、加密后通过httpclint调用OpenApi接口
OpenApiTrSyncReq syncRequest = getSyncRequest();
String url = "https://openapi-uat.hwht.com/v3/syntr";
String body = SdkUtil.toJson(syncRequest);
String auth = clientAuth.signClientRequest("POST", "/v3/syntr", body);
String encrypted = clientAuth.encryptClientRequestWithJson(body);
Map<String, String> headers = new HashMap<>();
headers.put(com.google.common.net.HttpHeaders.AUTHORIZATION, auth);
headers.put(com.google.common.net.HttpHeaders.CONTENT_TYPE, "application/json");
headers.put(com.google.common.net.HttpHeaders.ACCEPT, "application/json");
ApiHttpClient.HttpResult result = new DefaultHttpClient().post(url, headers, encrypted, 10000, 60000);
String respString = clientAuth.verifyServerResponse("POST", "/v3/syntr", result.getAuthorization(), result.getBody());
OpenApiTrSyncResp resp = SdkUtil.fromJson(respString, OpenApiTrSyncResp.class);
其中clientAuth初始化方法参考上面的例子。
接口分组与API对应关系
具体分组里面的方法名基本符合url的小驼峰格式,具体可以点击接口查看url详情 如:url为adduserinfo,则方法名为addUserInfo
接口分组 | API |
---|---|
成员管理 | MemberApi |
部门管理 | DeptApi |
成本中心 | CostCenterApi |
企业自有审批单管理 | TrApi |
审批管理 | TrApi |
城市编码 | CityApi |
附加功能 | OtherApi |
国内机票 | AirOrderApi |
国际机票 | AirOrderApi |
国内火车票 | TrainApi |
国内/国际用车 | CarApi |
酒店订单 | HotelApi |
结算单 | AccountApi |
其他功能 | OtherApi |
附springboot工程示例
新建配置类
package com.hwht.trip.sdk;
import com.huawei.trip.sdk.auth.CipherUtil;
import com.huawei.trip.sdk.auth.ClientAuth;
import com.huawei.trip.sdk.auth.ServerAuth;
import com.huawei.trip.sdk.spring.TripApiFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.Duration;
@Configuration
public class OpenApiConfiguration {
// 企业对接账号,可以联系企业管理员获取
protected static final String CLIENT_AUTH_ID = "OpenApiCompanyNum";
protected static final String SERVER_AUTH_ID = "HWHT";
// 企业密码,可以联系企业管理员在前台配置
private static final String AES_KEY = "ADADADADADADADADADADADADADADADAD";
@Bean
public ClientAuth clientAuthRegistration() {
PrivateKey clientPrivate;
PublicKey serverPublic;
try {
// 私钥由客户自己生成,对应的公钥需要在慧通系统里配置(可以联系企业管理员在前台配置)
clientPrivate = CipherUtil.loadKeyStore(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/client/CLIENT.keystore"),
"CLIENT", "JKS", "123456", "123456").getPrivate();
// 该公钥由慧通提供
serverPublic = CipherUtil.loadCER(
Thread.currentThread().getContextClassLoader().getResourceAsStream("cert/server/public.cert")).getPublicKey();
} catch (Exception ex) {
SdkUtil.logError("init cert fail {}", ex);
throw new RuntimeException(ex);
}
return new ClientAuth(CLIENT_AUTH_ID, AES_KEY, clientPrivate, serverPublic);
}
@Bean
public TripApiFilter tripApiFilterRegistration(@Autowired ClientAuth clientAuth) {
TripApiFilter tripApiFilter = new TripApiFilter(clientAuth);
return tripApiFilter;
}
@Bean
@Qualifier("tripApiClient")
public RestTemplate openApiClient(@Autowired RestTemplateBuilder builder) {
// 专门用于调用Open API的客户端,可能还需要自定义ClientHttpRequestFactory等来精细控制并发策略、SSL等
return builder.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(60))
.build();
}
@Bean
public FilterRegistrationBean<TripApiFilter> authFilterRegistration(@Autowired TripApiFilter tripApiFilter) {
// 拦截Open API请求,根据自己的开放接口的url进行配置
FilterRegistrationBean<TripApiFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(tripApiFilter);
reg.addUrlPatterns("/openapi/*");
reg.setName("TripApiAuthFilter");
return reg;
}
}
其中 authFilterRegistration 方法为可选,可以实现回调接口(提供给慧通的接口,例如订单状态通知)请求消息解密、鉴权,响应消息签名、加密,客户只需要处理业务逻辑即可
authFilterRegistration 方法中用到的TripApiFilter实现
import com.huawei.trip.sdk.SdkUtil;
import com.huawei.trip.sdk.auth.ClientAuth;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import javax.security.auth.message.AuthException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 通过拦截Servlet的请求,对Open API进行验签并解密数据,spring项目按需使用
*/
public class TripApiFilter implements Filter {
private final ClientAuth clientAuth;
public TripApiFilter(ClientAuth clientAuth) {
this.clientAuth = clientAuth;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse rsp = (HttpServletResponse) servletResponse;
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(req);
HttpResponseWrapper responseWrapper = new HttpResponseWrapper(rsp);
SdkUtil.logInfo("do open api auth filter for {}", req.getRequestURI());
try {
String authorization;
String encryptedBody;
String httpMethod = req.getMethod();
if ("GET".equals(httpMethod)) {
authorization = req.getQueryString();
encryptedBody = req.getParameter("encrypt");
encryptedBody = "{\"encrypt\":\"" + encryptedBody + "\"}";
} else {
authorization = req.getHeader(HttpHeaders.AUTHORIZATION);
encryptedBody = requestWrapper.getBody();
}
SdkUtil.logDebug("receive authorization: {}", authorization);
SdkUtil.logDebug("receive encrypted: {}", encryptedBody);
String body = clientAuth.verifyClientRequest(req.getMethod(), req.getRequestURI(), authorization,
encryptedBody);
requestWrapper.setBody(body);
if ("GET".equals(httpMethod)) {
Map<String, String[]> paramsMap = new HashMap<>();
((Map<String, String>) SdkUtil.fromJson(body, Map.class))
.forEach((key, value) -> paramsMap.put(key, new String[]{value}));
requestWrapper.setParamsMap(paramsMap);
}
// 原处理链
filterChain.doFilter(requestWrapper, responseWrapper);
// 响应消息签名和转换
body = responseWrapper.getCaptureBody();
authorization = clientAuth.signServerResponse(req.getMethod(), req.getRequestURI(), body);
SdkUtil.logDebug("response authorization: {}", authorization);
String encrypt = clientAuth.encryptServerResponse(body);
SdkUtil.logDebug("response body: {}", encrypt);
rsp.addHeader(HttpHeaders.AUTHORIZATION, authorization);
rsp.getWriter().write(encrypt);
} catch (AuthException ex) {
SdkUtil.logError(ex.getMessage(), ex);
rsp.sendError(HttpStatus.UNAUTHORIZED.value(), ex.getMessage());
} catch (Exception ex) {
SdkUtil.logError(ex.getMessage(), ex);
rsp.sendError(HttpStatus.UNAUTHORIZED.value(), ex.getMessage());
}
}
}
其中 openApiClient 方法为可选,可以实现自定义http调用客户端,自定义后通过下面的示例配置和使用 自定义http调用客户端(可选)
import com.huawei.trip.sdk.client.ApiHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
/**
* Spring配置示例代码
*/
@Slf4j
@Component
public class RestTemplateClient implements ApiHttpClient {
@Autowired
@Qualifier("tripApiClient")
private RestTemplate tripApiClient;
@Override
public HttpResult post(String url, Map<String, String> headers, String body, int connTimeout, int readTimeout) {
HttpHeaders httpHeaders = new HttpHeaders();
headers.forEach((key, value) -> httpHeaders.add(key, value));
HttpEntity<String> httpEntity = new HttpEntity(body, httpHeaders);
log.info("send header: {}", headers);
log.info("send body: {}", body);
ResponseEntity<String> rsp = tripApiClient.exchange(url, HttpMethod.POST, httpEntity, String.class);
return new HttpResult(rsp.getHeaders().getFirst(HttpHeaders.AUTHORIZATION), rsp.getBody());
}
@Override
public HttpResult get(String url, Map<String, String> headers, Map<String, String> params, int connTimeout,
int readTimeout) {
HttpHeaders httpHeaders = new HttpHeaders();
headers.forEach((key, value) -> httpHeaders.add(key, value));
HttpEntity<String> httpEntity = new HttpEntity(httpHeaders);
log.info("send header: {}", headers);
log.info("send body: {}", params);
ResponseEntity<String> rsp = tripApiClient.exchange(url, HttpMethod.GET, httpEntity, String.class, params);
return new HttpResult(rsp.getHeaders().getFirst(HttpHeaders.AUTHORIZATION), rsp.getBody());
}
}
接口调用
// 对接的环境域名
private static final String SERVER_ADDRESS = "https://openapi-uat.hwht.com";
@Autowired
private ClientAuth clientAuth;
@Autowired
private RestTemplateClient restTemplateClient;
public void testSynctr() {
// 获取审批单同步的请求
OpenApiTrSyncReq syncRequest = getSyncRequest();
// 客户端调用同步接口
ApiClient apiClient = SdkFactory.createClient(clientAuth, SERVER_ADDRESS, restTemplateClient);
OpenApiTrSyncResp resp = apiClient.createApi(TrApi.class).syntr(syncRequest);
Assertions.assertTrue(resp.isSuccess());
}
public static OpenApiTrSyncReq getSyncRequest() {
OpenApiTrSyncReq req = new OpenApiTrSyncReq();
req.setCorpCode("OpenApiCompanyNum");
// 参数设置细节省略
// ...
return req;
}