ios的簽名機(jī)制詳解
iOS 簽名機(jī)制挺復(fù)雜,各種證書(shū),Provisioning Profile,entitlements,CertificateSigningRequest,p12,AppID,概念一堆,也很容易出錯(cuò),本文嘗試從原理出發(fā),一步步推出為什么會(huì)有這么多概念,希望能有助于理解 iOS App 簽名的原理和流程。
目的先來(lái)看看蘋(píng)果的簽名機(jī)制是為了做什么。在 iOS 出來(lái)之前,在主流操作系統(tǒng)(Mac/Windows/Linux)上開(kāi)發(fā)和運(yùn)行軟件是不需要簽名的,軟件隨便從哪里下載都能運(yùn)行,導(dǎo)致平臺(tái)對(duì)第三方軟件難以控制,盜版流行。蘋(píng)果希望解決這樣的問(wèn)題,在 iOS 平臺(tái)對(duì)第三方 App 有絕對(duì)的控制權(quán),一定要保證每一個(gè)安裝到 iOS 上的 App 都是經(jīng)過(guò)蘋(píng)果官方允許的,怎樣保證呢?就是通過(guò)簽名機(jī)制。
非對(duì)稱(chēng)加密通常我們說(shuō)的簽名就是數(shù)字簽名,它是基于非對(duì)稱(chēng)加密算法實(shí)現(xiàn)的。對(duì)稱(chēng)加密是通過(guò)同一份密鑰加密和解密數(shù)據(jù),而非對(duì)稱(chēng)加密則有兩份密鑰,分別是公鑰和私鑰,用公鑰加密的數(shù)據(jù),要用私鑰才能解密,用私鑰加密的數(shù)據(jù),要用公鑰才能解密。 簡(jiǎn)單說(shuō)一下常用的非對(duì)稱(chēng)加密算法 RSA 的數(shù)學(xué)原理,理解簡(jiǎn)單的數(shù)學(xué)原理,就可以理解非對(duì)稱(chēng)加密是怎么做到的,為什么會(huì)是安全的:
1. 選兩個(gè)質(zhì)數(shù) p 和 q,相乘得出一個(gè)大整數(shù) n,例如 p = 61,q = 53,n = pq = 3233;
2.選 1-n 間的隨便一個(gè)質(zhì)數(shù) e,例如 e = 17;
3. 經(jīng)過(guò)一系列數(shù)學(xué)公式,算出一個(gè)數(shù)字 d,滿(mǎn)足:
通過(guò) n 和 e 這兩個(gè)數(shù)據(jù)一組數(shù)據(jù)進(jìn)行數(shù)學(xué)運(yùn)算后,可以通過(guò) n 和 d 去反解運(yùn)算,反過(guò)來(lái)也可以。
如果只知道 n 和 e,要推導(dǎo)出 d,需要知道 p 和 q,也就是要需要把 n 因數(shù)分解。
上述的 (n,e) 這兩個(gè)數(shù)據(jù)在一起就是公鑰,(n,d) 這兩個(gè)數(shù)據(jù)就是私鑰,滿(mǎn)足用私鑰加密,公鑰解密,或反過(guò)來(lái)公鑰加密,私鑰解密,也滿(mǎn)足在只暴露公鑰 (只知道 n 和 e)的情況下,要推導(dǎo)出私鑰 (n,d),需要把大整數(shù) n 因數(shù)分解。目前因數(shù)分解只能靠暴力窮舉,而 n 數(shù)字越大,越難以用窮舉計(jì)算出因數(shù) p 和 q,也就越安全,當(dāng) n 大到二進(jìn)制 1024 位或 2048 位時(shí),以目前技術(shù)要破解幾乎不可能,所以非常安全。 若對(duì)數(shù)字 d 是怎樣計(jì)算出來(lái)的感興趣,可以詳讀這兩篇文章:RSA 算法原理(一)、(二)。
數(shù)字簽名現(xiàn)在知道了有非對(duì)稱(chēng)加密這東西,那數(shù)字簽名是怎么回事呢?
數(shù)字簽名的作用是我對(duì)某一份數(shù)據(jù)打個(gè)標(biāo)記,表示我認(rèn)可了這份數(shù)據(jù)(簽了個(gè)名),然后我發(fā)送給其他人,其他人可以知道這份數(shù)據(jù)是經(jīng)過(guò)我認(rèn)證的,數(shù)據(jù)沒(méi)有被篡改過(guò)。
有了上述非對(duì)稱(chēng)加密算法,就可以實(shí)現(xiàn)這個(gè)需求:

首先用一種算法,算出原始數(shù)據(jù)的摘要。需滿(mǎn)足
若原始數(shù)據(jù)有任何變化,計(jì)算出來(lái)的摘要值都會(huì)變化。 摘要要夠短。這里最常用的算法是 MD5。 生成一份非對(duì)稱(chēng)加密的公鑰和私鑰,私鑰我自己拿著,公鑰公布出去。 對(duì)一份數(shù)據(jù),算出摘要后,用私鑰加密這個(gè)摘要,得到一份加密后的數(shù)據(jù),稱(chēng)為原始數(shù)據(jù)的簽名。把它跟原始數(shù)據(jù)一起發(fā)送給用戶(hù)。 用戶(hù)收到數(shù)據(jù)和簽名后,用公鑰解密得到摘要。同時(shí)用戶(hù)用同樣的算法計(jì)算原始數(shù)據(jù)的摘要,對(duì)比這里計(jì)算出來(lái)的摘要和用公鑰解密簽名得到的摘要是否相等,若相等則表示這份數(shù)據(jù)中途沒(méi)有被篡改過(guò),因?yàn)槿绻鄹倪^(guò),摘要會(huì)變化。之所以要有第一步計(jì)算摘要,是因?yàn)榉菍?duì)稱(chēng)加密的原理限制可加密的內(nèi)容不能太大(不能大于上述 n 的位數(shù),也就是一般不能大于 1024 位 / 2048 位),于是若要對(duì)任意大的數(shù)據(jù)簽名,就需要改成對(duì)它的特征值簽名,效果是一樣的。
好了,有了非對(duì)稱(chēng)加密的基礎(chǔ),知道了數(shù)字簽名是什么,怎樣可以保證一份數(shù)據(jù)是經(jīng)過(guò)某個(gè)地方認(rèn)證的,來(lái)看看怎樣通過(guò)數(shù)字簽名的機(jī)制保證每一個(gè)安裝到 iOS 上的 App 都是經(jīng)過(guò)蘋(píng)果認(rèn)證允許的。
最簡(jiǎn)單的簽名要實(shí)現(xiàn)這個(gè)需求很簡(jiǎn)單,最直接的方式,蘋(píng)果官方生成一對(duì)公私鑰,在 iOS 里內(nèi)置一個(gè)公鑰,私鑰由蘋(píng)果后臺(tái)保存,我們傳 App 上 App Store 時(shí),蘋(píng)果后臺(tái)用私鑰對(duì) App 數(shù)據(jù)進(jìn)行簽名,iOS 系統(tǒng)下載這個(gè) App 后,用公鑰驗(yàn)證這個(gè)簽名,若簽名正確,這個(gè) App 肯定是由蘋(píng)果后臺(tái)認(rèn)證的,并且沒(méi)有被修改過(guò),也就達(dá)到了蘋(píng)果的需求:保證安裝的每一個(gè) App 都是經(jīng)過(guò)蘋(píng)果官方允許的。

如果我們 iOS 設(shè)備安裝 App 只有從 App Store 下載這一種方式的話(huà),這件事就結(jié)束了,沒(méi)有任何復(fù)雜的東西,只有一個(gè)數(shù)字簽名,非常簡(jiǎn)單地解決問(wèn)題。
但實(shí)際上因?yàn)槌藦?App Store 下載,我們還可以有三種方式安裝一個(gè) App:
開(kāi)發(fā) App 時(shí)可以直接把開(kāi)發(fā)中的應(yīng)用安裝進(jìn)手機(jī)進(jìn)行調(diào)試。 In-House 企業(yè)內(nèi)部分發(fā),可以直接安裝企業(yè)證書(shū)簽名后的 App。 AD-Hoc 相當(dāng)于企業(yè)分發(fā)的限制版,限制安裝設(shè)備數(shù)量,較少用。蘋(píng)果要對(duì)用這三種方式安裝的 App 進(jìn)行控制,就有了新的需求,無(wú)法像上面這樣簡(jiǎn)單了。
新的需求我們先來(lái)看第一個(gè),開(kāi)發(fā)時(shí)安裝 App,它有兩個(gè)個(gè)需求:
安裝包不需要傳到蘋(píng)果服務(wù)器,可以直接安裝到手機(jī)上。如果你編譯一個(gè) App 到手機(jī)前要先傳到蘋(píng)果服務(wù)器簽名,這顯然是不能接受的。 蘋(píng)果必須對(duì)這里的安裝有控制權(quán),包括: 經(jīng)過(guò)蘋(píng)果允許才可以這樣安裝; 不能被濫用導(dǎo)致非開(kāi)發(fā) App 也能被安裝。為了實(shí)現(xiàn)這些需求,iOS 簽名的復(fù)雜度也就開(kāi)始增加了。 蘋(píng)果這里給出的方案是使用了雙層簽名,會(huì)比較繞,流程大概是這樣的:

上述流程只解決了上面第一個(gè)需求,也就是需要經(jīng)過(guò)蘋(píng)果允許才可以安裝,還未解決第二個(gè)避免被濫用的問(wèn)題。怎么解決呢?蘋(píng)果再加了兩個(gè)限制,一是限制在蘋(píng)果后臺(tái)注冊(cè)過(guò)的設(shè)備才可以安裝,二是限制簽名只能針對(duì)某一個(gè)具體的 App。
怎么加的?在上述第三步,蘋(píng)果用私鑰 A 簽名我們本地公鑰 L 時(shí),實(shí)際上除了簽名公鑰 L,還可以加上無(wú)限多數(shù)據(jù),這些數(shù)據(jù)都可以保證是經(jīng)過(guò)蘋(píng)果官方認(rèn)證的,不會(huì)有被篡改的可能。

可以想到把 允許安裝的設(shè)備 ID 列表 和 App 對(duì)應(yīng)的 AppID 等數(shù)據(jù),都在第三步這里跟公鑰 L 一起組成證書(shū),再用蘋(píng)果私鑰 A 對(duì)這個(gè)證書(shū)簽名。在最后第 5 步驗(yàn)證時(shí)就可以拿到設(shè)備 ID 列表,判斷當(dāng)前設(shè)備是否符合要求。根據(jù)數(shù)字簽名的原理,只要數(shù)字簽名通過(guò)驗(yàn)證,第 5 步這里的設(shè)備 IDs / AppID / 公鑰 L 就都是經(jīng)過(guò)蘋(píng)果認(rèn)證的,無(wú)法被修改,蘋(píng)果就可以限制可安裝的設(shè)備和 App,避免濫用。
最終流程到這里這個(gè)證書(shū)已經(jīng)變得很復(fù)雜了,有很多額外信息,實(shí)際上除了 設(shè)備 ID / AppID,還有其他信息也需要在這里用蘋(píng)果簽名,像這個(gè) App 里 iCloud / push / 后臺(tái)運(yùn)行 等權(quán)限蘋(píng)果都想控制,蘋(píng)果把這些權(quán)限開(kāi)關(guān)統(tǒng)一稱(chēng)為 Entitlements,它也需要通過(guò)簽名去授權(quán)。
實(shí)際上一個(gè)“證書(shū)”本來(lái)就有規(guī)定的格式規(guī)范,上面我們把各種額外信息塞入證書(shū)里是不合適的,于是蘋(píng)果另外搞了個(gè)東西,叫 Provisioning Profile,一個(gè) Provisioning Profile 里就包含了證書(shū)以及上述提到的所有額外信息,以及所有信息的簽名。
所以整個(gè)流程稍微變一下,就變成這樣了:

因?yàn)椴襟E有小變動(dòng),這里我們不辭??輪匭略倭幸槐檎?雋鞒蹋?/p> 在你的 Mac 開(kāi)發(fā)機(jī)器生成一對(duì)公私鑰,這里稱(chēng)為公鑰 L,私鑰 L。L:Local 蘋(píng)果自己有固定的一對(duì)公私鑰,跟上面 App Store 例子一樣,私鑰在蘋(píng)果后臺(tái),公鑰在每個(gè) iOS 設(shè)備上。這里稱(chēng)為公鑰 A,私鑰 A。A:Apple 把公鑰 L 傳到蘋(píng)果后臺(tái),用蘋(píng)果后臺(tái)里的私鑰 A 去簽名公鑰 L。得到一份數(shù)據(jù)包含了公鑰 L 以及其簽名,把這份數(shù)據(jù)稱(chēng)為證書(shū)。 在蘋(píng)果后臺(tái)申請(qǐng) AppID,配置好設(shè)備 ID 列表和 App 可使用的權(quán)限,再加上第③步的證書(shū),組成的數(shù)據(jù)用私鑰 A 簽名,把數(shù)據(jù)和簽名一起組成一個(gè) Provisioning Profile 文件,下載到本地 Mac 開(kāi)發(fā)機(jī)。 在開(kāi)發(fā)時(shí),編譯完一個(gè) App 后,用本地的私鑰 L 對(duì)這個(gè) App 進(jìn)行簽名,同時(shí)把第④步得到的 Provisioning Profile 文件打包進(jìn) App 里,文件名為 embedded.mobileprovision,把 App 安裝到手機(jī)上。 在安裝時(shí),iOS 系統(tǒng)取得證書(shū),通過(guò)系統(tǒng)內(nèi)置的公鑰 A,去驗(yàn)證 embedded.mobileprovision 的數(shù)字簽名是否正確,里面的證書(shū)簽名也會(huì)再驗(yàn)一遍。 確保了 embedded.mobileprovision 里的數(shù)據(jù)都是蘋(píng)果授權(quán)以后,就可以取出里面的數(shù)據(jù),做各種驗(yàn)證,包括用公鑰 L 驗(yàn)證 App 簽名,驗(yàn)證設(shè)備 ID 是否在 ID 列表上,AppID 是否對(duì)應(yīng)得上,權(quán)限開(kāi)關(guān)是否跟 App 里的 Entitlements 對(duì)應(yīng)等。
開(kāi)發(fā)者證書(shū)從簽名到認(rèn)證最終蘋(píng)果采用的流程大致是這樣,還有一些細(xì)節(jié)像證書(shū)有效期/證書(shū)類(lèi)型等就不細(xì)說(shuō)了。
概念和操作上面的步驟對(duì)應(yīng)到我們平常具體的操作和概念是這樣的:
第 1 步對(duì)應(yīng)的是 keychain 里的 “從證書(shū)頒發(fā)機(jī)構(gòu)請(qǐng)求證書(shū)”,這里就本地生成了一堆公私鑰,保存的 CertificateSigningRequest 就是公鑰,私鑰保存在本地電腦里。
第 2 步蘋(píng)果處理,不用管。
第 3 步對(duì)應(yīng)把 CertificateSigningRequest 傳到蘋(píng)果后臺(tái)生成證書(shū),并下載到本地。這時(shí)本地有兩個(gè)證書(shū),一個(gè)是第 1 步生成的,一個(gè)是這里下載回來(lái)的,keychain 會(huì)把這兩個(gè)證書(shū)關(guān)聯(lián)起來(lái),因?yàn)樗麄児借€是對(duì)應(yīng)的,在 XCode 選擇下載回來(lái)的證書(shū)時(shí),實(shí)際上會(huì)找到 keychain 里對(duì)應(yīng)的私鑰去簽名。這里私鑰只有生成它的這臺(tái) Mac 有,如果別的 Mac 也要編譯簽名這個(gè) App 怎么辦?答案是把私鑰導(dǎo)出給其他 Mac 用,在 keychain 里導(dǎo)出私鑰,就會(huì)存成 .p12 文件,其他 Mac 打開(kāi)后就導(dǎo)入了這個(gè)私鑰。
第 4 步都是在蘋(píng)果網(wǎng)站上操作,配置 AppID / 權(quán)限 / 設(shè)備等,最后下載 Provisioning Profile 文件。
第 5 步 XCode 會(huì)通過(guò)第 3 步下載回來(lái)的證書(shū)(存著公鑰),在本地找到對(duì)應(yīng)的私鑰(第一步生成的),用本地私鑰去簽名 App,并把 Provisioning Profile 文件命名為 embedded.mobileprovision 一起打包進(jìn)去。這里對(duì) App 的簽名數(shù)據(jù)保存分兩部分,Mach-O 可執(zhí)行文件會(huì)把簽名直接寫(xiě)入這個(gè)文件里,其他資源文件則會(huì)保存在 _CodeSignature 目錄下。
第 6 ? 7 步的打包和驗(yàn)證都是 Xcode 和 iOS 系統(tǒng)自動(dòng)做的事。
這里再總結(jié)一下這些概念:
證書(shū): 內(nèi)容是公鑰或私鑰,由其他機(jī)構(gòu)對(duì)其簽名組成的數(shù)據(jù)包。 Entitlements: 包含了 App 權(quán)限開(kāi)關(guān)列表。 CertificateSigningRequest: 本地公鑰。 p12: 本地私鑰,可以導(dǎo)入到其他電腦。 Provisioning Profile: 包含了 證書(shū) / Entitlements 等數(shù)據(jù),并由蘋(píng)果后臺(tái)私鑰簽名的數(shù)據(jù)包。其他發(fā)布方式前面以開(kāi)發(fā)包為例子說(shuō)了簽名和驗(yàn)證的流程,另外兩種方式 In-House 企業(yè)簽名和 AD-Hoc 流程也是差不多的,只是企業(yè)簽名不限制安裝的設(shè)備數(shù),另外需要用戶(hù)在 iOS 系統(tǒng)設(shè)置上手動(dòng)點(diǎn)擊信任這個(gè)企業(yè)才能通過(guò)驗(yàn)證。
而 App Store 的簽名驗(yàn)證方式有些不一樣,前面我們說(shuō)到最簡(jiǎn)單的簽名方式,蘋(píng)果在后臺(tái)直接用私鑰簽名 App 就可以了,實(shí)際上蘋(píng)果確實(shí)是這樣做的,如果去下載一個(gè) App Store 的安裝包,會(huì)發(fā)現(xiàn)它里面是沒(méi)有 embedded.mobileprovision 文件的,也就是它安裝和啟動(dòng)的流程是不依賴(lài)這個(gè)文件,驗(yàn)證流程也就跟上述幾種類(lèi)型不一樣了。
據(jù)猜測(cè),因?yàn)樯蟼鞯?App Store 的包蘋(píng)果會(huì)重新對(duì)內(nèi)容加密,原來(lái)的本地私鑰簽名就沒(méi)有用了,需要重新簽名,從 App Store 下載的包蘋(píng)果也并不打算控制它的有效期,不需要內(nèi)置一個(gè) embedded.mobileprovision 去做校驗(yàn),直接在蘋(píng)果用后臺(tái)的私鑰重新簽名,iOS 安裝時(shí)用本地公鑰驗(yàn)證 App 簽名就可以了。
那為什么發(fā)布 App Store 的包還是要跟開(kāi)發(fā)版一樣搞各種證書(shū)和 Provisioning Profile?猜測(cè)因?yàn)樘O(píng)果想做統(tǒng)一管理,Provisioning Profile 里包含一些權(quán)限控制,AppID 的檢驗(yàn)等,蘋(píng)果不想在上傳 App Store 包時(shí)重新用另一種協(xié)議做一遍這些驗(yàn)證,就不如統(tǒng)一把這部分放在 Provisioning Profile 里,上傳 App Store 時(shí)只要用同樣的流程驗(yàn)證這個(gè) Provisioning Profile 是否合法就可以了
所以 App 上傳到 App Store 后,就跟你的 證書(shū) / Provisioning Profile 都沒(méi)有關(guān)系了,無(wú)論他們是否過(guò)期或被廢除,都不會(huì)影響 App Store 上的安裝包。 到這里 iOS 簽名機(jī)制的原理和主流程大致說(shuō)完了,希望能對(duì)理解蘋(píng)果簽名和排查日常簽名問(wèn)題有所幫助。
P.S.一些疑問(wèn)最后這里再提一下我關(guān)于簽名流程的一些的疑問(wèn)。
企業(yè)證書(shū)企業(yè)證書(shū)簽名因?yàn)橄拗粕伲趪?guó)內(nèi)被廣泛用于測(cè)試和盜版,fir.im / 蒲公英等測(cè)試平臺(tái)都是通過(guò)企業(yè)證書(shū)分發(fā),國(guó)內(nèi)一些市場(chǎng)像 PP 助手、愛(ài)思助手,一部分安裝手段也是通過(guò)企業(yè)證書(shū)重簽名。通過(guò)企業(yè)證書(shū)簽名安裝的 App,啟動(dòng)時(shí)都會(huì)驗(yàn)證證書(shū)的有效期,并且不定期請(qǐng)求蘋(píng)果服務(wù)器看證書(shū)是否被吊銷(xiāo),若已過(guò)期或被吊銷(xiāo),就會(huì)無(wú)法啟動(dòng) App。對(duì)于這種助手的盜版安裝手段,蘋(píng)果想打擊只能一個(gè)個(gè)吊銷(xiāo)企業(yè)證書(shū),并沒(méi)有太好的辦法。
這里我的疑問(wèn)是,蘋(píng)果做了那么多簽名和驗(yàn)證機(jī)制去限制在 iOS 安裝 App,為什么又要出這樣一個(gè)限制很少的方式讓盜版鉆空子呢?若真的是企業(yè)用途不適合上 App Store,也完全可以在 App Store 開(kāi)辟一個(gè)小的私密版塊,還是通過(guò) App Store 去安裝,就不會(huì)有這個(gè)問(wèn)題了
App Store 加密另一個(gè)問(wèn)題是我們把 App 傳上 App Store 后,蘋(píng)果會(huì)對(duì) App 進(jìn)行加密,導(dǎo)致 App 體積增大不少,這個(gè)加密實(shí)際上是沒(méi)卵用的,只是讓破解的人要多做一個(gè)步驟,運(yùn)行 App 去內(nèi)存 dump 出可執(zhí)行文件而已,無(wú)論怎樣加密,都可以用這種方式拿出加密前的可執(zhí)行文件。所以為什么要做這樣的加密呢?想不到有什么好處。
本地私鑰我們看到前面說(shuō)的簽名流程很繞很復(fù)雜,經(jīng)常出現(xiàn)各種問(wèn)題,像有 Provisioning Profile 文件但證書(shū)又不對(duì),本地有公鑰證書(shū)沒(méi)對(duì)應(yīng)私鑰等情況,不理解原理的情況下會(huì)被繞暈,我的疑問(wèn)是,這里為什么不能簡(jiǎn)化呢?
還是以開(kāi)發(fā)證書(shū)為例,為什么一定要用本地 Mac 生成的私鑰去簽名?蘋(píng)果要的只是本地簽名,私鑰不一定是要本地生成的,蘋(píng)果也可以自己生成一對(duì)公私鑰給我們,放在 Provisioning Profile 里,我們用里面的私鑰去加密就行了,這樣就不會(huì)有 CertificateSigningRequest 和 p12 的概念,跟本地 keychain 沒(méi)有關(guān)系,不需要關(guān)心證書(shū),只要有 Provisioning Profile 就能簽名,流程會(huì)減少,易用性會(huì)提高很多,同時(shí)蘋(píng)果想要的控制一點(diǎn)都不會(huì)少,也沒(méi)有什么安全問(wèn)題,為什么不這樣設(shè)計(jì)呢?
能想到的一個(gè)原因是 Provisioning Profile 在非 App Store 安裝時(shí)會(huì)打包進(jìn)安裝包,第三方拿到這個(gè) Provisioning Profile 文件就能直接用起來(lái)給他自己的 App 簽名了。但這種問(wèn)題也挺好解決,只需要打包時(shí)去掉文件里的私鑰就行了,所以仍不明白為什么這樣設(shè)計(jì)。
以上就是ios的簽名機(jī)制詳解的詳細(xì)內(nèi)容,更多關(guān)于ios的簽名機(jī)制的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. .net中string類(lèi)型可以作為lock的鎖對(duì)象嗎2. 新手學(xué)python應(yīng)該下哪個(gè)版本3. 詳細(xì)總結(jié)Java for循環(huán)的那些坑4. python 使用Tensorflow訓(xùn)練BP神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)鳶尾花分類(lèi)5. jsp文件下載功能實(shí)現(xiàn)代碼6. python對(duì)批量WAV音頻進(jìn)行等長(zhǎng)分割的方法實(shí)現(xiàn)7. Java進(jìn)行Appium自動(dòng)化測(cè)試的實(shí)現(xiàn)8. uni-app結(jié)合PHP實(shí)現(xiàn)單用戶(hù)登陸demo及解析9. ajax實(shí)現(xiàn)頁(yè)面的局部加載10. 如何利用Python matplotlib繪制雷達(dá)圖

網(wǎng)公網(wǎng)安備