前端基础

1.什么是 HTTP 请求?

HTTP(HyperText Transfer Protocol,超文本传输协议)是浏览器和服务器之间通信的协议。
当你在浏览器输入一个网址、或者前端调用接口时,实际上就是在向服务器发出 HTTP 请求

一个 HTTP 请求一般包含三部分:

  1. 请求行(Request Line)

    • 包括请求方法(GET/POST等)、请求 URL、HTTP 版本。

    • 例子:

      GET /index.html HTTP/1.1
      
  2. 请求头(Request Headers)

    • 携带附加信息,比如客户端信息、请求格式、认证信息、缓存控制等。

    • 例子:

      Host: www.example.com
      User-Agent: Mozilla/5.0
      Content-Type: application/json
      

      现在一般都用Content-Type: application/json这个格式了,另外的格式有兴趣自行Google。

  3. 请求体(Request Body)

    • 主要在 POST/PUT 请求中,用于传输数据,比如表单信息、JSON 数据。

    • 例子:

      {
        "username": "jack",
        "password": "123456"
      }
      

2. 常见的 HTTP 请求方法

(1)GET 请求

  • 作用:从服务器获取数据(读取资源)。

  • 特点

    • 参数一般拼接在 URL 上(如 ?id=1&name=jack)。

    • 没有请求体(理论上可有,但规范上不推荐)。

    • 幂等:多次请求结果相同。

  • 例子

    GET /user?id=1 HTTP/1.1
    Host: example.com
    

(2)POST 请求

  • 作用:向服务器提交数据(创建/修改资源)。

  • 特点

    • 数据一般放在请求体里。

    • 比 GET 更适合传输大数据(比如 JSON、文件上传)。

    • 不是幂等的,每次请求可能有不同结果。

  • 例子

    POST /login HTTP/1.1
    Host: example.com
    Content-Type: application/json
    
    {
      "username": "jack",
      "password": "123456"
    }
    

(3)其他常见方法

  • PUT:更新资源(整体更新)。

  • PATCH:更新资源(部分更新)。

  • DELETE:删除资源。

  • HEAD:只获取响应头,不返回具体数据。

  • OPTIONS:获取服务器支持的方法,常用于跨域请求的预检。


3. 请求头(Request Headers)

请求头用来告诉服务器客户端的一些信息或者请求的规则。常见的有:

  • Host:目标服务器的域名/IP。

  • User-Agent:客户端信息(浏览器、操作系统等)。

  • Accept:告诉服务器客户端希望接受的数据类型(如 application/json)。

  • Content-Type:请求体的数据类型,比如:

    • application/json → JSON 格式数据

    • application/x-www-form-urlencoded → 表单数据

    • multipart/form-data → 文件上传

  • Authorization:认证信息(比如 Token、JWT)。

  • Cookie:客户端携带的会话信息。


4. 请求体(Request Body)

  • GET 请求一般没有请求体,参数写在 URL 上。

  • POST/PUT/PATCH 通常在请求体中提交数据,常见格式:

    1. 表单:application/x-www-form-urlencoded

      username=jack&password=123
      
    2. JSON:application/json

      { "username": "jack", "password": "123" }
      
    3. 文件上传:multipart/form-data


5. 请求和响应的关系

  • 请求(Request):客户端发起的消息。

  • 响应(Response):服务器返回的结果。
    响应也有三部分:

    1. 响应行(状态码,如 200 OK404 Not Found

    2. 响应头(如 Content-Type, Set-Cookie)

    3. 响应体(实际的数据,比如 HTML、JSON、图片等)

6.Session原理

HTTP是无状态,有会话的:

  • 无状态是指,请求之间相互独立,第一次请求的数据,第二次请求不能复用。

  • 有会话是指,客户端和服务端都有相应的技术,可以暂存数据,让数据在请求之间共享。

服务端使用Session技术来暂存数据,当请求送到服务端,服务端创建一个Session对象,来存数据,Session对象可以简单理解为一个Key-Value的键值对数据结构。当别的请求送到服务端的时候,服务端会创建一个新的Session对象,来存储该请求的数据。当一个请求过来之后,它用啥来找到它起初创建的Session对象呢?当Session对象创建的时候,回给请求方返回一个jsessionid,不同的Session被创建的时候,产生的jsessionid都不一样。当下次请求来的时候,会带上这个jsessionid,那么服务端就可以知道这个请求在请求哪个Session。

下面我们来模拟这个过程:

后端代码:

@Controller
@RequestMapping("/test")
public class MyController {
    @RequestMapping("/s1")
    @ResponseBody
    public String s1(HttpSession session, String name) {
        session.setAttribute("name", name);
        return "数据已存储!";
    }

    @RequestMapping("/s2")
    @ResponseBody
    public String s2(HttpSession session) {
        return "取出数据" + session.getAttribute("name");
    }

}

使用Telnet发送请求:

GET /test/s1?name=zhang HTTP/1.1
Host: localhost

服务端响应:

image-20250823205512324

注意看我们得到了一个Sessionid,把它记下,后面要用!

取:

GET /test/s2 HTTP/1.1
Host: localhost
Cookie: JSESSIONID=AD6B5E162B5B52E47DF609681A0D51AD

服务器响应:

image-20250823205850240

Session技术实现身份验证:

sequenceDiagram participant Client participant LoginController participant LoginInterceptor participant Session %% Login Process Client->>LoginController: 登录请求 (Login Request) LoginController->>LoginController: 检查用户名, 密码, 验证通过 (Check username, password, validation passed) LoginController-->>Session: 存入用户名 (Store username) LoginController-->>Client: 登录成功 (Login successful) %% Other Requests Process Client->>LoginInterceptor: 其它请求 (Other requests) LoginInterceptor->>Session: 获取用户名 (Get username) Session-->>LoginInterceptor: 用户名存在, 放行 (Username exists, allow) LoginInterceptor-->>Client: 放行 (Allow)

但是这个验证只适用于单体项目,分布式项目可以使用jwt技术

jwt技术实现身份验证

sequenceDiagram participant Client participant LoginController participant LoginInterceptor %% Login Process Client->>LoginController: 登录请求 (Login Request) LoginController->>LoginController: 检查用户名, 密码, 验证通过 (Check username, password, validation passed) LoginController-->>Client: 登录成功, 返回token (Login successful, return token) %% Other Requests Process Client->>LoginInterceptor: 其它请求, 携带token (Other requests, with token) LoginInterceptor->>LoginInterceptor: 校验token, 校验无误, 放行 (Validate token, validation passed, allow) LoginInterceptor-->>Client: 放行 (Allow)

下面我们使用Telnet来模拟这一过程,这里我使用hutool工具类,构造token:

package com.hanserwei.frontprelearn.controller;

import cn.hutool.jwt.JWTUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/test")
public class MyController {

    // 密钥,实际项目中应该放在配置文件中
    private static final String SECRET_KEY = "mySecretKey123456";

    /**
     * JWT登录验证示例
     */
    @RequestMapping("/login")
    @ResponseBody
    public String j1(String name, String pass) {
        if (name.equals("hanserwei") && pass.equals("123456")) {
            // 创建payload内容
            Map<String, Object> payload = new HashMap<>();
            payload.put("username", name);
            payload.put("loginTime", System.currentTimeMillis());

            // 使用Hutool生成JWT token
            String token = JWTUtil.createToken(payload, SECRET_KEY.getBytes());
            return "登录成功!token: " + token;
        } else {
            return "登录失败!用户名或密码错误";
        }
    }

    /**
     * 验证JWT token示例
     */
    @RequestMapping("/verify")
    @ResponseBody
    public String verifyToken(@RequestHeader("Authorization") String authorization) {
        try {

            // 验证token是否有效
            boolean verify = JWTUtil.verify(authorization, SECRET_KEY.getBytes());
            if (verify) {
                // 解析token中的信息
                Map<String, Object> payload = JWTUtil.parseToken(authorization).getPayloads();
                return "Token验证成功!用户信息: " + payload.get("username");
            } else {
                return "Token验证失败!";
            }
        } catch (Exception e) {
            return "Token解析异常: " + e.getMessage();
        }
    }
}

取token:

GET /test/login?name=hanserwei&pass=123456 HTTP/1.1
Host: localhost

image-20250823213607032

验证token:

我们使用刚刚的token,来验证一下:

GET /test/verify HTTP/1.1
Host: localhost
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblRpbWUiOjE3NTU5NTYzNDIyMDQsInVzZXJuYW1lIjoiaGFuc2Vyd2VpIn0.7wrv4X4qmsRLJHCJRy2F2cAgSDb4gqKlpNu818KOVwU

image-20250823214001040

这里顺便说一下这个token咋来的,其实一个token可以看做三部分,以.分割,第一部分是算法签名部分(header),第二部分数据(payload),第三部分是签名。其中前两部分都没有加密,都是JSON字符串,只不过用了Base64编码而已,我可以直接Decode看一下:

import org.junit.jupiter.api.Test;

import java.util.Base64;

public class JwtTest {
    @Test
    public void test() {
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblRpbWUiOjE3NTU5NTYzNDIyMDQsInVzZXJuYW1lIjoiaGFuc2Vyd2VpIn0.7wrv4X4qmsRLJHCJRy2F2cAgSDb4gqKlpNu818KOVwU";
        System.out.println(new String(Base64.getDecoder().decode(token.split("\\.")[0])));
        System.out.println(new String(Base64.getDecoder().decode(token.split("\\.")[1])));

    }
}

输出:

{"typ":"JWT","alg":"HS256"}
{"loginTime":1755956342204,"username":"hanserwei"}

可以看到,签名算法用到了”HS25“算法,这个不用去深究,就是一个签名算法而已!第二部分包含了在login的时候存的数据。token的安全性在于第三步份,可以保证token的属性不会被篡改!

比如我现在的hanserwei是个普通用户,而管理员是bigdty,那么我们可以把用户改成bigdty然后发给服务端吗?

import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class JwtTest {
    @Test
    public void test() {
        String str = """
                {"loginTime":1755956342204,"username":"bigdty"}
                """;
        System.out.println(Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8)));
    }
}

先把{"loginTime":1755956342204,"username":"bigdty"}进行Base64编码。

eyJsb2dpblRpbWUiOjE3NTU5NTYzNDIyMDQsInVzZXJuYW1lIjoiYmlnZHR5In0K

然后替换我们token的第二部分,然后发送给服务端:

GET /test/verify HTTP/1.1
Host: localhost
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblRpbWUiOjE3NTU5NTYzNDIyMDQsInVzZXJuYW1lIjoiYmlnZHR5In0K.7wrv4X4qmsRLJHCJRy2F2cAgSDb4gqKlpNu818KOVwU

结果当然是验证失败啦:

image-20250823215906146

这其实是和第三部分有关,第三部分是由前两部分,加一个密钥,然后通过一个签名算法得出,而密钥只有服务端知道。只要我们改了前面两部分的任何一个部分,那么通过签名算法算出来结果就不一样。所以就G了。过不了验证!