本文原本是web安全基礎的一部分,作為安全的前置知識學習,但隨著學習進程的不斷深入,原有的前端的體系需要進一步擴充,已經到了可以獨立成章的地步,故將其拿出來單獨學習。
B/S工作原理
也就是瀏覽器與服務器的交互原理,網絡上通過應用層協議HTTP實現,通信的大致流程為瀏覽器客戶端向服務器請求資源,服務器解析并響應請求,資源發送給瀏覽器后由瀏覽器進行解析。
有關詳細傳輸協議及用戶身份驗證機制如下:
HTTP超文本傳輸協議
一般網站可分為三層架構:
界面層:UI即user interface,提供用戶交互接口,如網頁上的輸入框和展示區域;
業務邏輯層:負責進行邏輯判斷和處理,如輸入密碼時判斷輸入框是否為空,該步還僅在頁面內進行,無需消息傳遞;
數據訪問層:該層負責將客戶端傳來的請求在數據庫中進行查找,該層涉及整個數據庫及后臺系統,安全問題也多出現在該層。
http
超文本傳輸協議是客戶端(瀏覽器)和服務器(網站)通信的基礎,該協議主要支持TCP
,是一種無狀態的連接方式,早期協議請求一次響應一次直接斷開,如今1.1版本會保持連接幾秒鐘,等待用戶進一步操作請求,減少短時間內重復建立連接的開銷,詳細介紹可見HTTP超級詳解,大致原理是客戶端通過瀏覽器向服務器發送請求,傳參或請求訪問服務器上指定的資源,服務器響應請求,將內容回傳給客戶端,如果是html
則由瀏覽器負責解析顯示,大致流程圖如下:
其中get
和post
請求方式的主要區別在于安全性上,get請求直接將參數使用?
附在url
傳遞給服務器,方便但很不安全,post則是將數據封裝到消息體中傳遞。
cookie
cookie
是方便用戶和網站進行身份驗證的一小塊數據,其誕生之初的核心目的是解決HTTP協議的無狀態性帶來的會話管理問題。比如我們登陸過一個網站后,后續再登陸就可以免去驗證過程,這就是cookie
替我們完成了驗證工作。就形式而言,cookie
存儲的是鍵值對,訪問時將該鍵值對存于請求頭發送給服務端,服務端驗證后實現自動登陸等功能,詳細內容可見cookie是什么。
HTTP協議設計為無狀態,若每次請求都攜帶完整的用戶信息(如登錄憑證、偏好設置),服務器需重復解析大量冗余數據。通過Cookie的鍵值對標識符,服務器只需存儲少量關鍵信息(如Session ID
),其余數據由客戶端管理,可大幅減少服務端資源消耗。
session
session
是服務端給用戶分配用于區分用戶的唯一標識:sessionid
,更新請求時cookie
將新請求與sessionid
一起傳給服務器端,服務器根據該sessionid
找到用戶信息uid
后加上更新的請求,就避免了所有全部用戶信息都存在cookie
的負擔,但cookie
+session
的方式在實際運行時也有很大問題。
生產環境的服務器往往是多臺機器,通過負載均衡如Nginx
來決定請求到底落在哪臺機器上,機器A生成的session
機器B和C是不知道的,這種情況就會出現無法更新數據的情況,為了解決這種問題,目前有三種方法:
1,session
復制,A將session
復制到B和C上,這種方法增加了數據冗余;
2,session
粘連,通過協議,如Nginx
的sticky
模塊讓客戶端的每次請求只到一臺機器上,但該機器的可用性不能一直保證;
3,session
共享,這種方式也是目前各大公司普遍采用的方案,將 session 保存在 redis
(非關系型數據庫,鍵值對存儲系統),memcached
等中間件中,請求到來時,各個機器去這些中間件取一下 session 即可。該方案的不足在于每次請求都需要和中間件交互,消耗了一部分性能,而且為了保證redis
的可用,還需要作冗余備份,搞個集群。
token
對于大公司來說redis
集群本來也是要搞的,但對小公司來說確實有些奢侈,所以有了不用session
的身份驗證機制——token
。
token
是服務器端根據用戶提交的用戶名和密碼使用簽名算法計算得到的字符串,客戶端將token
發送給客戶端后,客戶端根據序列中的header
獲取簽名算法,payload
獲取userid
,使用將使用該算法計算得到的簽名與token
中的內容進行比較驗證,免去了從redis
中獲取的開銷。
更詳細直觀的講解可見講透token與Cookie和Session區別,首先解答文中沒有提到的一個問題:為什么token里就能直接包含uid,而sessionid還要去中間件查詢uid呢?
這是token
簽名機制決定的,uid
如果伴隨session
直接發送,首先服務器沒法驗證是否被篡改,攻擊者可輕易偽造其他用戶,而token
的簽名算法則有效解決該問題,框架對比AI生成的如下可作參考:
安全性分析,cookie
易被跨站腳本XSS
劫持,未設置HttpOnly
的可被JavaScript直接讀取;且因為瀏覽器發報自動攜帶cookie
的特性,惡意鏈接釣魚網站(CSRF攻擊)等可直接獲取cookie
,相比cookie
的長期存儲,token
多短時有效,且需要開發者手動添加,從源頭來說相對安全。但二者都是本地存儲,就存儲階段都有不安全性,而傳輸過程中token
也只能防止CSRF
的重放攻擊,所以他們本質上都沒有區別,驗證上來講只不過session
存放于客戶端和服務器端,而token
只存放于客戶端,并且由于token
的短期有效性和機制上,token
更適合一次登陸驗證的情景,session
更適合常規通信場景。
web服務器
超文本傳輸協議是瀏覽器通信的網絡基礎,客戶端使用瀏覽器解析接收到的html
文件,那客戶端如何根據url
確定要發送的文件呢?實現資源與url的對應并傳輸就是web服務器的主要功能,目前主流的服務器有nginx
、apache
,還有用于開發者自測的輕量級服務器USBWebserver
以及適配Java生態,專門用于動態程序編寫的Tomcat。
除了發送文件以外,目前的服務器還集成了很多其他功能,如動態資源處理(把請求轉發給后端處理程序),錯誤處理(返回404等錯誤頁面),反向代理(將請求發給其他地址),重定向(修改url)和負載均衡(將請求分發到多個服務器)等。
USBWebServer
通過USBWebServer官方下載,官網對該工具的介紹如下:
即一款本地web服務器,集成php語言、apache網絡服務器(負責監聽80端口,處理get、post請求等)和Mysql數據庫。
要注意的是該軟件解壓目錄中不能含中文,解壓到桌面的用戶名也不能有中文。
左側選項卡依次代表網頁地址、網頁綁定端口localhost
和數據庫管理界面PHPMyAdmin
。
點擊localhost
可進入本機8080端口,默認界面如下:
借助該工具可實現網頁本地編寫,本地部署,本地查看,在正式上線前幾乎完全模擬真實環境,可移植性強,目錄如下:
網頁內容在root
目錄下,使用vscode
打開該目錄可見如下頁面:
使用需安裝如下插件:
可使用如下代碼替換文件理解映射關系:
<!DOCTYPE html>
<html>
<body>
<?php
echo "Hello World!";
?>
</body>
</html>
替換后界面如圖:
vscode
中可使用!
快速生成html
框架:
選擇第一個!
后自動生成如下代碼:
<!DOCTYPE html>
<html lang="en"> <!--告知瀏覽器本文件的主要語言,方便搜索引擎優化-->
<head> <!--網頁頭部--><meta charset="UTF-8"> <!--編碼格式字符集--><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> <!--標題,顯示在網頁標簽上-->
</head>
<body><!--網頁內容-->
</body>
</html>
通過如下簡單示例可生成登錄框界面:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>這是一個網頁</title><style> /*CSS樣式,用于美化網頁*/.box {position: absolute; /* 絕對定位,相對于瀏覽器窗口進行定位 */left: 50%;top: 50%;transform: translate(-50%, -50%);text-align: center; /* 文本居中顯示 */border: 1px solid black; /* 邊框樣式 */padding: 20px 50px;}input[type="text"], input[type="password"] {display: block; /* 塊級顯示元素,每個框占一行 */text-align: center;margin: 10px auto;}input[type="submit"] {display: inline-block; /* 內聯塊級顯示元素,可以和其他內聯元素在一行中 */margin: 10px 5px;}.button-group {margin-top: 20px;text-align: center;}</style>
</head>
<body></body><div class="box"> <!-- 定義類名分塊,方便CSS管理--><h1>歡迎登錄</h1> <!-- 定義標題標簽 --><form action="" method="post"> <!-- 定義表單標簽,表單提交到哪里,“”表示提交到當前url,用什么方法提交 --><input type="text" name="username" placeholder="用戶名"><!-- type設置密碼不可見 --><input type="password" name="password" placeholder="密碼"><!-- submit提交表單,兩個按鈕功能暫時一致 --><input type="submit" name="register" value="注冊"></form></div>
</html>
生成界面如圖:
Nginx介紹
USBWebServer
是簡單自測版的服務器,Nginx
則是大型生產環境下可用的代理服務器,該系統由俄羅斯站點開發,該系統可完成負載均衡,動靜分離,正向代理和反向代理等十四個功能:
正向代理指給瀏覽器配置代理服務器,即瀏覽器請求經過代理服務器轉發請求;
反向代理指訪問節點內部多臺物理機僅需一次登陸訪問所有,相關基礎知識可見Nginx詳解。
該系統性能優異,占內存少,并發性高,目前國內大廠如BAT等在web端都使用Nginx
。
由于Linux系統更穩定,web服務器也多部署在Linux上,本文也嘗試在Linux上部署Nginx
。
以下操作借鑒如何在Ubuntu上部署使用nginx:
sudo apt update
sudo apt upgrade # 更新軟件包,避免版本呢問題
sudo apt install -y curl gnupg2 ca-certificates lsb-release # 安裝前置包
sudo apt install -y nginx # 安裝nginx
sudo systemctl start nginx # 啟動nginx
sudo systemctl enable nginx # 設置開機自啟動
此時Nginx
已在本機運行,瀏覽器欄中輸入本機網址可見如下界面:
后續要完成的是如何替換該界面,如何讓我們的html
文件與域名和IP進行綁定,我們首先準備一個index.html
文件代碼如下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body>pages loaded by nginx
</body>
</html>
將其放到用戶目錄下/home/kevin/testweb
,隨后修改nginx
配置文件,使用如下命令實現:
sudo nano /etc/nginx/nginx.conf # nano編輯器打開nginx主配置文件
http{
...
} # http模塊內增加如下語句
server {listen 80;server_name 192.168.145.129; # 綁定IPlocation / {root /home/kevin/testweb; # 文件目錄index index.html; # 訪問IP默認展示的文件}
}
sudo nginx -t # 檢查配置文件語法問題,如果輸出ok和successful說明修改沒有語法錯誤
sudo systemctl restart nginx # 重啟nginx服務
此時用IP訪問會出現403forbidden,使用sudo tail -f /var/log/nginx/error.log
#查看日志錯誤信息出現如下權限不足錯誤:
網上很多資料到這都是順利訪問的,最后找到了解決方案,先排查防火墻和文件資源不存在兩種情況,最后從報錯推斷可能nginx用自己的用戶訪問,但目標目錄沒有給足夠的權限,使用ps -aux | grep nginx
查看進程可見下圖:
可見master
進程由root
用戶創建,而work
進程由www-data
用戶執行,該用戶并非我們創建而是nginx
默認,可能有權限問題,這也是出于安全性考慮,防止攻擊者拿到nginx
管理員權限就控制了我們的機器,要解決該問題很簡單,sudo nano /etc/nginx/nginx.conf
進入主配置文件可見其執行用戶如圖:
將其改為root
并重啟服務即可訪問。
但這樣也失去其新用戶的意義了,攻擊者拿下web管理員也直接獲得了整個系統的控制權,常規操作應該是把文件目錄權限賦予nginx
用戶。
修改權限有兩種方法:
1,使用chown 選項 屬主:屬組 文件名
修改屬主和數組-R
遞歸進行,再使用chmod 選項 對象+權限 文件名
修改權限,該方法效果可直接通過ls -l
查看,比如修改后的文件權限為-rwxr-xr-x 1 www-data www-data 227 4月 11 15:10 index.html
,這中方法不靈活,有時我們并不想修改屬主屬組,只要給特定用戶增加權限即可。
2,setfacl -m u:用戶名:權限 文件或目錄名
給特定用戶增加權限,該方法增加權限后ls -l
查看后續會有+
號,如drwxr-xr-x+ 18 kevin kevin 4096 4月 12 19:20 kevin
。
本次網頁目錄在/home/kevin/testweb
下,已嘗試對三層目錄分別賦予www-data
以rwx
權限,但網站訪問仍為403,這種方法也很不安全,都賦予讀寫權限還跟root
有什么區別,按理是應該放在根目錄下單獨的文件,轉而修改為/testweb
下,重啟服務后不用修改權限就能成功訪問,暫時不清楚具體原理,修改后的conf
文件如圖:
前端三大件
前端核心功能分別由html、css和Javascript語言實現,其中html負責搭建網頁的結構骨架,css負責完成網頁的視覺表現,JavaScript負責網頁的行為交互,其中html和css主要是對規則的了解和熟悉,JavaScript看似上手很容易,和其他語言沒什么差別,甚至還是比較容易的一種,但要熟練掌握還是需要一些功夫的。
html超文本標記語言
html是描述網頁的標記語言,1991年誕生,95年更新到標準化的2.0,97年推出的4.01稱為w3c推薦標準,2014年發布HTML5,該版本強調結構與語義,新增了多媒體和離線存儲,實現動態發展,持續發展的規范,詳細文檔可見w3cschool。
常用標簽及解釋通過代碼解釋如下:
<!DOCTYPE html> //聲明文檔類型
<html lang="en"> // 根元素,html標簽,lang屬性定義語言
<head> // 頭部元素,包含了文檔的元(meta)數據<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> // 文檔標題
</head>
<body> // 文檔的主體部分,包含了可見的頁面內容<h1>Hello, World!</h1> // 標題元素,表示最高等級的標題<p>This is a paragraph.</p> // 段落元素,表示文本塊<div id="div"></div> // 容器元素,無實意,與CSS布局相關// 屬性可通過鍵值對表示,大小寫不敏感,值在引號內
</body>
</html>
常見元素有:
占據整行的塊級元素如div
、p
等前后自動換行的元素;
行內元素如span
、a
超文本鏈接、em
和img
圖像;
列表元素如ul
、li
和ol;
以及表格table
、tr
、td
上述介紹的標簽如div
、span
等都是無語義的標簽,不方便維護可讀性差,為了解決該問題推出的html5可使用語義化標簽,也方便瀏覽器根據語義解析網頁,如header
、main
、footer
,類似函數化的設置,將特定功能放到特定標簽中。
結構化標簽有nav
導航欄,section
章節,asid
側邊欄,內容描述的figure
,交互性的details
和summary
等,詳細可見html語義化標簽。
改寫后的html代碼如下:
<!DOCTYPE html>
<html lang="en">
<header><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</header>
<body><nav><section><h1>section start</h1><header>this is a header</header></section></nav><main><article>this is the article</article></main><footer>this is a footer</footer>
</body>
</html>
交互行為使用表單實現,該部分負責完成網頁的數據提交、驗證操作,基礎操作同樣直接通過代碼展示:
<form action="index.php" method="post">
<!-- 提交表單, action為信息傳入url并自動跳轉,缺省為本頁面,method為發送信息的方法get或post--><label for="name">input name:</label><input type="text" name="name" ><!-- 輸入框為文本格式,也可設置為密碼掩碼password,郵箱email等,require設置強制必填--><button type="submit">提交</button>
</form>
index.php
中
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {// 獲取提交的 name 值$name = $_POST['name'];echo "Received name: " ,$name;
}
?>
其中按鍵類型可設置為submit
提交和reset
重置,表單事件也可設置為onblur
失去焦點或onchange
數值更改等,
CSS層疊樣式表
該語法描述了如何顯示html元素,層疊表示允許多個規則應用到一個元素,所以前端工作雖然較簡單,但不同規則的應用和疊加可能使調試過程變得十分復雜。
css語法由選擇器和聲明塊構成,如p { color: red; text-align: center; }
會將所有元素都設置為居中紅色,這些規則都單獨寫在<style></style>
標簽內。
選擇器有以下幾種:
簡單選擇器:通過標簽名,id或類名選擇,id選擇器使用#id
,類選擇器使用.class
,通用選擇器*
選擇所有元素,多個相同規則的元素可以寫在一起,如h1, h2, p { text-align: center; color: red; }
。
組合選擇器:根據特定關系選擇。空格
表示后代,>
表示子選擇,+
相鄰兄弟選擇器,~
通用兄弟選擇器,如div + p { background-color: yellow; }
,兄弟(同級)元素必須具有相同的父元素,“相鄰”的意思是“緊隨其后”,該的例子選擇緊隨 div
元素之后的所有p
元素。
偽類選擇器:用于特殊狀態的選擇,比如鼠標懸停和點擊獲取焦點等狀態。使用方法為:特殊狀態
,如懸停時更改顏色div:hover { background-color: blue; }
。
偽元素選擇器:用于設置元素指定部分的樣式,如首字母,首行,使用方法為::特殊元素
,如首行設置p::first-line { color: #ff0000; font-variant: small-caps; }
。
添加CSS有三種方法:
外部CSS:通過導入外部寫好的CSS文件設置樣式,方法為在head
中設置<link rel="stylesheet" type="text/css" href="mystyle.css">
,
內部CSS:如果頁面樣式唯一,可用內部CSS。樣式在head
中的<style>
元素內定義。
行內CSS:也成內聯樣式,用于為單個元素應用唯一的樣式,將style
屬性添加到單個元素,如<h1 style="color:blue;text-align:center;">This is a heading</h1>
。
層疊順序為:
1,行內樣式
2,內部樣式
3,外部樣式
4,默認樣式
也就是離元素越近的優先級越高。
剩下的就是一些雜項,顏色設置可使用名稱、RGB或HEX等格式指定,詳細設置可見CSS顏色,背景圖像設置默認重復覆蓋整個元素,通過background-color
指定,詳細可見CSS背景,邊框通過border
屬性指定,可指定樣式為點框dotted
,虛線dashed
或實線solid
,詳細可見CSS邊框,這些用的時候到文檔中查一下,用的多也就記住了。
此外還有外邊距內邊距,需要注意的是邊距合并問題,二者邊距由其中最大者決定,比如兩個組件分別設置外邊距為10px和20px,這兩個組件布局相鄰時邊距就會變為20px。
以上內外邊距、高度寬度等可以統一使用盒子模型設置,還有對其方式text-align:
、字體設置等,盒子設置的示例代碼如下:
div {background-color: lightgrey;width: 300px;border: 25px solid green;padding: 25px;margin: 25px;
}
position
定位屬性指定了元素的定位類型,其屬性值及含義如下:
static 默認值,不受top, bottom, left, right影響,以頁面正常流定位
relative 相對其正常位置變化,接受top, bottom, left, right設置
fixed 相對窗口的固定位置,滾動頁面其相對位置不變
absolute 絕對定位,相對最近已定位的父級元素
sticky 根據滾動位置定位,滾動超出區域時固定在特定位置,先相對再絕對,類似網頁的側邊欄廣告
最后是display
布局屬性,這是CSS控制布局的最重要的屬性。通過{display:inline}
設置為行內元素,不從新行開始,僅占用該元素所需寬度,屬于該分類的元素有span
、a
、img
;block
設置為塊級元素,從新的一行開始,屬于該分類的元素有div
、h
、p
、form
以及語義元素。
設置隱蔽元素可使用none
屬性值或設置visibility:hidden
實現,使用后一種方法元素仍然占據空間。
JavaScript基本語法
JavaScript
是web的編程語言,主要用于實現網頁的動態變化,使用<script> </script>
標記語言,詳細內容可見菜鳥教程和MDN指南,大致使用方法簡單介紹如下:
var
聲明變量,該語言弱類型,無需聲明變量類型,只區分常量const
和變量let
,var
與let
只有作用域的區別,js中一個{}
為一個域,var
可提升作用域。
數字只有number
64位雙精度浮點數,使用時可通過typeof 變量名
查看數據類型;
數組使用var a =[]
,長度可直接用a.length=n
,多余位置的元素默認為空;
分支if
和switch
,循環while
和do while
;
函數使用function 函數名(參數){函數體}
聲明;
面向對象使用var 對象={成員:值 方法名:function(){方法體}}
實現,屬性創建后默認為undefined
而非null
,工程中常用的定義方法為name= "" age="" var p={name,age}
。
一些常用語法如:
window.alert()
彈出警告框;
console.log()
寫入瀏覽器控制臺;
innerHTML
操作HTML元素。
JavaScript
語言整體由三部分組成:ECMAScript
語法,DOM
頁面文檔對象模型和BOM
瀏覽器對象模型。
ECMAScript
語法規定瀏覽器如何運行JavaScript
腳本;
DOM
頁面文檔對象模型則是面向web
提供了腳本語言和頁面組件交互的方法;
BOM
瀏覽器對象模型則是給腳本語言直接控制瀏覽器提供了接口。
詳細解釋可見徹底搞懂DOM和BOM。
php基本語法
php
語言是一種廣泛使用的開源服務器端腳本語言,尤其適用于Web開發。該語言可嵌入在html
中編寫,用<?php 語句 ?>
標記,解釋型語言,該部分代碼在服務器端執行,結果返回到html
上,可實現頁面的動態交互,開發效率高,但也因為這些特點導致其更適合用于中小型web開發,代碼與html
混合難以維護,解釋執行效率底下,相比Spring
和Django
生態不夠完善,大型項目還是多使用Java。
該語言只作簡單了解和介紹,更多內容可見php語言基礎知識。
php語言定義變量使用$變量名=值
的形式,弱類型,全局變量用static
修飾;使用function 函數名(參數){函數體}
的形式定義函數;echo 字符串
表示在終端中輸出字符串,常用于測試,if
判斷與C++幾乎完全一致,常結合isset
判斷值是否設置和$_REQUEST[組件名稱]
接受指定組件名的值,簡單示例代碼如下:
<?php
if(isset($_REQUEST['username']) && isset($_REQUEST['password'])){$username = $_REQUEST['username'];$password = $_REQUEST['password'];if($username == '張三' && $password == '123456'){echo '登錄成功';echo '<meta http-equiv="refresh" content="0;url=main.php">';}else{echo '登錄失敗';}
}
?>
增加后在瀏覽器中查看源代碼如下,可見php代碼只在后端執行,前端不可見:
php
連接數據庫,并在數據庫查詢驗證登陸有效性可使用如下代碼實現:
<?php
$dbhost = "數據庫地址,測試多為127.0.0.1";
$dbuser = "用戶名";
$dbpass = "密碼";
$db = "數據庫名";
// 獲取數據庫連接信息
$conn = mysqli_connect($dbhost, $dbuser, $dbpass) or exit("數據庫連接失敗!");
// 修改默認數據庫
@mysqli_select_db($conn, $db);
// 設置本次連接使用字符集為utf8
mysqli_query($conn, "set names utf8");if (isset($_POST["login"])) {
// 檢測按鍵事件,登錄邏輯處理$username = $_POST["username"];$password = $_POST["password"];$sql = "select * from users where username='$username' and password='$password'";$result = mysqli_query($conn, $sql);if (mysqli_num_rows($result) > 0) {echo "<script>alert('登錄成功')</script>";} else {echo "<script>alert('用戶名或密碼錯誤!');</script>";}
}
if (isset($_REQUEST['name'])) {echo $_REQUEST['name'];
}
?>
使用session
記錄會話使用如下代碼實現:
session_start();
if (mysqli_num_rows($result) > 0) {// 登陸成功記錄會話$_SESSION["username"] = $username;echo "<script>alert('登錄成功')</script>";}
// 需要驗證登陸狀態的頁面前加上下方判斷代碼
if(isset($_SESSION["username"]))
phpinfo()
函數可輸出php相關信息。