api网关作为后端服务的代理,提供网络请求的反向代理,需要保证http请求的安全性。根据实际服务对安全性的不同要求,一般可以采用http协议结合认证方式来保证报文防篡改,或采用https协议保证数据传输的安全。
本文主要介绍http的几种认证方式、https的原理以及https私有证书的管理扩展。
http协议下,传输的报文是明文传输,对于敏感数据,需要服务提供方和消费方约定加密方式,对敏感数据进行加密后传输。采用一定的认证方式,保证客户端和服务端之间的报文一定程度上不被篡改。这些认证方式并不能完全保证信息安全,仍存在风险,对于安全要求高的服务需采用https协议。
basic认证方式:
客户端在http请求的时候,将accessKey信息和accessSecret信息以’:'拼接,并进行Base64加密得到Authorization报文头,添加在header中进行http请求。
服务端在收到请求后,对Authorization报文头进行Base64解密,提取出accessKey和accessSecret信息,并进行数据库校验,判断请求方是否合法。
token认证是在http请求前,先根据用户名和密码向服务器请求一个token,token一般有一定的时效性。在后续请求时,把这个token以query参数的形式传给服务端,服务端根据这个token值来校验请求的合法性。
digest认证相比于basic认证和token认证比较复杂。摘要的格式、采用的算法可以由服务端约定。
客户端:
服务端:
HTTPS在HTTP的基础上加入了SSL协议,对信息、数据加密,用来保证数据传输的安全。
HTTPS和HTTP的区别主要为以下四点:
比如https://www.taobao.com 的证书是由GlobalSign颁发的,是中国权威的可信CA机构,浏览器认可这些第三方机构,所以可以直接访问https请求。
私有证书是服务端应用自己生成的证书,浏览器或JDK无法鉴别认证,需要将证书导入到浏览器或JDK中。
API网关需要维护后端服务的私有证书,可行的方式有两种:
public interface X509TrustManager extends TrustManager {
void checkClientTrusted(X509Certificate[] var1, String var2) throws CertificateException;
void checkServerTrusted(X509Certificate[] var1, String var2) throws CertificateException;
X509Certificate[] getAcceptedIssuers();
}
以下代码是刚毕业时写的,提供关键部分的思路。
这部分代码做了2个事情,根据certString解析出对应的cert,导入到TrustManagerFactory中,保证应用重启后可直接访问。
另一方面自己维护了一个trustManager的list,重写x509TrustManager接口的三个方法,保证在不重启应用时也可以通过cert验证。
然后在https请求的时候,指定使用自己自定义的trustManager。
//keyStore初始化
KeyStore ks;
try {
ks = KeyStore.getInstance(KeyStore.getDefaultType());
// Note: KeyStore requires it be loaded even if you don't load anything into it:
ks.load(null);
} catch (Exception e) {
log.error("keystore load cert error:" + e.getMessage());
return;
}
// Base64解码
BASE64Decoder decoder = new BASE64Decoder();
//获取CertificateFactory
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X509");
} catch (CertificateException e) {
log.error(e);
}
//将指定证书certString加载到CertificateFactory
InputStream in = null;
X509Certificate cert = null;
try {
byte[] byteCert = decoder.decodeBuffer(certString);
in = new ByteArrayInputStream(byteCert);
cert = (X509Certificate) cf.generateCertificate(in);
} catch (IOException | CertificateException e) {
log.error(e);
return;
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
//ignore
log.debug(e);
}
}
}
//将添加了证书的cert设置到keyStore
ks.setCertificateEntry(UUID.randomUUID().toString(), cert);
TrustManagerFactory tmf = null;
try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
//将keyStore初始化到TrustManagerFactory
tmf.init(ks);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
log.error(e);
return;
}
//将新增证书的trustManager添加到extraX509TrustManagers中
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
extraX509TrustManagers.add((X509TrustManager) tm);
break;
}
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
try {
//JDK自带的check
defaultX509TrustManager.checkServerTrusted(x509Certificates, s);
} catch (CertificateException e) {
//extraX509TrustManagers中包含动态添加的证书
while (extraX509TrustManagers.iterator().hasNext()) {
X509TrustManager tm = extraX509TrustManagers.iterator().next();
try {
tm.checkServerTrusted(x509Certificates, s);
return;
} catch( CertificateException exception ) {
// ignore
log.error(exception);
}
}
//动态添加的证书也未校验通过,则抛出异常,无法识别https的安全性
throw new CertificateException(e.getMessage());
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
final ArrayList list = new ArrayList();
list.addAll(Arrays.asList(defaultX509TrustManager.getAcceptedIssuers()));
Iterator iterator = extraX509TrustManagers.iterator();
while (iterator.hasNext()) {
X509TrustManager tm = iterator.next();
list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
}
return list.toArray(new X509Certificate[list.size()]);
}