應用程序身份驗證曾經只依賴於用戶名/郵箱和密碼等憑據,會話用於維護用戶狀態直至用戶註銷。之後,我們開始使用身份驗證API。最近,JSON Web Tokens (JWT) 越來越多地用於對服務器請求進行身份驗證。
本文將介紹JWT是什麼以及如何使用PHP進行基於JWT的用戶請求身份驗證。
要點
- 身份驗證方法的演變: 本文概述了用戶身份驗證方法的演變,從傳統的會話到使用JSON Web Tokens (JWT),突出了向更安全、更高效的Web應用程序用戶身份驗證和會話管理方式的轉變。
- JWT的優勢和應用: 本文解釋了JWT相對於其他身份驗證方法的優勢,例如存儲信息和元數據的能力、與OAUTH2的兼容性以及過期控制的提供,說明了JWT如何增強用戶身份驗證過程的安全性與靈活性。
- PHP中的實際應用: 本文提供了在基於PHP的應用程序中實現JWT的綜合指南,涵蓋了JWT的生成、使用和驗證。其中包括詳細的代碼示例和解釋,為讀者提供了一個在自己的Web項目中集成基於JWT的身份驗證的清晰路線圖。
JWT與會話
首先,為什麼會話不是那麼好呢?主要有三個原因:
- 數據以明文形式存儲在服務器上。即使數據通常不存儲在公共文件夾中,任何擁有足夠服務器訪問權限的人都可以讀取會話文件的內容。
- 它們涉及文件系統讀/寫請求。每次會話啟動或其數據被修改時,服務器都需要更新會話文件。每次應用程序發送會話cookie時也是如此。如果用戶數量很多,最終可能會導致服務器速度變慢,除非您使用備用的會話存儲選項,例如Memcached和Redis。
- 分佈式/集群式應用程序。由於會話文件默認存儲在文件系統上,因此很難為高可用性應用程序(需要使用負載均衡器和集群服務器等技術)構建分佈式或集群式基礎架構。必須實現其他存儲介質和特殊配置,並且必須充分了解其含義。
JWT
現在,讓我們開始學習JWT。 JSON Web Token規範(RFC 7519)於2010年12月28日首次發布,最近一次更新是在2015年5月。
JWT比API密鑰具有許多優勢,包括:
- API密鑰是隨機字符串,而JWT包含信息和元數據。這些信息和元數據可以描述各種內容,例如用戶的身份、授權數據以及令牌在時間範圍或相對於域的有效性。
- JWT不需要集中的頒發或撤銷機構。
- JWT與OAUTH2兼容。
- JWT數據可以被檢查。
- JWT具有過期控制。
- JWT適用於空間受限的環境,例如HTTP Authorization標頭。
- 數據以JavaScript對象表示法(JSON)格式傳輸。
- JWT使用Base64url編碼表示。
JWT長什麼樣?
這是一個JWT示例:
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
乍一看,這個字符串似乎只是用句點或點字符連接的隨機字符組。因此,它似乎與API密鑰沒有什麼不同。但是,如果您仔細觀察,就會發現有三個單獨的字符串。
JWT標頭
第一個字符串是JWT標頭。它是一個Base64 URL編碼的JSON字符串。它指定了用於生成簽名的加密算法以及令牌的類型,該類型始終設置為JWT。該算法可以是對稱的或非對稱的。
對稱算法使用單個密鑰來創建和驗證令牌。該密鑰在JWT的創建者和使用者之間共享。務必確保只有創建者和使用者知道密鑰。否則,任何人都可以創建有效的令牌。
非對稱算法使用私鑰來簽署令牌,並使用公鑰來驗證令牌。當共享密鑰不切實際或其他方只需要驗證令牌的完整性時,應使用這些算法。
JWT的有效負載
第二個字符串是JWT的有效負載。它也是一個Base64 URL編碼的JSON字符串。它包含一些標準字段,稱為“聲明”。聲明有三種類型:註冊的、公共的和私有的。
註冊的聲明是預定義的。您可以在JWT的RFC中找到它們的列表。以下是一些常用的聲明:
- iat:令牌頒發的日期時間戳。
- key:一個唯一的字符串,可用於驗證令牌,但這與沒有集中的頒發者機構相悖。
- iss:包含頒發者名稱或標識符的字符串。可以是域名,可用於丟棄來自其他應用程序的令牌。
- nbf:令牌應開始被視為有效的日期時間戳。應等於或大於iat。
- exp:令牌應停止有效的日期時間戳。應大於iat和nbf。
您可以根據需要定義公共聲明。但是,它們不能與註冊的聲明或已存在的公共聲明的聲明相同。您可以隨意創建私有聲明。它們僅供雙方使用:生產者和消費者。
JWT的簽名
JWT的簽名是一種加密機制,旨在使用對令牌內容唯一的數字簽名來保護JWT的數據。簽名確保JWT的完整性,以便使用者可以驗證它沒有被惡意行為者篡改。
JWT的簽名是三件事的組合:
- JWT的標頭
- JWT的有效負載
- 一個秘密值
這三者使用JWT標頭中指定的算法進行數字簽名(未加密)。如果我們解碼上面的示例,我們將得到以下JSON字符串:
JWT的標頭
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
JWT的數據
<code>{ "alg": "HS256", "typ": "JWT" }</code>
您可以自己嘗試jwt.io,在那裡您可以嘗試編碼和解碼您自己的JWT。
在基於PHP的應用程序中使用JWT
既然您已經了解了JWT是什麼,那麼現在是時候學習如何在PHP應用程序中使用它們了。在深入研究之前,您可以隨意克隆本文的代碼,或者按照我們的步驟進行操作。
您可以採用多種方法來集成JWT,但以下是我們將要採用的方法。
除登錄和註銷頁面外,對應用程序的所有請求都需要通過JWT進行身份驗證。如果用戶在沒有JWT的情況下發出請求,他們將被重定向到登錄頁面。
用戶填寫並提交登錄表單後,表單將通過JavaScript提交到我們應用程序中的登錄端點authenticate.php。然後,端點將從請求中提取憑據(用戶名和密碼),並檢查它們是否有效。
如果有效,它將生成一個JWT並將其發送回客戶端。當客戶端收到JWT時,它將存儲JWT並將其用於對應用程序的未來每次請求。
對於一個簡單的場景,用戶只能請求一個資源——一個恰當命名的PHP文件resource.php。它不會做太多事情,只是返回一個包含請求時當前時間戳的字符串。
在發出請求時,可以使用多種方法來使用JWT。在我們的應用程序中,JWT將發送在Bearer授權標頭中。
如果您不熟悉Bearer Authorization,它是一種HTTP身份驗證形式,其中令牌(例如JWT)發送在請求標頭中。服務器可以檢查令牌並確定是否應授予令牌的“持有者”訪問權限。
這是一個標頭的示例:
<code>{ "iat": 1416929109, "jti": "aa7f8d0a95c", "scopes": [ "repo", "public_repo" ] }</code>
對於我們的應用程序收到的每個請求,PHP都將嘗試從Bearer標頭中提取令牌。如果存在,則對其進行驗證。如果有效,用戶將看到該請求的正常響應。但是,如果JWT無效,則不允許用戶訪問資源。
請注意,JWT並非旨在替代會話cookie。
先決條件
首先,我們需要在我們的系統上安裝PHP和Composer。
在項目的根目錄中,運行composer install。這將引入Firebase PHP-JWT,這是一個簡化JWT操作的第三方庫,以及用於簡化應用程序中對配置數據訪問的laminas-config。
登錄表單
安裝庫後,讓我們逐步完成authenticate.php中的登錄代碼。我們首先進行通常的設置,確保Composer生成的自動加載器可用。
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
收到表單提交後,憑據將針對數據庫或其他一些數據存儲進行驗證。出於本示例的目的,我們將假設它們有效,並將$hasValidCredentials設置為true。
<code>{ "alg": "HS256", "typ": "JWT" }</code>
接下來,我們初始化一組變量,用於生成JWT。請記住,由於JWT可以在客戶端進行檢查,因此不要在其中包含任何敏感信息。
另一件值得指出的是,$secretKey不會像這樣初始化。您可能會在環境中設置它並提取它,使用phpdotenv等庫,或在配置文件中設置它。在本例中,我避免這樣做,因為我想關注JWT代碼。
切勿洩露它或將其存儲在版本控制下!
<code>{ "iat": 1416929109, "jti": "aa7f8d0a95c", "scopes": [ "repo", "public_repo" ] }</code>
準備好有效負載數據後,我們接下來使用php-jwt的靜態encode方法來創建JWT。
該方法:
- 將數組轉換為JSON
- 生成標頭
- 簽署有效負載
- 編碼最終字符串
它接受三個參數:
- 有效負載信息
- 密鑰
- 用於簽署令牌的算法
通過對函數結果調用echo,返回生成的令牌:
<code>Authorization: Bearer ab0dde18155a43ee83edba4a4542b973</code>
使用JWT
現在客戶端有了令牌,您可以使用JavaScript或您喜歡的任何機制來存儲它。以下是如何使用原生JavaScript進行操作的示例。在index.html中,成功提交表單後,收到的JWT將存儲在內存中,登錄表單將被隱藏,並且將顯示請求時間戳的按鈕:
<?php declare(strict_types=1); use Firebase\JWT\JWT; require_once('../vendor/autoload.php');
使用JWT
單擊“獲取當前時間戳”按鈕時,將向resource.php發出GET請求,該請求在Authorization標頭中設置身份驗證後收到的JWT。
<?php // 从请求中提取凭据 if ($hasValidCredentials) {
當我們單擊按鈕時,將發出類似於以下內容的請求:
$secretKey = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew='; $issuedAt = new DateTimeImmutable(); $expire = $issuedAt->modify('+6 minutes')->getTimestamp(); // 添加60秒 $serverName = "your.domain.name"; $username = "username"; // 从过滤后的POST数据中检索 $data = [ 'iat' => $issuedAt->getTimestamp(), // 颁发时间:生成令牌的时间 'iss' => $serverName, // 颁发者 'nbf' => $issuedAt->getTimestamp(), // 不早于 'exp' => $expire, // 过期 'userName' => $username, // 用户名 ];
假設JWT有效,我們將看到資源,之後響應將寫入控制台。
驗證JWT
最後,讓我們看看如何在PHP中驗證令牌。與往常一樣,我們將包含Composer的自動加載器。然後,我們可以選擇檢查是否使用了正確的請求方法。為了繼續關注JWT特定的代碼,我已經跳過了執行此操作的代碼:
<?php // 将数组编码为JWT字符串。 echo JWT::encode( $data, $secretKey, 'HS512' ); }
然後,代碼將嘗試從Bearer標頭中提取令牌。我已經使用preg_match這樣做了。如果您不熟悉該函數,它將在字符串上執行正則表達式匹配。
我在這裡使用的正則表達式將嘗試從Bearer標頭中提取令牌,並轉儲其他所有內容。如果找不到,則返回HTTP 400錯誤請求:
const store = {}; const loginButton = document.querySelector('#frmLogin'); const btnGetResource = document.querySelector('#btnGetResource'); const form = document.forms[0]; // 将jwt插入到store对象中 store.setJWT = function (data) { this.JWT = data; }; loginButton.addEventListener('submit', async (e) => { e.preventDefault(); const res = await fetch('/authenticate.php', { method: 'POST', headers: { 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: JSON.stringify({ username: form.inputEmail.value, password: form.inputPassword.value }) }); if (res.status >= 200 && res.status < 300) { const jwt = await res.text(); store.setJWT(jwt); frmLogin.style.display = 'none'; btnGetResource.style.display = 'block'; } else { // 处理错误 console.log(res.status, res.statusText); } });
請注意,默認情況下,Apache不會將HTTP_AUTHORIZATION標頭傳遞給PHP。其背後的原因是:
基本授權標頭只有在您的連接通過HTTPS完成時才安全,因為否則憑據將以編碼的明文(未加密)形式通過網絡發送,這是一個巨大的安全問題。
我完全理解這一決定的邏輯。但是,為了避免很多混淆,請將以下內容添加到您的Apache配置中。然後代碼將按預期工作。如果您使用的是NGINX,則代碼應該按預期工作:
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
接下來,我們嘗試提取匹配的JWT,它將位於$matches變量的第二個元素中。如果不可用,則沒有提取JWT,並返回HTTP 400錯誤請求:
<code>{ "alg": "HS256", "typ": "JWT" }</code>
如果我們到達此點,則已提取JWT,因此我們轉到解碼和驗證階段。為此,我們再次需要我們的密鑰,它將從環境或應用程序的配置中提取。然後,我們使用php-jwt的靜態decode方法,將JWT、密鑰和一組用於解碼JWT的算法傳遞給它。
如果能夠成功解碼,我們就會嘗試驗證它。我這裡的示例非常簡單,因為它只使用頒發者、不早於和過期時間戳。在實際應用程序中,您可能還會使用許多其他聲明。
<code>{ "iat": 1416929109, "jti": "aa7f8d0a95c", "scopes": [ "repo", "public_repo" ] }</code>
如果令牌無效,例如令牌已過期,則用戶將收到HTTP 401未授權標頭,並且腳本將退出。
如果解碼和驗證過程失敗,則可能是:
- 提供的段數與前面描述的標準三個段數不匹配。
- 標頭或有效負載不是有效的JSON字符串。
- 簽名無效,這意味著數據已被篡改!
- JWT中設置了nbf聲明,其時間戳小於當前時間戳。
- JWT中設置了iat聲明,其時間戳小於當前時間戳。
- JWT中設置了exp聲明,其時間戳大於當前時間戳。
如您所見,JWT具有一套不錯的控制措施,無需手動撤銷或針對有效令牌列表進行檢查即可將其標記為無效。
如果解碼和驗證過程成功,則用戶將被允許發出請求,並將收到相應的響應。
總結
這是一個關於JSON Web Tokens(或JWT)以及如何在基於PHP的應用程序中使用它們的快速介紹。從這裡開始,您可以嘗試在下一個API中實現JWT,也許嘗試一些使用非對稱密鑰(如RS256)的其他簽名算法,或者將其集成到現有的OAUTH2身份驗證服務器中以用作API密鑰。
如果您有任何意見或問題,請隨時通過Twitter與我們聯繫。
關於使用JWT進行PHP授權的常見問題解答
我們可以在PHP中使用JWT嗎?
您確實可以在PHP中使用JWT來在Web應用程序中建立身份驗證和授權機制。要開始使用,您需要使用Composer安裝PHP JWT庫,例如“firebase/php-jwt”或“lcobucci/jwt”。這些庫提供了創建、編碼、解碼和驗證JWT的必要工具。 要創建JWT,您可以使用庫來構建包含發行者、受眾、過期時間等聲明的令牌。創建後,您可以使用密鑰簽署令牌。接收JWT時,您可以使用庫對其進行解碼和驗證以確保其真實性。如果令牌有效且已驗證,您可以訪問其聲明以確定用戶身份和權限,從而允許您在PHP應用程序中實現安全的身份驗證和授權。 保護密鑰並遵循使用JWT時的安全最佳實踐對於防止未經授權訪問應用程序資源至關重要。
PHP中的JWT身份驗證是什麼?
PHP中的JWT(JSON Web Token)身份驗證是一種廣泛用於在Web應用程序中實現用戶身份驗證和授權的方法。它基於令牌的身份驗證,能夠進行安全且無狀態的用戶驗證。以下是JWT身份驗證在PHP中的工作方式: 首先,在用戶身份驗證或登錄期間,服務器會生成一個JWT,這是一個緊湊的、自包含的令牌,其中包含與用戶相關的信息(聲明),例如用戶ID、用戶名和角色。這些聲明通常是JSON數據。然後,服務器使用密鑰簽署此令牌以確保其完整性和真實性。 其次,成功進行身份驗證後,服務器會將JWT發送回客戶端,客戶端通常將其存儲在安全位置,例如HTTP cookie或本地存儲。此令牌作為身份驗證的證明。 最後,對於對服務器上受保護資源的後續請求,客戶端會在請求標頭中附加JWT,通常使用帶有“Bearer”方案的“Authorization”標頭。服務器收到JWT後,會使用在令牌創建期間使用的相同密鑰來驗證其簽名。此外,它還會檢查令牌是否未過期且包含有效的聲明。成功驗證後,它會從令牌聲明中提取用戶信息,並實現授權邏輯以確保用戶具有訪問請求資源所需的權限。這種方法允許在PHP應用程序中進行安全、無狀態的身份驗證,而無需服務器端會話存儲。 雖然PHP中的JWT身份驗證提供了許多好處,例如可擴展性和無狀態性,但保護密鑰並採用令牌管理的最佳實踐對於維護應用程序的安全至關重要。使用已建立的PHP JWT庫可以簡化令牌處理並增強安全性。
PHP中JWT的替代方案是什麼?
PHP中用於身份驗證和授權的JWT(JSON Web Tokens)的替代方案是基於會話的身份驗證。在基於會話的身份驗證中,服務器為每個已驗證的用戶維護一個會話。當用戶登錄時,會創建一個唯一的會話標識符(通常存儲為客戶端瀏覽器上的會話cookie)。此標識符用於將用戶與服務器端會話數據關聯起來,包括與用戶相關的信息,例如用戶ID、用戶名和權限。 基於會話的身份驗證提供簡單性和易於實現,使其適用於各種Web應用程序,尤其是在您不需要JWT的無狀態性和可擴展性功能時。它本質上是有狀態的,當您需要在用戶會話期間管理特定於用戶的數據(例如購物車內容或用戶首選項)時,這可能是有利的。 但是,使用基於會話的身份驗證時,需要考慮一些事項。對於需要無狀態身份驗證的分佈式或基於微服務的架構,它可能不太適用。此外,管理用戶會話可能會增加服務器負載,尤其是在用戶群較大的應用程序中。最終,在PHP中選擇JWT和基於會話的身份驗證應與應用程序的特定需求和設計考慮因素相符,確保安全有效的身份驗證機制最能滿足您的需求。
如何使用JWT保護PHP API?
使用JWT(JSON Web Tokens)保護PHP API涉及一個多步驟過程,該過程結合了身份驗證和授權。首先,選擇合適的PHP JWT庫(如“firebase/php-jwt”或“lcobucci/jwt”)來處理與令牌相關的操作,並使用Composer管理依賴項。 對於身份驗證,您需要在PHP應用程序中實現用戶身份驗證系統。此系統會針對您的數據庫或身份驗證提供程序驗證用戶憑據。成功進行身份驗證後,您將生成一個JWT令牌,其中包含與用戶相關的聲明,例如用戶的ID、用戶名和角色。務必設置過期時間以控制令牌的有效性,然後使用密鑰簽署令牌。此已簽名的令牌作為身份驗證響應的一部分發送回客戶端。 客戶端安全地存儲收到的JWT,通常存儲在HTTP cookie或本地存儲中。對於後續的API請求,客戶端會在請求標頭中包含JWT,作為帶有“Bearer”方案的“Authorization”標頭。在您的PHP API中,您可以通過使用創建令牌時使用的相同密鑰來驗證其簽名來驗證傳入的JWT。此外,您還會檢查令牌是否未過期且包含有效的聲明。成功驗證後,您將從令牌聲明中提取用戶信息,並實現授權邏輯以確保用戶具有訪問請求資源所需的權限。 保持密鑰安全至關重要,因為它對於簽署和驗證令牌都至關重要。此密鑰的任何洩露都可能導致嚴重的安全性漏洞。
以上是JWT(JSON Web令牌)的PHP授權的詳細內容。更多資訊請關注PHP中文網其他相關文章!

PHPSession失效的原因包括配置錯誤、Cookie問題和Session過期。 1.配置錯誤:檢查並設置正確的session.save_path。 2.Cookie問題:確保Cookie設置正確。 3.Session過期:調整session.gc_maxlifetime值以延長會話時間。

在PHP中調試會話問題的方法包括:1.檢查會話是否正確啟動;2.驗證會話ID的傳遞;3.檢查會話數據的存儲和讀取;4.查看服務器配置。通過輸出會話ID和數據、查看會話文件內容等方法,可以有效診斷和解決會話相關的問題。

多次調用session_start()會導致警告信息和可能的數據覆蓋。 1)PHP會發出警告,提示session已啟動。 2)可能導致session數據意外覆蓋。 3)使用session_status()檢查session狀態,避免重複調用。

在PHP中配置會話生命週期可以通過設置session.gc_maxlifetime和session.cookie_lifetime來實現。 1)session.gc_maxlifetime控制服務器端會話數據的存活時間,2)session.cookie_lifetime控制客戶端cookie的生命週期,設置為0時cookie在瀏覽器關閉時過期。

使用數據庫存儲會話的主要優勢包括持久性、可擴展性和安全性。 1.持久性:即使服務器重啟,會話數據也能保持不變。 2.可擴展性:適用於分佈式系統,確保會話數據在多服務器間同步。 3.安全性:數據庫提供加密存儲,保護敏感信息。

在PHP中實現自定義會話處理可以通過實現SessionHandlerInterface接口來完成。具體步驟包括:1)創建實現SessionHandlerInterface的類,如CustomSessionHandler;2)重寫接口中的方法(如open,close,read,write,destroy,gc)來定義會話數據的生命週期和存儲方式;3)在PHP腳本中註冊自定義會話處理器並啟動會話。這樣可以將數據存儲在MySQL、Redis等介質中,提升性能、安全性和可擴展性。

SessionID是網絡應用程序中用來跟踪用戶會話狀態的機制。 1.它是一個隨機生成的字符串,用於在用戶與服務器之間的多次交互中保持用戶的身份信息。 2.服務器生成並通過cookie或URL參數發送給客戶端,幫助在用戶的多次請求中識別和關聯這些請求。 3.生成通常使用隨機算法保證唯一性和不可預測性。 4.在實際開發中,可以使用內存數據庫如Redis來存儲session數據,提升性能和安全性。

在無狀態環境如API中管理會話可以通過使用JWT或cookies來實現。 1.JWT適合無狀態和可擴展性,但大數據時體積大。 2.Cookies更傳統且易實現,但需謹慎配置以確保安全性。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

WebStorm Mac版
好用的JavaScript開發工具

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Linux新版
SublimeText3 Linux最新版

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)