使用WebService发布soap接口,并实现客户端的https验证
什么是https
HTTPS其实是有两部分组成:HTTP + SSL / TLS,
也就是在HTTP上又加了一层处理加密信息的模块,并且会进行身份的验证。
如何进行身份验证?
首先我们要明白什么是对称加密,什么是非堆成加密
对称加密
对称加密就是只有一个密钥,客户端双方按照约定的密钥对自己的明文进行加密。
但是这种方式有个很不好的情况。A和B通讯之前并不知道使用哪种密钥加密。所以在第一次通讯的时候会事先沟通好。
A->B:嗨!在吗?我们开始聊天吧。
B->A:好的,我们的密钥是:xxxxx。
如果此时黑客截取到了你的密钥,那加密也无济于事。
非对称加密
非对称加密就是在对称加密上再套一层公钥。所以有公钥和私钥两种形式。
A->B:嗨!在吗?我们开始聊天吧。
B->A:好的,我们的公钥是:xx。
A->B(以公钥:xx加密): 你好我是A,我们之后使用对称加密的zz来通讯。
B:以xx的密钥解密,得到密钥zz,之后双方通讯都通过对称加密的zz来通讯。
公钥是公开的,用公钥加密的信息只能使用密钥解密。公钥是解不开的,即便公钥被非法路由或黑客拦截,也不能通过公钥解密。这就是数学的魅力,有兴趣的话可以了解一下:RSA算法
这样就安全了?答案是否定的,如果在B->A发送公钥 xx 的时候就已经被非法路由器或者黑客拦截,然后向A发送偷天换日后的公钥 yy。此时非法路由就有两个公钥 xx 和 yy 。并且拥有有 yy 的私钥,因为 yy 是其自己生成的。
因此,当A收到非法路由发送过来的偷天换日后的公钥 yy ,A信以为真的认为这个就是B发送过来的公钥。当A向B发送发送一串由 YY 公钥加密的信息,非法路将其拦截并且用 yy 的密钥解密后再通过公钥 xx 加密后发送给B。这样你的消息通讯依旧是不安全的。
证书体系
上面这个环节按理来说已经很安全了,为什么还是会被黑客或者非法路由攻击?思考上面的环节,问题出在发送公钥的时候,被黑客拦截,然后偷天换日的换了一个公钥。那是不是只要保证公钥的不可更改,就能维护了呢?是的,证书的出现就是解决这个问题(也是为什么你要去找证书签发机构花钱购买证书的原因)!!
简单来说,一个HTTPS网站响应给我们的并不是一个公钥,而是证书。证书上包含了公钥,还包含了域名、签发机构、有效期、签名等等。
那证书的安全性怎么保证?为什么中间人不能做一个假证书?
因为这套证书体系已经根植于每一个操作系统里了。每一个操作系统里,都内置了数十张根证书,每个根证书都对应一个非常权威的证书签发机构。这些根证书上记录了各个机构的公钥。
那非法路由或者黑客能不能申请一个真的证书然后去做劫持呢?
通常来说,证书签发机构的审核非常严格,如果无法证明B这个域名属于他,签发机构是不会给他签发一个知乎域名的证书的。而如果中间人用了其他域名的证书,浏览器会发现你请求的域名和返回的证书不一致,从而拒绝继续请求。除非你一定要点下面的「仍然继续」:
最后无奈地说,在破解了这么多数学上的难题后,HTTPS的安全性仍然保证在『证书签发机构一定都是很有良心的』这种脆弱的基础上。(即便一些网站是通过证书签发机构的,但是嘛依旧会被植入小广告,咳咳....)。
实现客户端验证的WebService
我们了解了https和证书的作用。其实我们在系统之间内部通讯的时候就可以使用对称加密的方式进行访问即可。
因为是内部嘛,我们提前约定好使用何种证书就可以了。除非是很正式的项目,否则使用自己签发的证书即可,因为官方生成证书是要花钱滴。
有两种方式
第一种:服务端生成一个证书,每个客户端都生成自己的证书,然后让服务端与各个客户端相互信任。
第二种:生成一个证书,将这个证书派发给各个客户端,每个客户端携带该证书就会被服务端认证(这种安全性稍微低一些,但是在大部分对安全性要求不是特别高的场景推荐使用。下面主要说明这一种方式,如果想实现第一种参考:https://blog.csdn.net/u011350541/article/details/71941536)
生成证书
首先保证服务器有jdk,打开CMD工具,进入到jdk的bin目录下输入一下信息。如果配置了环境变量则直接在命令行中输入即可。
keytool -genkey -alias tomcat -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore D:/keys/server.jks -storepass 123456
上述参数的具体说明如下:
keytool
-genkey
-alias service(别名)
-keypass 123456(别名密码)
-keyalg RSA(算法)
-keysize 1024(密钥长度)
-validity 365(有效期,天单位)
-keystore D:/keys/server.jks(指定生成证书的位置和证书名称)
-storepass 123456(获取jks信息的密码)
发布的soap接口
/**
* @author Eric
* @Title: SoapService
* @date 2019/7/17 10:42
* @Description: 发布的soap接口,注意注释的使用
*/
@WebService
public interface SoapService {
void sayHello(@WebParam(name = "clientName") String clientName);
}
接口的具体实现类
/**
* @author Eric
* @Title: SoapServiceImpl
* @date 2019/7/17 10:42
* @Description: soap接口的具体实现,注意注释的使用
*/
@javax.jws.WebService
public class SoapServiceImpl implements SoapService {
@Override
public void sayHello(String clientName) {
System.out.println(clientName + "调用接口打了招呼");
}
}
发布soap接口
/**
* @author Eric
* @Title: testTest
* @date 2019/7/17 10:45
* @Description: 发布soap接口
*/
public class Release {
public static void main(String[] args) throws Exception {
initSoap();
}
//初始化soap接口
public static void initSoap() {
try {
String host = "0.0.0.0";
int port = 8888;
String address = "https://" + host + ":" + port + "/logProcessor"; //发布于https
JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
sf.setServiceClass(SoapService.class); //发布实现soap的接口类(你自己实现的接口,里面的方法就是发布的soap接口)
sf.setAddress(address);
SoapServiceImpl soapServiceImpl = new SoapServiceImpl(); //SoapService的实现类
sf.getServiceFactory().setInvoker(new BeanInvoker(soapServiceImpl));
sf = configureSSLOnTheServer(sf, port);
Server server = sf.create();
String endpoint = server.getEndpoint().getEndpointInfo().getAddress();
System.out.println("soap发布的地址为:" + endpoint);
} catch (Exception e) {
System.out.println("Soap初始化失败:"+e);
}
}
//https并且开启客户端认证。如果发布的接口是http,则无需实现该方法。
private static JaxWsServerFactoryBean configureSSLOnTheServer(JaxWsServerFactoryBean sf, int port) throws Exception {
TLSServerParameters tlsParams = new TLSServerParameters();
ClientAuthentication clientAuthentication = new ClientAuthentication();
clientAuthentication.setRequired(true); //开启客户端认证
tlsParams.setClientAuthentication(clientAuthentication);
KeyStore keyStore = KeyStore.getInstance("JKS");
String password = "123456"; //生成证书时候定义的的密码
File truststore = new File(Paths.get("D:\\keys", "server.jks").toString());
keyStore.load(new FileInputStream(truststore), password.toCharArray());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, password.toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
tlsParams.setKeyManagers(km);
truststore = new File(Paths.get("D:\\keys", "server.jks").toString());
keyStore.load(new FileInputStream(truststore), password.toCharArray());
TrustManagerFactory trustFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(keyStore);
TrustManager[] tm = trustFactory.getTrustManagers();
tlsParams.setTrustManagers(tm);
JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory();
factory.setTLSServerParametersForPort(port, tlsParams);
}
}
使用soapUI模拟客户端调用soap接口
通过上述代码,我们将接口发布在了你自己定义的https://0.0.0.0:8888/logProcessor。如果发布成功,我们想远程调用soap接口,此时打开soapUI
- 选择左上角File然后选择Preferences。
- 选择SSL Settings,在KeyStore上导入我们生成的证书。否则无法远程调用(因为我们在代码中开启了客户端认证,如果没有开启可以跳过这一步)
- 然后返回主界面,创建soap,在弹出的界面中 ‘Project Name’可以自己定义。‘Initial WSDL’中输入你发布的地址加上‘?wsdl’如下:https://0.0.0.0:8888/logProcessor?wsdl
- 如果创建成功,你就能在左边看到自己项目中发布的接口。选择我们发布的接口sayHello,双击Request。
- 在弹出的界面中,将自己的参数输入,然后点击绿色三角形开始调用,因为我们这个方法没有参数返回,所有调用后没有什么信息会提示。
- 打开我们的ide,此时就能看到下面打印了一行话,说明方法调用成功
参考
https://zhuanlan.zhihu.com/p/37738632
https://blog.csdn.net/u011350541/article/details/71941536