上周自己玩的微信小程序打包发布体验版的时候发现微信平台需要配置的合法域名固定了以https开头,所以自己立即在阿里云服务器的购买一个0元的ssl证书。阿里云文档上配置https的方法很简洁,但由于自己忘记了在安全组配置443端口导致一直访问不了。在了解https很http的过程中发现大学时期的网络知识基本都忘光了,所以这里整理了一份笔记。
三次握手:
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
摘录百度百科的三次握手,好难记啊,自己理解一下:
第一次握手:客户端问服务器:服务器端,你在吗?
第二次握手:服务器端回答客户端:客户端,我在的。
第三次握手:客户端回答服务器:好的。(确立连接)
当服务器端接到客户端回应的“好的”的时候连接就建立了,这时候两者之间就可以互相传输数据了。
仅仅两次握手问题:
第一次握手:客户端问服务器:服务器端,你在吗?
第二次握手:服务器端回答客户端:客户端,我在的。(确立连接)
这种情况下客户端发送给服务器端可能会出现一种情况:客户端给服务器端传输的报文在传输的过程中,报文由于某种原因“偷懒了一刻钟”,这段时间内客户端与服务器端有可能已经断卡了连接,“一刻钟”之后“偷懒”的报文继续传输到到服务器端,这时候由于之前的连接已经断开,这个报文会被认为是新的连接请求:“服务器端,你在吗?‘,然后服务器依然回答"客户端,我在的。(确立连接)"。可是由于客户端已经断开了,所以不会回复服务器端,那么服务器端就一直在等待着与客户端进行数据传输,这样容易造成资源浪费。
四次挥手:
第一次挥手:TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
第二次挥手:服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
第三次挥手:服务器关闭客户端的连接,发送一个FIN给客户端。
第四次挥手:客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
这玩意一样不好记,自己理解:
第一次挥手:客户端告诉服务器端:我要跟你分手,我这边已经没有数据要传给你了,分手之后我们互相就不要再传输数据了。
第二次挥手:服务器端回应客户端:太突然了,我有些数据可能还没传输完呢,让我再想想。(这个时候服务器依然还能向客户端传输数据)
第三次挥手:服务器端再次回复客户端:我这边的数据也传输完成了,我同意分手了。
第四次挥手:客户端接收到之后回复服务器端:好的。(服务器端接收到之后就断开连接了)
(客户端回复的时候等待2MSL时间,2MSL内没收到服务器端任何回复自己也断开连接。)
这里会产生两个问题(标准答案看这里):
1.为什么创建连接的时候是操作三次,断开连接的时候要操作四次:
答:分手需要时间,比如先把朋友圈、空间秀恩爱说说删除,然后再跟渣男说,是我pass的你。所以听到说分手的时候先回应一下,准备好了(比如数据传输完成)再回应一下。
2.最后一次挥手,为什么客户端需要等待2MSL时间?
答:2MSL(最大报文段生存时间),最后一次挥手的过程中,传送的报文有可能“偷懒”或者“迷路丢失”了,第三次挥手之后服务器端如果没接收到客户端任何回复的话会继续重新回复。等待的2MSL时间,就有足够的时间重发可能丢失的ACK报文。
UDP
全称:User Datagram Protocol,直接翻译:用户数据报协议。是一种传输层的协议。无连接。采用UDP进行通信时不用建立连接,可以直接向一个IP地址发送数据,但是不能保证对方是否能收到。个人理解就相当于广播一下,你只管发送广播,客户端接不接收不用管它,所以它传输相对快,但丢包率很高。
特点:
使用场景:
音频、视频、即时通讯、直播
TCP
全称:Transmission Control Protocol,直接翻译:传输控制协议。是一种面向连接的、可靠的、基于字节流的传输层通信协议。利用TCP进行通信时,首先要通过三步握手。
特点:
使用场景:网页浏览、文件下载(不是BT、电脑下载)、邮件的发送。
HTTP:超文本传输协议。是一个简单的请求-响应协议,它通常运行在TCP之上,请求和响应消息的头以ASCII码形式给出。世界上几乎所有的HTTP通信都是由TCP/IP承载的。
HTTP1.0、HTTP2.0相关
http:简单的对象访问协议,对应于应用层。Http协议是基于TCP链接的。
Socket:是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
Http连接:短连接,客户端向服务器发送一次请求,服务器端相应后连接即会断掉。
socket连接:长链接,理论上客户端和服务端一旦建立连接,则不会主动断掉
HTTP协议是一种使用明文数据传输的网络协议。
HTTPS协议可以理解为HTTP协议的升级,就是在HTTP的基础上增加了数据加密。HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
上周根据阿里云服务器的文档配置:
申请证书->上传证书到服务器->tomcat配置证书
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="443" />
<Connector port="443"
protocol="HTTP/1.1"
SSLEnabled="true"
scheme="https"
secure="true"
keystoreFile="/usr/tomcat/apache-tomcat-7.0.85/conf/itnewers.com.pfx"
keystoreType="PKCS12"
keystorePass="x0c4f6XT"
clientAuth="false"
SSLProtocol="TLS"/>
按照这个流程配置,使用https访问的时候怎么也访问不了,最蛋疼的是网上的帖子基本也都是这么配置的。周末打了两天“农药”,周一打开电脑的时候突然想起来访问80端口的时候需要在阿里云服务器的安全组配置进入的端口号,那么我这里使用了redirectPort="443"是不是也需要在安全组配置一个443的端口号,如下图:
配置了443端口号之后意外地发现:
//访问不成功
https://域名:8080
//访问成功
https://域名:443
//访问成功
https://域名
为什么呢?
查看很多帖子都说redirectPort是重定向到某个接口,比如说上面的
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="443" />
是访问8080端口会重定向到443端口,并且我也是这么认为的,但实际上https用法好像又不是这样,从结果上看是:
http访问的时候使用8080端口,https访问的时候使用443端口。(这里其实还是不太明白)
令人奇怪的是使用https访问不需要端口号也能正常访问?
答:http协议的默认端口是80,https的默认端口是443,这里将端口改为了443,访问https时可不用加端口号。
将下载好的证书复制到src/main/resoureces下,然后在application.properties内配置证书信息:
# 配置Https访问
server.ssl.key-store=classpath:itnewers.com.pfx
server.ssl.key-store-password=x0c4f6XT
server.ssl.key-store-type=PKCS12
如下图:
运行结果如下:
但是这时候是不支持使用http访问的,火狐使用http访问会报
Bad Request
This combination of host and port requires TLS.
想要同时支持https、http还需要进行如下配置:
1.application.properties内添加:
server.http.port=8082
由于8081端口已经被https使用,http就不能再使用那个端口,所以配置8082端口给给http使用。
2.添加TomcatConfig类:
package com.streamlet;
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TomcatConfig {
@Value("${server.http.port}")
private int httpPort;
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
TomcatEmbeddedServletContainerFactory containerFactory =
(TomcatEmbeddedServletContainerFactory) container;
Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
connector.setPort(httpPort);
containerFactory.addAdditionalTomcatConnectors(connector);
}
}};
}
}
1.无证书方式:
/**
* @author Linxz
* 创建日期:2019年02月20日 20:11
* version:1.0.0
* 描述:
*/
public class LinxzHttpCreator {
/**
* 构建Http
* */
private static final class OkHttpHolder {
private static final int TIME_OUT = 60;
private static final OkHttpClient.Builder BUILDER = new OkHttpClient.Builder();
private static final ArrayList<Interceptor> INTERCEPTORS = Linxz.getConfiguration(ConfigKeys.INTERCEPTOR);
private static OkHttpClient.Builder addInterceptor() {
if (INTERCEPTORS != null && !INTERCEPTORS.isEmpty()) {
for (Interceptor interceptor : INTERCEPTORS) {
BUILDER.addInterceptor(interceptor);
}
}
return BUILDER;
}
/**
* http请求
* */
private static final OkHttpClient OK_HTTP_CLIENT = addInterceptor()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();
/**
* https请求
* */
private static final OkHttpClient OK_HTTPS_CLIENT = httpsBuilder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();
private static OkHttpClient.Builder httpsBuilder(){
try {
TrustManager[] trustAllCerts = buildTrustManagers();
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder =addInterceptor();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return builder;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
return new OkHttpClient.Builder();
}
}
private static TrustManager[] buildTrustManagers() {
return new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
}
}
/**
* 构建全局Retrofit客户端
*/
private static final class RetrofitHolder {
private static final Retrofit RETROFIT_CLIENT = new Retrofit.Builder()
.baseUrl(HttpConfig.BASE_URL)
.client(OkHttpHolder.OK_HTTP_CLIENT)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
private static final class HttpStoreHolder {
private static final HttpStore REST_SERVICE = RetrofitHolder.RETROFIT_CLIENT.create(HttpStore.class);
}
public static HttpStore getHttpStore(){
return HttpStoreHolder.REST_SERVICE;
}
public static OkHttpClient getHttpsClent(){
return OkHttpHolder.OK_HTTPS_CLIENT;
}
}
2.需要p12证书方式:
public class SSLHelper {
private static final String KEY_STORE_TYPE_BKS = "bks";
private static final String KEY_STORE_TYPE_P12 = "PKCS12";
/**
* 记得添加相应的证书到assets目录下面
*/
private static final String KEY_STORE_CLIENT_PATH = "client.p12";//P12文件
private static final String KEY_STORE_TRUST_PATH = "client.truststore";//truststore文件
private static final String KEY_STORE_PASSWORD = "自己的P12文件密码";//P12文件密码
private static final String KEY_STORE_TRUST_PASSWORD = "自己的truststore文件密码";//truststore文件密码
private static final String KEY_CRT_CLIENT_PATH = "xxx.crt";//CRT文件
private static boolean isServerTrusted = false;
private static SSLContext sslContext = null;
public static SSLContext getSslContext(Context context) {
if (sslContext == null) {
try {
// 服务器端需要验证的客户端证书
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
// 客户端信任的服务器端证书
KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);
InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
try {
keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ksIn.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
tsIn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
}
return sslContext;
}
public static SSLContext getSslContextByDefaultTrustManager(Context context) {
if (sslContext == null) {
try {
// 服务器端需要验证的客户端证书
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
try {
keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ksIn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
sslContext = SSLContext.getInstance("TLS");
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
}
return sslContext;
}
public static SSLContext getSslContextByCustomTrustManager(Context context) {
if (sslContext == null) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(context.getResources().getAssets().open(KEY_CRT_CLIENT_PATH));
final Certificate ca;
try {
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new X509TrustManager[]{new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
Log.d("SSLHelper", "checkClientTrusted --> authType = " + authType);
//校验客户端证书
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
Log.d("SSLHelper", "checkServerTrusted --> authType = " + authType);
//校验服务器证书
for (X509Certificate cert : chain) {
cert.checkValidity();
try {
cert.verify(ca.getPublicKey());
isServerTrusted = true;
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
e.printStackTrace();
isServerTrusted = false;
}
}
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}}, null);
} catch (Exception e) {
e.printStackTrace();
}
}
return sslContext;
}
}
public class UnSafeHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
fun okHttpsClientBuilder(): OkHttpClient.Builder {
val builder = okHttpClientBuilder()
if (SSLHelper.getSslContext(JBT.getApplicationContext()) != null) {
SSLHelper.getSslContext(JBT.getApplicationContext()).socketFactory.let {
builder.sslSocketFactory(it)
.hostnameVerifier(UnSafeHostnameVerifier())
}
}
return builder
}
3.Glide加载https图片:
/**
* @author Linxz
* 创建日期:2020年03月23日 16:07
* version:v4.5.4
* 描述:Glide加载Https失败问题
*/
@GlideModule
public final class HttpsGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
super.applyOptions(context, builder);
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
OkHttpClient okHttpClient= LinxzHttpCreator.getHttpsClent();
registry.replace(GlideUrl.class, InputStream.class,new OkHttpUrlLoader.Factory(okHttpClient));
}
}