一盛正、背景小故事
筆者手里有個朋友交給我去日常運(yùn)維項(xiàng)目是PHP+微信小程序隧熙,部署在Linux系統(tǒng)上灶似。
這個項(xiàng)目是用寶塔面板去進(jìn)行日常的可視化運(yùn)維管理荠卷,用起來蠻香的模庐。
如不清楚寶塔的同學(xué),可以自行了解油宜,這里就不詳細(xì)說明掂碱。
我們都知道, 小程序請求的后端接口慎冤,要求是https協(xié)議的疼燥。所以后端服務(wù)器得配置上SSL證書。
我接手之前蚁堤,這個項(xiàng)目的SSL證書是直接購買的醉者,而且也要到期了。當(dāng)時(shí)披诗,我對寶塔面板是第一次接觸撬即,不是特熟悉。經(jīng)過一段時(shí)間摸索呈队,看到寶塔面板提供Let's Encrypt免費(fèi)的SSL證書剥槐。這個證書有個缺點(diǎn)就是只有3個月的免費(fèi)期限,到期后需要再去續(xù)期宪摧。
經(jīng)過一番折騰舆声,就給網(wǎng)站安排上了這個免費(fèi)的證書聪富。而且寶塔面板在這里也有明確提示:將在距離到期時(shí)間一個月內(nèi)嘗試自動續(xù)簽。
看到那個提示后悲敷,發(fā)現(xiàn)這個證書可以一直免費(fèi)使用柿赊,那倒是省錢又省心了闷游。
隔了3個月后瞻鹏,老板發(fā)來一條消息說:網(wǎng)站不正常了朦佩,給我看看唄。
經(jīng)過我一頓熟悉操作分析膝蜈,排查服務(wù)器锅移,排查應(yīng)用熔掺,并利用fiddler抓包工具去進(jìn)行抓包分析后饱搏,確定是Https協(xié)議到期導(dǎo)致的問題。
然后我在SSL配置界面上置逻,手動去點(diǎn)了一下續(xù)簽推沸,等了一會兒,續(xù)簽成功,網(wǎng)站又可以正常訪問鬓催,告訴老板完美解決肺素。
又經(jīng)過一段時(shí)間后,老板又發(fā)來一條消息說宇驾,網(wǎng)站又不正常倍靡,再給我看看什么問題。
我一看到消息课舍,知道又翻車了塌西。不過這次我是輕車熟路,直接去手動點(diǎn)了一下續(xù)簽筝尾,解決捡需。
......
就這樣,重復(fù)了很多次筹淫。
這樣長久下去站辉,也不是辦法。
二损姜、萌動想法
我得想出一個法子來解決這個問題饰剥。畢竟我們都是一枚程序員,專門去解決生活中出現(xiàn)的重復(fù)勞動力摧阅。
既然點(diǎn)一下續(xù)簽捐川,就可以解決證書到期問題。那我們能不能在程序里用定時(shí)任務(wù)+模擬請求來自動續(xù)簽逸尖?
我就開始構(gòu)想一下實(shí)現(xiàn)思路:
第一步:我們需要拿到續(xù)簽按鈕觸發(fā)的后端服務(wù)接口及請求參數(shù)古沥,后續(xù)能模擬請求。
第二步:驗(yàn)證接口是否可以直接請求成功娇跟,是否需要權(quán)限驗(yàn)證岩齿?經(jīng)過驗(yàn)證,需要先登陸苞俘,才能請求成功盹沈。
第三步:還需要一個定時(shí)任務(wù)功能。經(jīng)過確認(rèn) 寶塔面板自帶有任務(wù)計(jì)劃功能吃谣。
帶著這樣的想法乞封,開始去嘗試實(shí)現(xiàn),過程中有遇到很多問題岗憋,就不詳細(xì)說明肃晚,主要都是在寫Shell腳本構(gòu)造請求參數(shù)傳遞。
我就直接上解決方案供同學(xué)們參考仔戈。
三关串、方案實(shí)踐
3.1 找到續(xù)簽請求接口
接口地址:http://IP:8888/ssl?action=Renew_SSL
如果我們直接請求該接口拧廊,會發(fā)現(xiàn)需要登陸,不能直接請求成功晋修。
3.2 設(shè)置寶塔 API接口
經(jīng)過查閱資料吧碾,寶塔面板提供了API接口,用密鑰key生成token后墓卦,再發(fā)起請求就可以倦春,而不需要用戶名和密碼。我們能方便直接使用寶塔里面的任何API接口落剪。
面板設(shè)置 =》打開 API接口溅漾,拿到密鑰key,并配置IP白名單著榴。IP可以直接添加服務(wù)器IP添履。
而且寶塔也提供了多個版本的API接口 Demo樣例,可以很方便的集成脑又。
樣例地址:https://www.bt.cn/bbs/thread-20376-1-1.html
我比較熟悉Java暮胧,也就下載的JavaDemo研究的。
package com.raysonfang.bt.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
/**
* 寶塔API測試
*
* @author fanglei
* @date 2021/02/21 10:44
**/
public class BTTest {
public static void main(String[] args)
{
try {
String btSign = "寶塔API密鑰";
String url = "http://IP:8888/ssl?action=Renew_SSL";
String timestamp = System.currentTimeMillis() + "";
String md5Sign = getMd5(btSign);
String temp = timestamp+md5Sign;
String token = getMd5(temp);
String json = "request_time="+timestamp+"&request_token="+token;
String responseText = sendPost(url,json);
System.out.println(responseText);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getMd5(String str) throws Exception
{
try {
// 生成一個MD5加密計(jì)算摘要
MessageDigest md = MessageDigest.getInstance("MD5");
// 計(jì)算md5函數(shù)
md.update(str.getBytes());
// digest()最后確定返回md5 hash值问麸,返回值為8為字符串往衷。因?yàn)閙d5 hash值是16位的hex值,實(shí)際上就是8位的字符
// BigInteger函數(shù)則將8位的字符串轉(zhuǎn)換成16位hex值严卖,用字符串來表示席舍;得到字符串形式的hash值
return new BigInteger(1, md.digest()).toString(16);
} catch (Exception e) {
throw new Exception("MD5加密出現(xiàn)錯誤,"+e.toString());
}
}
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuffer result = new StringBuffer();
try {
URL realUrl = new URL(url);
// 打開和URL之間的連接
URLConnection conn = realUrl.openConnection();
// 設(shè)置通用的請求屬性
conn.setRequestProperty("accept", "text/xml,text/javascript,text/html,application/json");
conn.setRequestProperty("connection", "Keep-Alive");
// 發(fā)送POST請求必須設(shè)置如下兩行
conn.setDoOutput(true);
conn.setDoInput(true);
// 獲取URLConnection對象對應(yīng)的輸出流
out = new PrintWriter(conn.getOutputStream());
// 發(fā)送請求參數(shù)
out.print(param);
// flush輸出流的緩沖
out.flush();
// 定義BufferedReader輸入流來讀取URL的響應(yīng)
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
System.out.println("發(fā)送 POST 請求出現(xiàn)異常哮笆!"+e);
e.printStackTrace();
}
//使用finally塊來關(guān)閉輸出流来颤、輸入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result.toString();
}
}
下載demo 研究請求參數(shù)構(gòu)成,并調(diào)試成功稠肘。
3.3 設(shè)置定時(shí)任務(wù)福铅,模擬請求
寶塔 直接提供有計(jì)劃任務(wù)功能,我們先看看能不能實(shí)現(xiàn)我們想要的功能项阴,如果不能滑黔,我們再想其他辦法解決。
我看了任務(wù)類型有:Shell腳本, 備份網(wǎng)站环揽,備份數(shù)據(jù)庫略荡,日志切割,釋放內(nèi)存歉胶,訪問URL汛兜。
其中Shell腳本和訪問URL這兩種任務(wù)類型跟我們想要的很接近,其他的都不怎么適合跨扮。
訪問URL序无,這種類型也可以排除验毡,是因?yàn)檫@里采用直接配置URL衡创,適合無動態(tài)參數(shù)帝嗡,無權(quán)限驗(yàn)證的URL。
那剩下的就只有Shell腳本來實(shí)現(xiàn)璃氢。
我們知道Shell腳本也是一種編程語言腳本哟玷,那我們就用它來模擬請求了。
經(jīng)過幾個小時(shí)的研究一也,把shell腳本寫出來巢寡。還是很費(fèi)勁,對于Shell腳本里的參數(shù)傳遞語法不怎么熟悉椰苟,也反復(fù)去嘗試抑月,才摸索清楚。
#!/bin/bash
# 獲取時(shí)間戳
cur_timestamp=$((`date '+%s'`*1000+`date '+%N'`/1000000))
# 寶塔密鑰
api_sk='uSth3rmADQ9Np5Zyhxxxxxxxxxxxxxxx'
# 密鑰MD5加密
key=`echo -n $api_sk|md5sum|cut -d" " -f1`
# 生成token
request_token=`echo -n $cur_timestamp$key|md5sum|cut -d" " -f1`
# 構(gòu)造請求參數(shù)舆蝴,并通過curl發(fā)送請求
curl -i -X POST -d "request_token=$request_token&request_time=$cur_timestamp" http://ip:8888/ssl?action=Renew_SSL
把Shell腳本的密鑰和IP進(jìn)行替換谦絮,就可以直接去任務(wù)計(jì)劃添加上,然后手動執(zhí)行一下看看 是否可以運(yùn)行成功
注意:第一次添加任務(wù)后洁仗,需要手動點(diǎn)執(zhí)行层皱,并在日志去查看是否執(zhí)行成功
至此,以后可以放心交給程序自動續(xù)簽赠潦。
四叫胖、總結(jié)
也許官方已經(jīng)解決了自動續(xù)簽的問題,而我這個也許是個偏方她奥,但是這里面還是包含抓包瓮增,定時(shí)任務(wù),接口鑒權(quán)哩俭,Shell腳本等知識運(yùn)用钉赁。