HTTP协议

这是javaweb元知识库中的一个组成部分,通过标签分类可快速抵达

  • 彩色文字
    在一段话中方便插入各种颜色的标签,包括:红色黄色绿色青色蓝色灰色

不同的颜色以为意味这不同的重点度,红色最高,意味着出现频率越高(其实这玩意恨不得全搞成红色的各凭心情吧)

本文章基本为了快速通过面试和抽查,只收录了高频问题
标签样式示例:

danger 提示块标签


warning 提示块标签


success 提示块标签

HTTP请求报文和响应报文的结构

HTTP 报文是纯文本格式的结构化数据,分为请求报文(客户端→服务器)响应报文(服务器→客户端),二者核心结构相似,均由起始行 + 首部字段 + 空行 + 消息体四部分组成,空行(CRLF)是首部和消息体的唯一分隔符,是报文解析的关键。

  • 请求报文结构(request)[这是客户端发送给服务器的报文]【一般格式为json】
    • 第一部分(请求行)【请求方法、请求URI、HTTP版本】
    • 第二部分(请求头)键值对结构,是多组的头部字段名,头部字段值
    • 第三部分(空行)这是头部和主体的分隔符
    • 第四部分(请求体)携带请求参数
  • 响应报文结构(response)[这是服务器返回给客户端的报文]【一般格式为json】
    • 第一部分(响应行)【HTTP版本,状态码、原因短语】
    • 第二部分(响应头)键值对结构,是多组的头部字段名,头部字段值
    • 第三部分(空行)这是头部和主体的分隔符
    • 第四部分(响应体)携带响应数据

HTTP常见状态码

这个确实很重要,因为状态码是判断请求是否成功的标准

HTTP 状态码为3 位数字,分 5 大类,核心用于标识请求处理结果,原因短语是状态码的文字描述(无实际功能,仅易读,不同服务器表述可能略有差异但核心一致)。

  • 1xx状态码
    • 表示服务器已收到请求,需客户端继续执行后续操作,实际开发中极少直接使用,仅作协议层交互。
  • 2xx状态码
    • 最常用大类,表示服务器已成功接收、解析、处理请求,返回客户端所需资源 / 数据。
    • 例如:200 OK(成功)、201 Created(创建成功)、202 Accepted(已接受但未处理)等。
  • 3xx状态码
    • 需要客户端重定向请求,即要求客户端使用服务返回的地址从新发送请求。
  • 4xx状态码
    • 客户端错误,请求语法错误、资源不存在等。
    • 例如常见的400(Bad Request)、401(Unauthorized)、403(Forbidden)、404(Not Found)等。
      • 400 :客户端请求语法错误,服务器无法理解,这个是通用的状态码
      • 401 :客户端未授权访问,需要登录认证
      • 403 :客户端拒绝访问,服务器拒绝提供服务
      • 404 :客户端请求的资源不存在
      • 405 :客户端请求的方法被服务器拒绝
  • 5xx状态码
    • 责任在服务器,表示客户端请求本身无错误,但服务器在处理过程中出现异常,无法返回结果。
    • 例如常见的500(Internal Server Error)、501(Not Implemented)、502(Bad Gateway)等。
      • 500 :通用的服务器错误,比如服务器抛出了未处理的异常
      • 501 :服务器不支持请求的方法
      • 502 :网关错误,服务器无法与上游服务器建立连接

浏览器和Apifox的区别

浏览器的地址栏只可以发GET请求,不能发POST,PUT,DELETE等请求,而且直接使用浏览器没法直接用请求体传输数据.
而Apifox等工具是专业的HTTP测试工具,支持所有方式的请求

虽然我个人而言更喜欢使用浏览器来调试,因为浏览器的拓展性非常高,缺什么功能就去补什么插件就行,我可以说浏览器就是互联网最杰出的产品

请求报文中有几种方式携带数据

  • 方式一:在请求URI中携带数据
    • 例如:/api/user?id=123&name=张三
    • 仅适用于GET请求,不建议使用,因为数据会暴露在URL中,存在安全风险
    • 大小限制:URL传输的数据不能超过4k-8k字符
    • 解析速度快于请求体(因为URL是直接解析的,而请求体需要先解码)
  • 方式二:用请求体中携带数据
    • 例如:POST /api/user
    • 仅适用于POST请求,建议使用,因为数据不会暴露在URL中,不存在安全风险
  • 方式三:用请求头中携带数据
    • 例如:Authorization: Bearer 123456
    • 请求头也可以携带数据,但是很少会使用请求头来携带业务数据

请求方式有几种,以及他们的区别

请求方式 核心操作 数据携带方式 大小限制 解析速度
GET 查(获取资源) 在请求URI中携带数据 4k-8k字符 解析速度快
POST 创(提交数据) 在请求体中携带数据 无限制 解析速度慢
PUT 改(修改资源) 在请求体中携带数据 无限制 解析速度慢
DELETE 删(删除资源) 在请求URI中携带数据 4k-8k字符 解析速度快

其中POST得考虑数据重复提交的代码,因为POST是非幂等的

幂等性和安全性

多次发送相同请求,服务器端产生的结果完全一致(不会创建重复资源 / 修改多次数据),即请求操作的结果与执行次数无关;

  • 幂等性:如果请求操作的结果与运行次数无关,那么这个请求就是幂等的。(GET,HEAD,PUT,DELETE,OPTIONS)
  • 非幂等性:如果请求操作的结果与运行次数有关,那么这个请求就是非幂等的。(POST)这家伙是个新增请求,操作结果和请求次数强相关

安全性相关:这里面只有GET请求是安全的,因为只有GET请求不会修改服务器端的数据,不会导致数据泄露。

SpringBoot控制器的使用

控制器的常用注解

  • 写在类身上的
    • @Controller:这是标记类是传统控制器
    • @RestController:标记类是RESTful风格的新型控制器
    • @RequestMapping:用于绑定类的请求地址
  • 写在方法身上的
    • @GetMapping:用于绑定GET请求方法
    • @PostMapping:用于绑定POST请求方法
    • @PutMapping:用于绑定PUT请求方法
    • @DeleteMapping:用于绑定DELETE请求方法
    • @ResponseBody:用于将方法的返回值直接作为响应体
  • 写在方法的参数身上的
    • @RequestParam:用于绑定请求参数
      • 修改参数名
      • 配置必填参数,如果没有携带请求就等着出400吧
    • @PathVariable:用于绑定路径变量,匹配URL中的路径参数
    • @RequestBody:用于绑定请求体,接收json格式的请求体
    • @RequestHeader:用于绑定请求头
    • @CookieValue:用于绑定cookie值

如何在控制器中获取HTTP参数

  • 手动获取
    • 控制器方法入参:HttpServletRequest request参数
    • 使用request.getParameter("参数名") 获取参数值
  • 自动映射
    • 用多个成立参数映射,如String name,String age结构
    • 用一个实体对象映射,如User user
    • 实体类的属性必须与HTTP参数名一致
    • 实体类的属性必须提供getter,setter方法,否则框架无法进行反射

如何在控制器中返回HTTP响应结果

  • 手动返回
    • 方法返回值用void,即方法没有返回值
    • 方法入参:HttpServletResponse response参数
    • 使用response.getWriter().write("响应内容") 获得输出流
    • 用流输出数据给客户端
  • 自动返回
    • 方法返回值使用Object类型,或者自定义类型
    • 直接使用return返回结果
    • @Controlle注解的控制器方法,需要配合@ResponseBody注解,返回值会被自动转换为JSON格式,返回给客户端
    • @RestController注解的控制器方法,这个就没有绑定对象了,返回值会被自动转换为JSON格式,返回给客户端

什么是JSON格式

JSON是一种以键值对为基础、轻量级的文本传输格式,是前后端分离项目中最主流的数据传递格式。本质上是java对象或者数组序列的特定格式的字符串,从而方便传输和解析,在JSON格式中,对象,哈希表使用{ }表示,数组,集合使用[ ]表示。
这孩子(JSON)的出现,代替了传统的XML格式,作为新型的数据通信标准格式。

  • @ @RestController控制器会自动将方法返回的java对象转换为JSON格式,返回给客户端。
  • @Controller控制器需要搭配@ResponseBody注解,返回值才会被自动转换为JSON格式。

SpringBoot整合MyBatis框架

我也不知道为什么还要在这里再写一次,在这里写步骤真的很奇怪喔

我是懒狗,我写过了更详细的,这里贴链接,自己跳转过去看
MyBatis官方文档
我自己的文章地址跳转(站内跳转)
MyBatis学习笔记这里有MyBitas和MyBatis-Plus的详细学习笔记,前者是基础版,后者是增强版,
外部吊炸天详细版
狂神说MyBatis
狂神说javaweb

我这里做一下额外补充,就是开启mybatis-plus的sql日志功能

  • 在application.properties文件中添加以下配置:
    • logging.level.com.baomidou.mybatisplus.extension: debug
  • 重启项目,即可在控制台查看sql语句
    这个可以很有效的帮助我们调试sql语句,查看sql语句是否正确,是否符合预期。
    但是这个在开发环境下才建议开启,因为在生产环境下,开启这个功能会增加系统的开销,影响系统的性能。

springboot整合数据校验框架

数据校验框架是用来验证用户输入的数据是否符合预期的格式和规则的,常用的有Hibernate Validator和JSR 380(Bean Validation 2.0)等。Spring Boot提供了对这些数据校验框架的支持,可以方便地在控制器方法中进行数据校验。

现在我们先将其整合到项目中,下面是依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

validation框架常用注解(表格表示)

  • 可以用注解代替手写 if-else语句,实现数据校验。
  • 注解可以直接用在DTO类或者实体类中
  • 框架会通过反射阅读字节码中的注解,从而实现数据校验。
注解 作用 适用类型
@NotNull 验证对象不为null 任意对象
@NotEmpty 验证字符串、集合、数组不为null且不为空 String, Collection, Array
@NotBlank 验证字符串不为null且不为空白 String
@Size(min=, max=) 验证字符串、集合、数组的长度在指定范围内 String, Collection, Array
@Min(value=) 验证数值类型的值不小于指定值 Number
@Max(value=) 验证数值类型的值不大于指定值 Number
@Email 验证字符串是否为合法的邮箱地址 String
@Pattern(regexp=) 验证字符串是否匹配指定的正则表达式 String
@Past 验证日期类型的值必须是过去的日期 Date, Calendar
@Future 验证日期类型的值必须是未来的日期 Date, Calendar
@DecimalMin(value=) 验证数值类型的值不小于指定值 BigDecimal, BigInteger
@DecimalMax(value=) 验证数值类型的值不大于指定值 BigDecimal, BigInteger

以上的基本上就是常用注解了,其他的根据需要使用即可

数据校验的逻辑

1.用注解标记实体类(DTO类或者实体类)中需要校验的属性
2.在控制器的入参处,使用@Valid注解校验实体类的属性
3.如果校验失败,会自动返回一个HTTP响应,响应体中包含校验失败的信息
4.如果校验成功,控制器方法会继续执行,处理业务逻辑

TOKEN

TOKEN是一种用于身份验证和授权的字符串,通常由服务器生成并发送给客户端,客户端在后续请求中携带TOKEN以证明自己的身份和权限。常见的TOKEN类型有JWT(JSON Web Token)和OAuth 2.0 Token等。
我们一般情况所说的token是JWT,它是一种基于JSON的TOKEN,用于在客户端和服务器之间传递安全的认证信息。是令牌
而另外一种token是指的是AI大模型中的token,他是和AI对话时的消耗量,用于支付AI服务的费用。

JWT是什么

  • JWT(JSON Web Token)是一种基于JSON的TOKEN,用于在客户端和服务器之间传递安全的认证信息。
  • 生成Token令牌的方式有很多,JWT是目前最流行的Token令牌生成和解析技术

Token令牌的工作机制

  • 用户从前端发起登录请求,包含用户名和密码
  • 后端从数据库中查询用户是否存在,并验证密码,验证通过后,后端会签发一个加密身份证,返回给前端,前端将令牌存储在浏览器中
  • 以后前端的每次请求后端,都带着Token令牌(放在报文的头部)
  • 后端在拦截器中,获取报文头部的令牌进行校验
  • 如果校验通过,说明用户已登录,继续执行业务逻辑
    • 令牌合法就放行
    • 没有携带令牌就拦截
    • 令牌过期就拦截
    • 令牌被篡改就拦截

      为什么前端每次请求后端都要携带令牌

  • 用令牌代替账号和密码进行用户身份校验
  • 如果每次请求都携带账号和密码,每次都查数据库,会增加系统的开销,影响系统的性能。
  • 可以用令牌代替账号密码,作为临时身份证,令牌有效期内都无需验证密码

基于Springboot3 + mybatis-plus + jwt + 拦截器实现用户登录认证校验的全流程

  • 安装相应的依赖,配置
  • 数据库相关的表支持
  • JwtUtils工具类 + 四个通用的公共类
  • loginDto类:用于接收前端登录的用户名和密码
  • user类:用于接收数据库中的用户信息
  • UserDetails接口:用于生成和解析token令牌
  • UserController控制器:用于处理用户登录和校验的请求
  • UserService服务:用于处理用户相关的业务逻辑
  • UserMapper映射器:用于操作数据库中的用户表
  • AuthInterceptor拦截器:用于校验请求中的令牌令牌是否合法
  • GlobalExceptionHandler全局异常处理类:用于处理全局的异常
    满足以上条件即可完成一个简单的用户登录认证校验的全流程

SpringSecurity安全框架

核心功能

认证(Authentication):验证“你是谁”,核心是确认用户身份的合法性。比如进入学校大门,门卫检查你的学生证,确认你是本校学生(不是外人),这个过程就是认证;对应系统中,用户输入用户名/密码,系统校验其是否存在、密码是否正确,就是认证。

授权(Authorization):验证“你能做什么”,核心是给已认证的用户分配访问权限。比如你是学生,只能进入教学楼、图书馆,不能进入教师办公室、校长室;对应系统中,学生账号只能访问学生相关接口,不能访问管理员后台,这个过程就是授权。

安全防护(Security):抵御外界利用漏洞的攻击

核心逻辑:先认证,后授权;未认证的用户,连授权的资格都没有;认证通过后,再根据用户角色/权限,决定其能访问哪些资源。

只有认证,没有授权的系统会怎么样呢

  • 【没有授权的系统】任意用户通过登录认证之后,将可以访问系统任何资源
  • 【有授权功能的系统】佣兵工会通过登录认证后,会由系统进行权限分配,用户内只能访问权限范围内的资源

什么样的系统需要用Security框架进行授权和认证

  • 不用认证和授权的系统
    • 公共(公开)的数据接口:例如天气预报数据接口
    • 纯静态网页,系统只有浏览,没有提交功能
    • 没有用户角色和权限控制的简单系统
  • 必须认证和授权的系统
    • 需要用户登录才能访问的系统
    • 分布式系统,需要对不同服务的访问进行权限控制
    • 需要对用户操作进行权限控制的系统,例如银行系统

相关能力

当你在项目中集成了这个框架后什么都不做的情况下,框架默认赋予项目以下能力

  • 项目中的所有资源和接口都会受到该框架的保护,必须登录才会访问
    • 他会自动设置一道拦截器,用于对所有请求进行拦截和校验
    • 如果请求中没有JWT令牌,或者令牌无效,会拒绝访问
  • 默认提供名为User的默认用户名
  • 启动后提供的默认密码会在项目重启后自动打印在控制台,是一串随机密码

第一次访问服务器时的运行流程

  • 当用户第一次访问服务器任意资源时,此时在安全框架眼中,此用户没有认证
  • 安全框架会返回一个302的响应给客户端(表示需要重定向)
    • 会在响应头中携带一个Location字段,值是登录页面的URL
    • 登录页面的URL默认是/login,是由框架提供的认证地址, 会返回一个认证页面
  • 此时客户端会根据重定向地址【自动】发起【第二次】请求
  • 客户端就看见了第二次请求返回的认证页面
  • 在认证页面中输入用户名和密码,点击登录按钮
  • 会成功通过安全框架,就可以成功访问到第一次请求的资源
  • 此后的30分钟内无需认证,可以一直访问服务器

配置对个用户,分配不同的角色

  • yml只能配置一个用户,配置类可以配置多个用户
  • @Bean注解表示此方法返回一个组件给框架
  • UserDetails接口是SpringSecurity提供的用户管理器组件的父接口
  • InMemoryUserDetailsManager是UserDetailsService的一种实现,表示基于内存管理用户
    • 内存用户管理器,基于内存存储用户角色信息
      • 优点:简单方便,无需配置数据库
      • 缺点:不安全,因为用户角色信息存储在内存中,一旦重启,就会丢失。
    • 还可以用数据库存储用户角色信息,但是需要配置数据库连接池,数据库表结构等。
  • UserDetails接口代表一个用户角色信息类,存储到用户管理器中
  • User代表一个用户类,用于构建UserDetails对象

认证和授权失败的问题

  • 认证失败:用户输入的用户名/密码错误
  • 配置了密码加密组件PasswordEncoder,但是用的是明文密码
  • PasswordEncoder接口是SpringSecurity框架提供的密码加密组件接口,返回的是BCryptPassword对象是基于BCrypt加密算法的密码加密组件
  • 我们无需单独安装密码加密解密的依赖,security框架已经集成了密码加密解密的功能。
  • 你只要告诉框架用什么加密组件就行

    如果没有配置加密组件,框架直接用输入的明文比对初始密码,此时密码应该用明文
    如果配置了加密组件,框架会用加密后的密码比对初始密码,此时密码应该用密文

出现403错误,就表示授权失败了,此时的后端控制台应该会抛出一个授权失败的java异常,我们不应该让用户看到这个异常信息。
应该使用管理器异常统一处理授权失败的异常,返回jaon格式的统一结果:

1
2
3
4
{
"code": 403,
"msg": "授权失败"
}

全限定

可以根据role角色配置用户是否允许访问某个接口

  • hasRole(‘角色权限’)
    • 权限的粗粒度控制,通常应用在在接口类身上
    • 全大写命名,例如USER、ADMIN等
  • hasAuthority(‘操作权限’)
    • 权限的中等粒度控制,通常应用在在方法身上
    • 控制角色是否具有某种特定的操作权限
    • 全小写命名,例如read、write、delete等
  • hasPermission(‘数据权限’)
    • 权限的细粒度控制,控制用户操作某资源的权限,例如张三和李四都可以删除订单数据,但是张三只能删除自己的订单数据,李四也是一样的,但是管理员可以删除全部订单数据。(订单数据在同一张表,用的同一个删除接口)