赞
踩
HTTPS其实就是在TCP到应用层之间加了一层加解密的环节。那一层就是SSL。
对于SSL的操作java已经有了实现SSLEngine,我们也就不用再造轮子了,使用方法在https://nowjava.com/docs/java-api-11/java.base/javax/net/ssl/SSLEngine.html
可以通过SSLEngineResult.HandshakeStatus来判断握手的状态,它会指导我们下一步应该要做什么。
FINISHED:握手完成。
NEED_TASK:需要等待一些task的完成,否则handshake无法继续,出现这个情况时,后续engine的wrap和unwrap方法都会阻塞直到task完成。
NEED_UNWRAP:需要从peer端读取新的数据,否则handshake无法继续。
NEED_UNWRAP_AGAIN:与NEED_UNWRAP类似,但表示从peer读取的数据已经存在于本地了,这个状态下,不需要再重新走一遍网络,只要解析已经接收到的数据就可以了。NOTE:在java8_u151中,并没有这个枚举类型。
NEED_WRAP:需要向peer端发送数据,否则handshake无法继续。
NOT_HANDSHAKING:当前没有处于handshake阶段。
对于需要认证客户端的这种情况,要求客户端也得有CA证书,当然我们可以花钱去受信任的机构去申请证书,也可以用java自带的工具生成证书。
执行下面命令
keytool -genkey -keyalg RSA -keysize 2048 -keystore /home/XXX.jks
注意, 此时需要输入此keystore的密码.
密码长度为至少6位
- Enter keystore password:
- Keystore password is too short - must be at least 6 characters
- Enter keystore password:
- Re-enter new password:
输入密码后,下一步需输入一些与此key相关的信息.
需要注意的部分是first and last name, 因为它表示应用这个证书的域名
- What is your first and last name?
- [Unknown]: XXX.XX.com
- What is the name of your organizational unit?
- [Unknown]: XX
- What is the name of your organization?
- [Unknown]: XX
- What is the name of your City or Locality?
- [Unknown]: Dalian
- What is the name of your State or Province?
- [Unknown]: CN
- What is the two-letter country code for this unit?
- [Unknown]: CN
- Is CN=XXX.com, OU=XX, O=XX, L=Dalian, ST=CN, C=CN correct?
- [no]: y
最后一步, 密码置空直接回车即可
- Enter key password for <mykey>
- (RETURN if same as keystore password):
这样, 在指定的目录下,jks证书已经被生成出来了.
然后我们可以用SSLContext来配置我们的证书(第一个参数)和受信任的证书列表(第二个参数)
sslContext.init(keyManagers(), trustManagers(), null);
如果服务器的ca证书不是受信任的机构颁发的,客户端的受信任的证书列表配置服务器的证书。同理服务器也一样。
- SSLEngineResult res = sslEngine.wrap(appWBuffer, packetWBuffer);
- SSLEngineResult res = sslEngine.unwrap(packetBuffer, appBuffer)
通过SSLEngineResult来判断加解密的状态
OK:wrap(发送数据)或者unwrap(接受数据)成功,没有错误。
CLOSED:对于handshake NEED_WRAP操作来说,就是当前端主动关闭了TLS通信;对于NEED_UNWRAP来说,就是peer主动调用了TLS通信,当前端获取到了peer发送过来的close_notify message。
BUFFER_UNDERFLOW(buffer空闲):理论上来说,这个情况不会出现在handshake NEED_WRAP阶段;对于NEED_UNWRAP阶段来说,(1)packetBuffer空间不足,需要扩容(可以初始化大小为sslSession.getPacketBufferSize());(2)packetBuffer读取的数据出现了半包问题,需要继续从socket中read(可以执行packetBuffer.compact(),然后继续读)。
BUFFER_OVERFLOW(buffer溢出):对于NEED_WRAP来说,myNetBuf空间不足,需要扩充或者清空;对于NEED_UNWRAP,peerAppBuf不足,需要扩容或者清空。
因为我们是模仿浏览器发送https,所以不用加载我们自己的证书,也不用加受信任的证书。如果是访问私有网站,则需要把其证书放到受信任证书里面,如果对方要求双向认证,那么还需要加载自己的证书,并且把自己的证书发给对方配置为受信任(如果证书是受信任的ca机构颁发的就不用配置受信任证书了)
那么我们改一下上一篇的代码,以Content-Length为例(Transfer-Encoding等于chunked也一样,只是进行的数据的加解密)。这里的代码是非阻塞的,io多路复用的完整代码在github上 https://github.com/cxsummer/net-nio
- public static void main(String[] args) throws Exception {
- int port = 443;
- String host = "www.ximalaya.com";
- String path = "/";
- SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
- socketChannel.configureBlocking(false);
-
- SSLContext sslCtx = SSLContext.getInstance("TLS");
- sslCtx.init(null, null, null);
- SSLEngine sslEngine = sslCtx.createSSLEngine(host, port);
- sslEngine.setUseClientMode(true);
- sslEngine.beginHandshake();
- SSLSession sslSession = sslEngine.getSession();
- SSLEngineResult.HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
- ByteBuffer appBuffer = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
- ByteBuffer packetBuffer = ByteBuffer.allocate(sslSession.getPacketBufferSize());
-
- ByteBuffer appWBuffer = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
- ByteBuffer packetWBuffer = ByteBuffer.allocate(sslSession.getPacketBufferSize());
-
- while (handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
- switch (handshakeStatus) {
- case NEED_UNWRAP:
- socketChannel.read(packetBuffer);
- packetBuffer.flip();
- SSLEngineResult res = sslEngine.unwrap(packetBuffer, appBuffer);
- packetBuffer.compact();
- handshakeStatus = res.getHandshakeStatus();
- break;
- case NEED_WRAP:
- packetWBuffer.clear();
- res = sslEngine.wrap(appWBuffer, packetWBuffer);
- handshakeStatus = res.getHandshakeStatus();
- if (res.getStatus() == SSLEngineResult.Status.OK) {
- packetWBuffer.flip();
- while (packetWBuffer.hasRemaining()) {
- socketChannel.write(packetWBuffer);
- }
- }
- break;
- case NEED_TASK:
- Runnable task;
- while ((task = sslEngine.getDelegatedTask()) != null) {
- new Thread(task).start();
- }
- handshakeStatus = sslEngine.getHandshakeStatus();
- break;
- }
- }
-
- StringBuilder stringBuilder = new StringBuilder("GET " + path + " HTTP/1.1 \r\n");
- stringBuilder.append("Host: " + host + "\r\n");
- stringBuilder.append("Accept-Encoding: gzip, deflate\r\n");
- stringBuilder.append("\r\n");
- ByteBuffer byteBuffer = ByteBuffer.wrap(stringBuilder.toString().getBytes());
-
- packetBuffer.clear();
- SSLEngineResult res = sslEngine.wrap(byteBuffer, packetBuffer);
- if (res.getStatus() != SSLEngineResult.Status.OK) {
- throw new RuntimeException("SSL加密失败");
- }
- packetBuffer.flip();
-
- while (packetBuffer.hasRemaining()) {
- socketChannel.write(packetBuffer);
- }
-
- int num;
- byte[] body = null;
- int bodyIndex = 0;
- int headerIndex = 0;
- Integer contentLength = null;
- byte[] originHeader = new byte[1024];
- LinkedHashMap<String, List<String>> head = null;
- appBuffer.clear();
- packetBuffer.clear();
- while ((num = socketChannel.read(packetBuffer)) > -2) {
- packetBuffer.flip();
- do {
- res = sslEngine.unwrap(packetBuffer, appBuffer);
- } while (res.getStatus() == SSLEngineResult.Status.OK);
- packetBuffer.compact();
- for (int i = 0; i < appBuffer.position(); i++) {
- byte b = appBuffer.get(i);
- if (head == null) {
- originHeader[headerIndex++] = b;
- if (originHeader.length == headerIndex) {
- originHeader = byteExpansion(originHeader, 1024);
- }
- if (originHeader[headerIndex - 1] == '\n' && originHeader[headerIndex - 2] == '\r' && originHeader[headerIndex - 3] == '\n' && originHeader[headerIndex - 4] == '\r') {
- String headerStr = new String(originHeader);
- String[] headerList = headerStr.split("\r\n");
- head = Arrays.stream(headerList).skip(1).filter(h -> h.contains(":")).collect(Collectors.groupingBy(h -> h.split(":")[0].trim(), LinkedHashMap::new, Collectors.mapping(h -> h.split(":")[1].trim(), Collectors.toList())));
- contentLength = Optional.ofNullable(head.get("Content-Length")).map(c -> Integer.parseInt(c.get(0))).orElse(-1);
- }
- } else {
- Integer finalContentLength = contentLength;
- body = Optional.ofNullable(body).orElseGet(() -> new byte[finalContentLength]);
- body[bodyIndex++] = b;
- if (bodyIndex == contentLength) {
- num = -2;
- break;
- }
- }
- }
- if (num < 0) {
- socketChannel.close();
- System.out.println(new String(originHeader));
- System.out.println(new String(uncompress(body)));
- return;
- }
- appBuffer.clear();
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。