前端基础——B/S工作原理、服务器与前端三大件

本文原本是web安全基础的一部分,作为安全的前置知识学习,但随着学习进程的不断深入,原有的前端的体系需要进一步扩充,已经到了可以独立成章的地步,故将其拿出来单独学习。

B/S工作原理

也就是浏览器与服务器的交互原理,网络上通过应用层协议HTTP实现,通信的大致流程为浏览器客户端向服务器请求资源,服务器解析并响应请求,资源发送给浏览器后由浏览器进行解析。

有关详细传输协议及用户身份验证机制如下:

HTTP超文本传输协议

一般网站可分为三层架构:
界面层 :UI即user interface,提供用户交互接口,如网页上的输入框和展示区域;
业务逻辑层 :负责进行逻辑判断和处理,如输入密码时判断输入框是否为空,该步还仅在页面内进行,无需消息传递;
数据访问层:该层负责将客户端传来的请求在数据库中进行查找,该层涉及整个数据库及后台系统,安全问题也多出现在该层。

http超文本传输协议是客户端(浏览器)和服务器(网站)通信的基础,该协议主要支持TCP,是一种无状态 的连接方式,早期协议请求一次响应一次直接断开,如今1.1版本会保持连接几秒钟,等待用户进一步操作请求,减少短时间内重复建立连接的开销,详细介绍可见HTTP超级详解,大致原理是客户端通过浏览器向服务器发送请求,传参或请求访问服务器上指定的资源,服务器响应请求,将内容回传给客户端,如果是html则由浏览器负责解析显示,大致流程图如下:

其中getpost请求方式的主要区别在于安全性上,get请求直接将参数使用?附在url传递给服务器,方便但很不安全,post则是将数据封装到消息体中传递。

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粘连,通过协议,如Nginxsticky模块让客户端的每次请求只到一台机器上,但该机器的可用性不能一直保证;

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服务器的主要功能,目前主流的服务器有nginxapache,还有用于开发者自测的轻量级服务器USBWebserver以及适配Java生态,专门用于动态程序编写的Tomcat。

除了发送文件以外,目前的服务器还集成了很多其他功能,如动态资源处理(把请求转发给后端处理程序),错误处理(返回404等错误页面),反向代理(将请求发给其他地址),重定向(修改url)和负载均衡(将请求分发到多个服务器)等。

USBWebServer

通过USBWebServer官方下载,官网对该工具的介绍如下:

即一款本地web服务器,集成php语言apache网络服务器 (负责监听80端口,处理get、post请求等)和Mysql数据库

要注意的是该软件解压目录中不能含中文 ,解压到桌面的用户名也不能有中文。

左侧选项卡依次代表网页地址、网页绑定端口localhost和数据库管理界面PHPMyAdmin

点击localhost可进入本机8080端口,默认界面如下:

借助该工具可实现网页本地编写,本地部署,本地查看,在正式上线前几乎完全模拟真实环境,可移植性强,目录如下:

网页内容在root目录下,使用vscode打开该目录可见如下页面:

使用需安装如下插件:

可使用如下代码替换文件理解映射关系:

bash 复制代码
<!DOCTYPE html>
<html>
<body>
<?php
echo "Hello World!";
?>
</body>
</html>

替换后界面如图:

vscode中可使用!快速生成html框架:

选择第一个!后自动生成如下代码:

php 复制代码
<!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>

通过如下简单示例可生成登录框界面:

php 复制代码
<!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文件代码如下:

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; # 绑定IP
    location / {
        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-datarwx权限,但网站访问仍为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

常用标签及解释通过代码解释如下:

html 复制代码
<!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>

常见元素有:

占据整行的块级元素如divp等前后自动换行的元素;

行内元素如spana超文本链接、emimg图像;

列表元素如ulli和ol;

以及表格tabletrtd

上述介绍的标签如divspan等都是无语义的标签,不方便维护可读性差,为了解决该问题推出的html5可使用语义化标签,也方便浏览器根据语义解析网页,如headermainfooter,类似函数化的设置,将特定功能放到特定标签中。

结构化标签有nav导航栏,section章节,asid侧边栏,内容描述的figure,交互性的detailssummary等,详细可见html语义化标签

改写后的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>

交互行为使用表单实现,该部分负责完成网页的数据提交、验证操作,基础操作同样直接通过代码展示:

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 复制代码
<?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:、字体设置等,盒子设置的示例代码如下:

html 复制代码
div {
    background-color: lightgrey;
    width: 300px;
    border: 25px solid green;
    padding: 25px;
    margin: 25px;
}

position定位属性指定了元素的定位类型,其属性值及含义如下:

html 复制代码
static 		默认值,不受top, bottom, left, right影响,以页面正常流定位
relative 	相对其正常位置变化,接受top, bottom, left, right设置
fixed		相对窗口的固定位置,滚动页面其相对位置不变
absolute	绝对定位,相对最近已定位的父级元素
sticky		根据滚动位置定位,滚动超出区域时固定在特定位置,先相对再绝对,类似网页的侧边栏广告

最后是display布局属性 ,这是CSS控制布局的最重要的属性。通过{display:inline}设置为行内元素,不从新行开始,仅占用该元素所需宽度,属于该分类的元素有spanaimgblock设置为块级元素,从新的一行开始,属于该分类的元素有divhpform以及语义元素。

设置隐蔽元素可使用none属性值或设置visibility:hidden实现,使用后一种方法元素仍然占据空间。

JavaScript基本语法

JavaScript是web的编程语言,主要用于实现网页的动态变化,使用<script> </script>标记语言,详细内容可见菜鸟教程MDN指南,大致使用方法简单介绍如下:

var声明变量,该语言弱类型,无需声明变量类型,只区分常量const和变量letvarlet只有作用域的区别,js中一个{}为一个域,var可提升作用域。

数字只有number64位双精度浮点数,使用时可通过typeof 变量名查看数据类型;

数组使用var a =[],长度可直接用a.length=n,多余位置的元素默认为空;

分支ifswitch,循环whiledo 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混合难以维护,解释执行效率底下,相比SpringDjango生态不够完善,大型项目还是多使用Java。

该语言只作简单了解和介绍,更多内容可见php语言基础知识

php语言定义变量使用$变量名=值的形式,弱类型,全局变量用static修饰;使用function 函数名(参数){函数体}的形式定义函数;echo 字符串表示在终端中输出字符串,常用于测试,if判断与C++几乎完全一致,常结合isset判断值是否设置和$_REQUEST[组件名称]接受指定组件名的值,简单示例代码如下:

php 复制代码
<?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 复制代码
<?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记录会话使用如下代码实现:

php 复制代码
session_start();
if (mysqli_num_rows($result) > 0) {
		// 登陆成功记录会话
		$_SESSION["username"] = $username;
		echo "<script>alert('登录成功')</script>";
	}
// 需要验证登陆状态的页面前加上下方判断代码
if(isset($_SESSION["username"]))

phpinfo()函数可输出php相关信息。