【Linux 网络】应用层


应用层

协议

序列化的概念

结构化的数据不能直接进行传输,一般要转化成字符串再放到网络传输。

序列化本质就是将数据的使用和数据的传输进行解耦。方便应用层进行通信和使用数据

将结构内容转化成字符串,称为序列化。将字符串转化为结构数据,称为反序列化。

序列化过程

jsoncpp

# 安装jsoncpp
$ yum  install jsoncpp-devel        --Centos
$ apt  install libjsoncpp-dev		--Ubuntu

//样例代码
std::string serialize()
{
    Json::Value root;
    root["res"] = _res;
    root["st"] = _st;
    return Json::FastWriter().write(root);
}

void deserialize(const std::string& rsp)
{
    Json::Value root;
    Json::Reader reader;
    reader.parse(rsp, root);
    _res = root["res"].asInt();
    _st = root["st"].asInt();
}

 

1. HTTP

HTTP协议(超文本协议)是一种应用层协议,是用于传输文本、图片、视频等数据的协议,是明文传输的协议。

HTTP内部自行实现了网络通信、序列化和反序列化、协议细节。

1.1URL

URL就是”网址“,网络上可请求的图片、文字、视频、音频等内容都称为“资源”,网址就是一种定位网络资源的方式。

通过IP+端口+路径的方式,就可以唯一地定位一个网络资源

IP被域名代替,应用层协议所采用的端口号是确定的。由浏览器和DNS服务自动添加转换,用户感知不到。

url包含信息示例

URL编解码
/ : . ? & # @ + <space> ...

这些符号在URL中已具有特殊意义,所以它们作为普通字符时就必须转义。将字符的ASCII码值的16进制数字,再前面加上%,编码成%XY的格式。

在这里插入图片描述

URL转码工具

1.2 HTTP的格式

HTTP请求格式

请求格式

组成内容
请求行GET,POST等是请求方法,url是请求资源的路径,http_version是协议版本,以空格隔开
请求报头一般有多组请求属性,每组属性都是键值对,以\n分隔
空行用来隔开请求报头和请求正文
请求正文空行之后都是正文,允许为空。如果正文存在,报头中有Content-Length属性来表示正文长度

如何读取一个完整的报文呢?

  1. 读到空行的时候,说明获取所有报头内容。
  2. 解析报头Content-Length属性,确定正文大小,准确读取正文。
HTTP响应格式

响应格式

组成内容
状态行HTTP版本,状态码,状态码描述,以空格分隔
响应报头一般有多组响应属性,每组属性都是键值对,以\n分隔
空行用来隔开响应报头和响应正文
响应正文空行之后都是正文,允许为空。如果正文存在,报头中会有属性Content-Length来表示长度

1.3 HTTP的方法

方法说明支持协议版本
GET获取资源1.0 1.1
POST传输实体资源1.0 1.1
PUT传输文件1.0 1.1
HEAD获得报文首部1.0 1.1
DELETE删除文件1.0 1.1
OPTIONS询问支持的方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0
UNLINE断开连接关系1.0

一般为安全起见,都不开放除GET POST以外的其他方法。

GET/POST
名称场景区别隐私性
GET获取资源,也可提交参数通过URL提交,存在大小限制直接回显在URL栏中
POST常用于提交参数通过正文提交,长度不受限制不回显在URL栏中,私密但不是安全

常见的面试题:get和post的区别在哪里,分别有什么作用?

  • GET 将数据以查询字符串的形式附加在 URL 后面,数据在 URL 中可见,适合传输少量非敏感数据,但因暴露在 URL 中不安全;
  • POST 则将数据包含在请求的正文部分,不会暴露在 URL 中,适合传输大量或敏感数据,更安全。
  • GET 受 URL 长度限制约 2000 字符,而 POST 理论上没有长度限制,但服务器可能设置正文最大接受大小。

1.4 HTTP的状态码

HTTP协议状态码标准混乱不一。

状态码类别翻译解释常见状态码
1XXInformational信息性接收的请求正在处理不常见
2XXSuccess成功请求正常处理完毕200 (OK)
3XXRedirection重定向需要附加操作以完成请求302 (Redirect)
4XXClient Error客户端错误服务器无法处理请求403 (Forbidden) / 404 (Not Found)
5XXServer Error服务端错误服务器处理请求出错504 (Bad Gateway)
  • 404属于客户端错误,是客户端访问不存在的资源。浏览器不会处理404响应,需要服务器返回错误页面。
  • 服务端程序出现错误崩溃等问题,就是服务器错误,状态码应设置为504。
  • 3XX的状态码代表重定向,意思是自动跳转到其他页面。使用属性Location表示新地址。
    • 301是永久重定向,一般是网站域名更换,本质更改了浏览器本地标签。
    • 302/307是临时重定向,登录付款的自动返回,每次访问都会对新地址发起请求。

1.5 HTTP的报头

报头属性解释
Content-Type正文的数据类型 content-type对照表
Content-Length正文的长度
Connection请求的是否保持长连接
Host客户端告知服务器,所请求的服务程序的IP和端口
User-Agent声明用户的操作系统和浏览器版本
referer当前页面是从哪个页面跳转过来的
location搭配3XX状态码使用,告诉客户端接下来要去哪里访问
Cookie用于在客户端存储少量信息,通常用于实现会话(session)的功能

cookie和session

HTTP只负责网络资源传输,HTTP本身是一种无状态的协议,不会记录历史请求的相关信息。类似记录登录信息这样方便的上网体验是由Cookie提供的,也就是说,Cookie提供“会话保持”的功能。

cookie本质是一个保存用户的私密信息的文件

  1. 用户登录认证成功后,
  2. 浏览器根据响应属性中的Set-Cookie,将数据保存在本地cookie中,
  3. 后续对该网站的请求都会添加属性Cookie,所以有效期内就可以自动登录了。

Cookie不仅有自定义字段,还有如下属性:

属性解释
domain可以访问该Cookie的域名。
path设置某个目录下的程序或者单个程序可以使用该Cookie。
httponlyjs脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容。
secure是否仅用安全协议传输该Cookie。
expires指定cookie的生命周期。默认cookie只在会话期间存在。max-age用秒来设置cookie的生存期。

一旦Cookie盗取,就可以盗取用户信息。单纯使用Cookie是有安全隐患的,需要搭配session使用。

session将用户私密信息保存在服务端

  1. 客户端登录认证成功后,服务端会形成session文件,保存用户的私密信息。
  2. 服务器构建响应时,添加属性Set-Cookie: session_id=123 ,称为当前用户的会话ID。
  3. 浏览器本地形成cookie文件,保存该session_id,每次请求都携带该session_id。

本地Cookie中只存储session_id,session_id具有唯一性,服务端根据session_id在服务端查找用户信息。

在这里插入图片描述

虽然无法解决第三方冒充用户身份,但cookie和session可以避免用户信息暴露在网络上传输

1.6 简单HTTP服务器

这段代码是一个简单的基于C++的HTTP服务器端程序,包含了处理HTTP请求和生成HTTP响应的功能。下面是对每个部分的详细注释:

struct http_request

struct http_request
{
    std::string _method;  // HTTP方法,如GET、POST等
    std::string _url;     // 请求的URL路径
    std::string _version; // HTTP协议版本号

    std::vector<std::string> _headers; // HTTP请求头部信息
    std::string _content;              // HTTP请求正文

    std::string _path; // 请求的文件路径,用于处理静态文件请求

    // 构造函数,从传入的请求字符串解析并填充http_request对象
    http_request(const std::string& reqstr) { deserialize(reqstr); }

    // 解析HTTP请求字符串的函数
    void deserialize(const std::string reqstr)
    {
        std::stringstream reqss(reqstr);
        std::string line;
        
        // 从请求字符串中读取第一行,包括方法、URL和版本信息
        getline(reqss, line);
        std::stringstream(line) >> _method >> _url >> _version;

        // 构建请求的文件路径
        _path += WEB_ROOT; // WEB_ROOT 是预定义的根路径常量
        if (_url.back() == '/') // 如果URL以'/'结尾,则默认请求index.html
            _path += "index.html";
        else
            _path += _url;

        // 读取并存储所有的请求头信息
        while (getline(reqss, line))
            _headers.push_back(line);

        // 最后读取请求的正文内容
        getline(reqss, _content);
    }
};

struct http_response

Const char* SEP="\r\n"
struct http_response
{
    std::string _version;      // HTTP协议版本号
    int _stcode;               // HTTP状态码,如200、404等
    std::string _stdesc;       // 状态码对应的描述信息
    std::vector<std::string> _headers; // HTTP响应头部信息
    std::string _path;         // 响应的文件路径
    std::string _content;      // 响应的内容

    // 构造函数,根据请求生成响应
    http_response(const http_request& req) : _version("HTTP/1.0"), _path(req._path)
    {
        struct stat st;
        // 检查请求的文件路径是否存在
        if (stat(_path.c_str(), &st) < 0) {
            _stcode = 404;
            _stdesc = "Not Found";
            _path = WEB_ROOT + "404.html"; // 如果文件不存在,则返回404页面
        }
        else {
            _stcode = 200;
            _stdesc = "OK"; // 文件存在则返回200状态码
        }

        // 读取文件内容并存储到_content中
        std::ifstream ifs(_path, std::ios_base::binary);
        std::string line;
        while (getline(ifs, line))
            _content += line;

        // 添加必要的响应头信息,如Content-Type和Content-Length
        _headers.push_back("Content-Type: " + get_type());
        _headers.push_back("Content-Length: " + std::to_string(_content.size()));
    }

    // 序列化HTTP响应为字符串
    std::string serialize()
    {
        std::stringstream ss;
        ss << _version << ' ' << _stcode << ' ' << _stdesc << SEP; // 输出协议版本、状态码和状态描述
        for (auto& e : _headers)
            ss << e << SEP; // 输出所有响应头
        ss << SEP; // 输出一个空行
        ss << _content; // 输出响应内容
        return ss.str();
    }
};

std::string callback(const std::string& msg)
{
    return http_response(http_request(msg)).serialize(); // 解析请求并生成相应的HTTP响应,并序列化为字符串返回
}
int main(int argc, char* argv[])
{
    uint16_t port = std::stoi(argv[1]); // 从命令行参数获取服务器端口号
    std::unique_ptr<http_server> tsvr(new http_server(callback, port)); // 创建HTTP服务器对象,传入回调函数和端口号
    tsvr->init(); // 初始化服务器
    tsvr->start(); // 启动服务器开始监听端口
}

这段代码实现了一个简单的HTTP服务器,可以处理基本的GET请求,并返回相应的静态文件内容或404错误页面。在实际应用中,需要确保服务器能够处理更多HTTP方法和动态内容请求,并进行安全性和性能优化。

 

2. HTTPS

2.1 加密方式

HTTPS协议是在HTTP协议的基础上加入了SSL/TLS加密机制,通过在传输层对数据进行加密,保证数据的安全性

SSL/TLS属于应用层,数据只有在应用层应用层被加密解密,传输层网络层链路层没有加密概念。

img

加密解密有如下几种方式。

对称加密

对称加密,也称单密钥加密,用同一个密钥进行加密和解密。

在客户端和服务端使用同一个密钥进行加密和解密的过程中,密钥绝不能保存在客户端。将密钥暴露在客户端相当于将肉送到黑客的嘴里,存在极大的安全风险。安全的做法是将密钥仅保存在服务端,客户端需要通过安全的网络请求获取密钥后进行加密操作。服务端接收到客户端的请求后,使用密钥解密数据。然而,如果在客户端请求获取密钥的过程中被黑客拦截,黑客就可以获取到密钥,从而轻易地解密客户端发送的加密数据。虽然对称加密过程简单且传输效率高,但其安全性相对较低,容易受到中间人攻击。

img

非对称加密

非对称加密,就是使用两个密钥。所有人公开的公钥和只有一方具有的私钥。用公钥加密须用私钥解密,用私钥加密须用公钥解密。

有两个密钥——公钥和私钥,分别用于加密和解密数据,且均保存在服务端。客户端首先通过安全的网络请求获取公钥。所有客户端发送的数据都通过公钥进行加密,只能由私钥在服务端进行解密,从而获取原始数据。即使黑客拦截了请求并获取了公钥和加密数据,由于私钥仅存储在服务端且不会通过网络传输,黑客无法解密数据,确保了数据的安全性。

对称加密方法简单且传输效率高,但安全性有限。非对称加密方法安全性高,因其复杂的过程和相对低的传输效率,适合保护敏感数据。综合两者特性,产生了混合加密方法,如HTTPS所采用的加密方式。

在这里插入图片描述

全对称加密
  1. 客户端和服务器分别创建自己的公私密钥,通信前先交换双方的公钥。
  2. 双方都采用非对称加密,双方都使用对端的公钥加密数据发给对端,对端用自己的私钥解密。

虽然可以保证双向通信安全,但时间成本太高效率太低,同样不可取。

混合加密

实际是将两种加密方式混合使用的。

在这种混合加密方案中,公钥和私钥都保存在服务端。客户端首先通过网络请求获取公钥,然后使用这个公钥生成一个随机码,比如ABC123。客户端将使用公钥加密后的随机码传输到服务端。服务端利用私钥对这个密文进行解密,从而获取ABC123随机码。

随后的通信过程中,客户端和服务端都使用这个ABC123作为对称秘钥,进行数据的对称加密和传输。这种方法保证了安全性,因为黑客无法通过网络层面获取到ABC123这个对称秘钥,同时也确保了传输效率。

这种混合加密方案类似于HTTPS的加密过程,结合了非对称加密和对称加密的优势,既保证了安全性,又提升了数据传输的效率。

密钥协商

客户端收到服务端发来到公钥,并用该公钥生成一个密钥,使用非对称加密加密该密钥并发给服务器。服务器用私钥解密获得该密钥。

对称加密通信

自此通信双方都有一个对称加密的密钥,以上称为密钥协商。之后双方可以使用该密钥进行对称加密通信。

在这里插入图片描述

中间人风险

这样做真的就万无一失了吗?其实不然,在上述密钥协商阶段是存在中间人私自更换密钥的风险的。

  1. 在密钥协商阶段,服务端发送的明文公钥S,被中间人截取替换成了中间人的公钥M,再发给客户端
  2. 客户端生成对称密钥X并使用中间人公钥M加密,然后将加密结果M+(X)发送给服务器。
  3. 中间人截取到加密结果,用私钥M'解密,得到密钥X,再用服务端的公钥S加密将其加密并发往服务器
  4. 服务器用S'对其解密得到密钥X

自此通信双方的对称加密密钥X被中间人获取,数据加密形同虚设且毫不知情。
在这里插入图片描述

上述问题的关键在于:客户端无法判断服务器发来的明文公钥是否被篡改。因此,我们必须赋予客户端辨别篡改的能力。

2.2 数据防篡改

如何防止如何识别数据的内容是否被篡改呢?

预备知识
数字摘要/数字指纹

对文本内容利用单向哈希算法,生成固定长度的、具有唯一性的、不可逆的字符序列,该字符序列就称为数字指纹/数字摘要。

数字摘要不是严格的加密,因为无法反推出文本内容,所以只能进行数据对比。

对传输来的文本重新生成哈希结果,对比两者哈希结果是否相同,就能看出文本是否被篡改。

数字签名

对数字指纹/数字摘要进行加密,加密结果就称为数字签名。

在这里插入图片描述

CA证书机构

由此出现一种权威机构CA证书机构。

  • 服务商将自身信息提交给CA机构,CA机构审核通过后形成证书颁发给服务端。

  • 服务端将证书发给客户端,客户端验证证书后,使用证书内公钥进行密钥协商。

本质就是在加密通信前,服务端不再传输公钥,而是传输证书。可以保证公钥不被篡改。

在这里插入图片描述

如何生成证书?
  1. CA机构将服务商的信息生成数字指纹,并用CA机构私钥生成数字签名。
  2. 再将服务商的信息和签名放到一起,称为证书。

在这里插入图片描述

客户端如何验证证书?
  1. 客户端收到证书之后,对其中服务商信息重新生成数字指纹,
  2. 再将证书中的数字签名用CA公钥解密出数字指纹。
  3. 对比两者数字指纹,如果相等说明证书没有被篡改。

在这里插入图片描述

是否存在中间人风险?

此时中间人仍能获取并解密证书,但中间人没有CA机构私钥A'因此无法生成数字签名,也就无法篡改证书

客户端收到证书后进行校验,证书中服务商信息和数字签名不管哪一个被修改都可以识别。

在这里插入图片描述

2.3 最终加密方案

最终方案就是混合加密+CA证书。

客户端请求服务器后,服务器不再明文返回公钥,而是返回CA机构颁发的证书。可以防止公钥被篡改,就不存在中间人风险了。

  1. 客户端向服务器请求连接,
  2. 服务器返回网站证书,客户端验证证书无误,并提取公钥加密对称密钥,发往服务端。
  3. 服务端利用自身私钥解密,得到对称密钥,
  4. 自此双方开始对称加密通信。
  • 37
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuhyOvO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值