频道栏目
首页 > 网络 > 网络协议 > 正文

https与SSL协议详解及Java实现免证书访问https服务代码

2017-11-29 02:20:59      个评论    来源:wonking666的博客  
收藏   我要投稿

https与SSL协议详解及Java实现免证书访问https服务代码。

本着实用原则,本文不讲太多理论性的东西,更多关注的是实际应用中的问题。在阅读此文章之前,默认你已经熟悉了对称加解密、非对称加解密算法,公钥&私钥等概念。

https解决了什么实际问题


互联网是开放环境,通信双方身份都是未知的,这样很多问题也随之而来。客户端如何确定服务器是正规的,而不是某个假冒的服务器?服务器如何确定客户端是我信任的客户端?又如何保证传输给客户端的数据没有被第三方篡改?这些都是传统的http协议很难解决的问题。https协议应运而生,它解决了以下三个问题:
1.服务器信任问题,即需要证明你这个服务器是被正规机构信任的服务器。
2.保证服务器传输到客户端的数据的安全性,防止数据被第三方截获篡改。
3.验证客户端的合法性,这个是双向的验证。双向验证保证的是,服务器是我信任的服务器,客户端也是我信任的客户端。
注:不要被双向验证完全占据了头脑,大部分应用场景只需单向验证,即客户端验证服务器的合法性,服务器不需要验证客户端的合法性。事实上,在浏览器应用场景中,服务器完全没必要验证你这个客户端是否是我允许访问的客户端,因为互联网是开放环境,我允许所有人访问。所以双向验证解决的问题3,一般只应用于个人网上银行。

https协议建立在SSL协议之上,可以认为,https协议等于ssl+http,所以了解ssl协议的工作原理即可。

SSL协议


SSL(Secure Socket Layer)安全套接字协议是NetScape公司研发的(一个低调而又伟大的公司,Javascript,css皆出自其手),SSL1.0和SSL2.0都出师未捷身先死,1996年,SSL 3.0版问世,才得到大规模应用。1999年,互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS 1.0版。目前,应用最广泛的是TLS 1.0,再就是SSL 3.0。

SSL协议运行流程


SSL协议运行过程,概括来说,就是两步,第一步是握手阶段,第二步是通信阶段。握手阶段主要完成两个工作:一,确认双方身份;二,协商通信阶段的加密算法(对称算法)。通信阶段,就是采用之前协商的加密算法对通信数据进行加密,这里不多介绍。握手阶段主流程如下:
1. 客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。请求包含如下信息:
- 支持的协议版本,比如TLS 1.0版
- 客户端生成的随机数,稍后用于生成”对话密钥”
- 加密方法,比如RSA公钥加密
这里需要注意的是,客户端发送的信息之中不包括服务器的域名。也就是说,理论上服务器只能包含一个网站,否则会分不清应该向客户端提供哪一个网站的数字证书。这就是为什么通常一台服务器只能有一张数字证书的原因。
对于虚拟主机的用户来说,这当然很不方便。2006年,TLS协议加入了一个Server Name Indication扩展,允许客户端向服务器提供它所请求的域名。

2.服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含如下内容:
- 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信
- 一个服务器生成的随机数,稍后用于生成”对话密钥”
- 确认使用的加密方法,比如RSA公钥加密
- 服务器证书(特别注意)
除了上面这些信息,如果服务器需要确认客户端的身份(当然不是强制的),就会再包含一项请求,要求客户端提供”客户端证书”,这就是前面说的双向认证。

3.客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告(这就是为什么当你访问某些网站会出现下面的提示,比如,12306),由其选择是否还要继续通信。


12306


如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送如下信息:
- 一个随机数。该随机数用服务器公钥加密,防止被窃听
- 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送
- 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验
上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称”pre-master key”。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把”会话密钥”。
至于为什么一定要用三个随机数,来生成”会话密钥”,bomb250

4.服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的”会话密钥”。然后,向客户端发送最后的握手信息:
- 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送
- 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验
至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用”会话密钥”加密内容。

服务端如何提供https服务


服务端提供https服务,一般只需要通过配置web容器即可。要提供正规的https网站,需要花钱去证书颁发机构购买。个人学习可以使用jdk自带的keytool工具生成证书,并部署到tomcat中。详细教程见Java WEB HTTPS使用详解

Java实现免证书访问https服务


前面说过,一般应用场景是不需要服务端验证客户端的,然而当我们访问以https方式提供的服务接口时,可能会接收到“Unrecognized SSL message, plaintext connection”返回信息,这是因为使用了默认的“证书信任管理器”,服务器的证书未经正规机构授权验证不通过,我们需要自己实现X509TrustManager,免去验证。代码如下:

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;


import com.sun.corba.se.impl.orbutil.threadpool.TimeoutException;
import org.apache.commons.httpclient.util.TimeoutController;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import cn.qtone.hjy.service.bean.yd.sync.AccountPasswordInfoSyncItem;
import cn.qtone.hjy.service.bean.yd.sync.OrderInfoSyncItem;
import cn.qtone.hjy.service.bean.yd.sync.YdOrderRelationshipInfoSyncItem;
import cn.qtone.hjy.service.core.DES;
import cn.qtone.hjy.service.dao.YdEduDao;
import cn.qtone.util.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;


public class HttpClientUtil {

    static Logger log = Logger.getLogger(HttpClientUtil.class) ;

    private static  RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();//设置请求和传输超时时间

    private static CloseableHttpClient httpclient = HttpClients.createDefault();

    public static String send(String httpUrl, String message) throws IOException {
        String result = null ;
        HttpPost httpPost = new HttpPost(httpUrl);
        //设置数据读取超时5s   传输超时5s    链接请求超时5s
        RequestConfig requestConfig = RequestConfig.custom()
                    .setSocketTimeout(5000)
                    .setConnectTimeout(5000)
                    .setConnectionRequestTimeout(5000)
                    .build();        
        httpPost.setConfig(requestConfig) ;
        message = URLEncoder.encode(message, "UTF-8") ;
        StringEntity entity = new StringEntity(message);
        httpPost.setEntity(entity);
        CloseableHttpResponse response = httpclient.execute(httpPost);
        BufferedReader in = null ;
        try {
            InputStream content = response.getEntity().getContent() ;
            in = new BufferedReader(new InputStreamReader(content));
            StringBuilder sb = new StringBuilder();
            String line = "" ;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            result = sb.toString() ;
            System.out.println("接收原始报文:" + URLDecoder.decode(result, "UTF-8")) ;
        } finally {
            EntityUtils.consume(response.getEntity());
            response.close();
        }
        return result ;
    }

    public static String post(String httpUrl, String message) throws Exception {
        String result = null ;
        CloseableHttpClient httpclient = HttpClients.createDefault();
        BufferedReader in = null ;
        HttpPost httpPost = new HttpPost(httpUrl);
        httpPost.setConfig(requestConfig);
        List  nvps = new ArrayList ();
        nvps.add(new BasicNameValuePair("tokenId", DES.encrypt(message)));
        httpPost.setEntity(new UrlEncodedFormEntity(nvps));
        try {
            System.out.println("发送报文:" + message);
            System.out.println("发送报文:" + DES.encrypt(message)) ;
            CloseableHttpResponse response = httpclient.execute(httpPost);
            InputStream content = response.getEntity().getContent() ;
            in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
            StringBuilder sb = new StringBuilder();
            String line = "" ;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            System.out.println("响应报文:" + sb.toString()) ;
            //result = URLDecoder.decode(sb.toString(), "UTF-8") ;
            //result = DES.decrypt(result) ;
            //System.out.println("完成:" + JSONObject.parseObject(result) + "\n");
            return result ;
        } catch (Exception e) {
            e.printStackTrace() ;
        } finally {
            httpclient.close();
        }
        return null ;
    }

    /**
     * 发起post请求,请求参数以Map集合形式传入,封装到List  发起post请求
     * @param httpUrl
     * @param params
     * @return
     * @throws Exception
     */
    public static String post(String httpUrl, Map params) throws Exception {
        String result = null ;
        CloseableHttpClient httpclient = createSSLClientDefault();
        //httpclient.
        //httpclient.
        BufferedReader in = null ;
        HttpPost httpPost = new HttpPost(httpUrl);
        httpPost.setConfig(requestConfig);
        List  nvps = new ArrayList ();
        StringBuffer paramsBuf = new StringBuffer() ;
        for(Entry e : params.entrySet()) {
            nvps.add(new BasicNameValuePair(e.getKey(), e.getValue()));
            paramsBuf.append("&").append(e.getKey()).append("=").append(e.getValue()) ;
        }
        httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
        try {
//          报文参数27:&id=jn-3-767744&groupPlatProTerminalId=119667&extend=uwJZ8j3CkpGPL4rM5J6KJhjR99O7yAe3BAGLS8ooI8ijNqKHfzTaK6W9wQvjZEVOmWJ3HxFb2O9D
//          wDbe3++UiQ==&xxtCode=370000&terminalType=1&role=3&type=3
            System.out.println("post请求报文地址:" + httpUrl+"?"+paramsBuf.toString()) ;
            CloseableHttpResponse response = httpclient.execute(httpPost);
            InputStream content = response.getEntity().getContent() ;
            in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
//          in = new BufferedReader(new InputStreamReader(content, "GBK"));
//          in = new BufferedReader(new InputStreamReader(content));
            StringBuilder sb = new StringBuilder();
            String line = "" ;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            result = sb.toString() ;
            System.out.println("响应报文:" + result) ;
            //  响应报文:{"ret":0,"msg":"成功","callbackurl":"https://edu.10086.cn/test-sso/login?service=http%3A%2F%2F112.35.7.169%3A9010%2Feducloud%2Flogin%2Flogin%3Ftype%3D3%26mode%3D1%26groupId%3D4000573%26provincePlatformId%3D54","accesstoken":"2467946a-bee9-4d8c-8cce-d30181073b75"}í
            //记录报文日志
            YdEduDao dao = SpringUtil.getBean(YdEduDao.class);
            dao.saveCallLog(httpUrl, paramsBuf.toString(), result);    // HJY_SERVICE_LOG

            return result ;
        } catch (Exception e) {
            e.printStackTrace() ;
        } finally {
            httpclient.close();
        }
        return null ;
    }

    public static JSONObject postData(String httpUrl, Object obj) throws Exception {
        JSONObject json = null;
        try{
        String result = post(httpUrl,obj);
             json = JSONObject.parseObject(result);
        }catch(TimeoutException e){
            System.out.println("请求超时了:"+httpUrl);
            throw e;
        }finally {
            return json ;
        }
    }

    public static String post(String httpUrl, Object obj) throws Exception {
        Map  params = getParamData(obj);
        String result = null ;

        try {
            result = post(httpUrl,params);
            return result ;
        } catch (Exception e) {
            e.printStackTrace() ;
        } finally {
            httpclient.close();
        }
        return null ;
    }


    private static Map  getParamData(Object obj) {

        Class cla = obj.getClass();
        Map map = new HashMap();
        Method[] methods = cla.getDeclaredMethods();
        try {
            for (Method m : methods) {
                String mname = m.getName();
                if (mname.startsWith("get")) {
                    String name = mname.substring(3, mname.length());// 截取字段
                    name = name.substring(0, 1).toLowerCase()
                            + name.substring(1, name.length());// 把首字母变小写
                    String value = m.invoke(obj)==null?"":m.invoke(obj).toString();
                    if(cla.equals(YdOrderRelationshipInfoSyncItem.class)&&name.equals("unionId")&&(value==null||value.equals(""))){
                        continue;
                    }
                    map.put(name,value);// 取值
                }

            }
            Class superclass = cla.getSuperclass();
            while (!superclass.equals(Object.class)) {
                Method[] superclassmethods = superclass.getDeclaredMethods();
                for (Method m : superclassmethods) {
                    String mname = m.getName();
                    if (mname.startsWith("get")) {
                        String name = mname.substring(3, mname.length());// 截取字段
                        name = name.substring(0, 1).toLowerCase()
                                + name.substring(1, name.length());// 把首字母变小写
                        String value = m.invoke(obj)==null?"":m.invoke(obj).toString();

                        if((cla.equals(OrderInfoSyncItem.class)||cla.equals(AccountPasswordInfoSyncItem.class)||cla.equals(YdOrderRelationshipInfoSyncItem.class))&&name.equals("operation"))
                            continue;

                        map.put(name,value);// 取值
                    }

                }
                superclass = superclass.getSuperclass();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return map;
    }

    public static CloseableHttpClient createSSLClientDefault(){

        try {
            //SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            // 在JSSE中,证书信任管理器类就是实现了接口X509TrustManager的类。我们可以自己实现该接口,让它信任我们指定的证书。
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            //信任所有
                X509TrustManager x509mgr = new X509TrustManager() {

                    //  该方法检查客户端的证书,若不信任该证书则抛出异常
                    public void checkClientTrusted(X509Certificate[] xcs, String string) {
                    }
                    //   该方法检查服务端的证书,若不信任该证书则抛出异常
                    public void checkServerTrusted(X509Certificate[] xcs, String string) {
                    }
                    //  返回受信任的X509证书数组。
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                };
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[] { x509mgr }, null);
                ////创建HttpsURLConnection对象,并设置其SSLSocketFactory对象
                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

            //  HttpsURLConnection对象就可以正常连接HTTPS了,无论其证书是否经权威机构的验证,只要实现了接口X509TrustManager的类MyX509TrustManager信任该证书。
            return HttpClients.custom().setSSLSocketFactory(sslsf).build();


        } catch (KeyManagementException e) {

            e.printStackTrace();

        } catch (NoSuchAlgorithmException e) {

            e.printStackTrace();

        } catch (Exception e) {

            e.printStackTrace();

        }

        // 创建默认的httpClient实例.
        return  HttpClients.createDefault();

    }



}
上一篇:网络桥接和链路聚合的配置
下一篇:宿主机通过NAT模式给kvm虚拟机配置公网IP
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站