Springboot中配置SSL证书 一、为什么要部署SSL证书:
目前互联网采取的是全网强制SSL,尤其是微信开发、APP开发、谷歌等都是强制的,为确保数据安全性,把http请求改成HTTPS(URL https://而不是http://)请求确保客户端与站点传输数据的加密作用,所有操作系统都可以部署。
二、SSL证书是什么?
SSL(Secure socket layer)对用户和服务器进行认证,对传输数据进行加密的和隐藏的全球化标准的的安全协议,保证在互联网交易中,双方传递信息的安全性。
作为文件形式存在的证书一般有这几种格式:
**1.**带有私钥的证书 由Public Key Cryptography Standards #12,PKCS#12标准定义,包含了公钥和私钥的二进制格式的证书形式,以pfx作为证书文件后缀名。
**2.**二进制编码的证书 证书中没有私钥,DER 编码二进制格式的证书文件,以cer作为证书文件后缀名。
3.Base64 编码的证书 证书中没有私钥,BASE64 编码格式的证书文件,也是以cer作为证书文件后缀名。
由定义可以看出,只有pfx** 格式的数字证书是包含有私钥的,cer**格式的数字证书里面只有公钥没有私钥。
在pfx证书的导入过程中有一项是“标志此密钥是可导出的。这将您在稍候备份或传输密钥”。一般是不选中的,如果选中,别人就有机会备份你的密钥了。如果是不选中,其实密钥也导入了,只是不能再次被导出。这就保证了密钥的安全。
如果导入过程中没有选中这一项,做证书备份时“导出私钥”这一项是灰色的,不能选。只能导出cer格式的公钥。如果导入时选中该项,则在导出时“导出私钥”这一项就是可选的。
如果要导出私钥(pfx),是需要输入密码的,这个密码就是对私钥再次加密,这样就保证了私钥的安全,别人即使拿到了你的证书备份(pfx),不知道加密私钥的密码,也是无法导入证书的。相反,如果只是导入导出cer格式的证书,是不会提示你输入密码的。因为公钥一般来说是对外公开的,不用加密
三. 配置
将.pfx文件放到项目的resources目录下
在properties或yml中如下配置
server.port=8002 server.ssl.key-store=classpath:yunservice.ltd.pfx server.ssl.key-store-password=7K8UBVe5 server.ssl.key-store-type=PKCS12 server.ssl.key-alias=alias
在项目中添加如下配置
@Configuration public class TomcatConfig { @Bean TomcatServletWebServerFactory tomcatServletWebServerFactory () { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() { @Override protected void postProcessContext (Context context) { SecurityConstraint constraint = new SecurityConstraint(); constraint.setUserConstraint("CONFIDENTIAL" ); SecurityCollection collection = new SecurityCollection(); collection.addPattern("/" ); constraint.addCollection(collection); context.addConstraint(constraint); } }; factory.addAdditionalTomcatConnectors(createTomcatConnector()); return factory; } private Connector createTomcatConnector () { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol" ); connector.setScheme("http" ); connector.setPort(8001 ); connector.setSecure(false ); connector.setRedirectPort(8002 ); return connector; } }
这里首先配置一个TomcatServletWebServerFactory,然后添加一个Tomcat中的Connector(监听80端口),并将请求转发到8080上去.
配置完成后,在浏览器中输入:”http://localhost:8001/hello",就会自动重定向到https://localhost:8002/hello上。
或者直接输入https://localhost:8002/hello也是能访问的
问题: post请求重定向后变成了GET请求 配置之后,确实生效。http get 请求 都能被重定向到https get 上,但是 http post 请求 死活不行,后台老是报requestMethod GET not supported,我访问的是post 方法呀,怎么就变成了get,百思不得其解,后来看到一篇博客讲到nginx做转发的时候也有这种问题,看了一下他的解决方法。
server { listen 80; server_name *.snsprj.cn; return 307 https:// request_uri; }
我首先注意到了307 状态码,于是我想看一下我用tomcat 配置的它默认返回的状态码是多少 从上图中可以看出状态码是302,由于本人知识面比较窄,并不知道302,307的代表含义,于是就去查询相关资料,摘自wikipedia
302 Found 要求客户端执行临时重定向(原始描述短语为“Moved Temporarily”)。[20]由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。 新的临时性的URI应当在响应的Location域中返回。除非这是一个HEAD请求,否则响应的实体中应当包含指向新的URI的超链接及简短说明。 如果这不是一个GET或者HEAD请求,那么浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。 注意:虽然RFC 1945和RFC 2068规范不允许客户端在重定向时改变请求的方法,但是很多现存的浏览器将302响应视作为303响应,并且使用GET方式访问在Location中规定的URI,而无视原先请求的方法。因此状态码303和307被添加了进来,用以明确服务器期待客户端进行何种反应。
看到这里就明白了为什么http post 请求重定向时会被变成http get 请求。
307 Temporary Redirect 在这种情况下,请求应该与另一个URI重复,但后续的请求应仍使用原始的URI。 与302相反,当重新发出原始请求时,不允许更改请求方法。 例如,应该使用另一个POST请求来重复POST请求
解决办法 由于我使用的是tomcat 进行重定向的,从上文中也可以看出tomcat 默认进行的是302重定向,不符合我们的需求,那又什么办法可以解决呢?办法就是不用tomcat提供的配置,那就是我们自己后台程序处理,毕竟用人家的受限制,自己搞,想怎么弄就怎么弄。自己做的话很明显需要一个过滤器,在请求到达sevlet 之前进行处理,代码如下:
import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Component @WebFilter(urlPatterns="/*",filterName="HttpsFilter") public class HttpsFilter implements Filter { private Logger logger = LoggerFactory.getLogger(HttpsFilter.class); private static final String HTTPS ="https" ; private static final int HTTPS_PORT = 8443 ; @Override public void destroy () { logger.info("------------destroy HttpsFilter --------------" ); } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { URL newUrl = null ; if (request.getScheme().equals(HTTPS)) { chain.doFilter(request, response); }else { HttpServletRequest httpRequest = (HttpServletRequest)request; HttpServletResponse httpResponse = (HttpServletResponse)response; String queryString = httpRequest.getQueryString()==null ? "" :"?" +httpRequest.getQueryString(); httpResponse.setStatus(307 ); String requestUrl = httpRequest.getRequestURL().toString(); URL reqUrl = new URL(requestUrl+queryString); logger.info("【original request-】 " +reqUrl.toString()); newUrl = new URL(HTTPS,reqUrl.getHost(),HTTPS_PORT,reqUrl.getFile()); logger.info("【new request-】 " +newUrl.toString()); httpResponse.setHeader("Location" , newUrl.toString()); httpResponse.setHeader("Connection" , "close" ); httpResponse.addHeader("Access-Control-Allow-Origin" , "*" ); } } @Override public void init (FilterConfig arg0) throws ServletException { logger.info("------------init HttpsFilter --------------" ); } }
注意 :这里又个坑,就是重定向后需要也需要解决跨域问题,不然页面ajax请求 http 地址会出现跨域问题。
关键代码
httpResponse.setStatus(307 );
将响应码换成307