概述
在认证与授权的技术栈中,HTTP 协议承载了客户端与资源服务器之间几乎所有的信任传递。
无论是经典的 Session-Cookie 模式,还是基于 Token 的 OAuth2 流程,敏感信息、身份凭证、令牌都嵌在 HTTP 请求与响应的特定位置上。
本文从一次受保护的 REST 请求入手,拆解 HTTP 请求的解剖结构,并结合 Spring Security 的实际配置,理解 HTTP Basic 认证头、CSRF 保护、请求体格式等内容如何在工程中落地。
纲要
- HTTP 请求的核心结构:起始行、请求头、实体头
- 使用 IntelliJ HTTP Client 发送 API 请求
- 携带认证头:HTTP Basic 认证的原生写法与 Base64 规则
- 请求参数传递的三种形式:查询参数、路径参数、JSON Body
Content-Type实体头的作用- Spring Security 上下文中的 CSRF 保护与临时绕过
- 完整可运行的 Spring Boot 示例代码(安全配置与 Controller)
HTTP 请求的解剖结构
一个标准的 HTTP 请求由纯文本构成,主要分为三大部分:
- 起始行 :包含请求方法、URI 和协议版本,例如
GET /api/demo HTTP/1.1 - 请求头:一组键值对,用于描述客户端环境、期望的响应格式、身份信息等。根据用途可细分为通用头、请求头、实体头,但它们在格式上完全一致
- 实体体 :对于
POST、PUT、PATCH等方法,请求可携带数据体,例如 JSON、表单字段等
浏览器开发者工具的 Network 面板中将信息归类为 Request Headers 和 Response Headers,其中前者便对应上述请求头部分。实体头(如 Content-Type)在传递数据时用于告知服务端数据的编码格式,它本质上也属于请求头的一部分。
使用 HTTP Client 快速调试
如果已安装 IntelliJ 的 REST Client 插件(通常名为 "HTTP Client"),任何扩展名为 .http 或 .rest 的文件都会被识别为请求文件。使用 ### 分隔不同的请求。
一个最简单的 GET 请求可以写成:
http
### 获取公共资源
GET http://localhost:8080/api/hello HTTP/1.1
当我们的资源受 Spring Security 保护时,直接访问会收到 401 Unauthorized。为了让请求通过认证,需要加入认证头。
携带认证头:HTTP Basic 认证
HTTP Basic 认证是最直接的身份传递方式,它使用的请求头格式为:
Authorization: Basic base64(username:password)
标准要求将 用户名:密码 拼接后使用 Base64 编码。HTTP Client 提供了简化写法,可以直接写入原始用户名与密码,工具会自动完成 Base64 编码:
http
### 携带 Basic 认证头
GET http://localhost:8080/api/hello HTTP/1.1
Authorization: Basic user password
发送后服务端将验证凭证,若通过则返回 200 OK 及响应体。这种将认证信息附加在请求头中的模式,与 OAuth2 的 Bearer 令牌传递方式在结构上如出一辙,只是认证方案标识和令牌内容不同。
POST 请求与 CSRF 的首次遭遇
将方法改为 POST,并携带相同的认证头,有时会发现请求直接被 403 Forbidden 拒绝,日志中出现 Invalid CSRF Token。这是因为 Spring Security 默认开启 CSRF 防护,对所有状态改变的请求(如 POST、PUT、DELETE)均要求携带一个由服务端生成的 _csrf token。
如果是在前后端分离、移动端或无状态的 Token 认证架构中,通常需要关闭 CSRF 保护。通过在安全配置中调用 csrf().disable() 即可:
java
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
重启应用后,POST 请求将不再因缺少 CSRF Token 而被拒绝。
请求参数的三种传递形式
查询参数(Query String)
查询参数通过 URL 中的 ?key=value&key2=value2 传递,多用于 GET 和某些 POST 场景:
http
### 携带查询参数
POST http://localhost:8080/api/greet?name=wangwu HTTP/1.1
Authorization: Basic user password
Controller 侧使用 @RequestParam 接收:
java
@PostMapping("/greet")
public String greet(@RequestParam String name) {
return "Hello, " + name;
}
路径参数(Path Variable)
参数直接作为 URL 路径的一部分,使用 @PathVariable 绑定:
java
@GetMapping("/users/{name}")
public String getUser(@PathVariable String name) {
return "User: " + name;
}
对应请求:
http
### 路径参数
GET http://localhost:8080/users/zhangsan HTTP/1.1
Authorization: Basic user password
请求体携带 JSON 数据
当需要传递结构化数据时,通常将 JSON 放在请求体中,并必须通过 Content-Type 头告知服务端格式:
http
### 发送 JSON 数据
POST http://localhost:8080/api/user/info HTTP/1.1
Authorization: Basic user password
Content-Type: application/json
{
"name": "lisi",
"gender": "male",
"idNumber": "123456199001011234"
}
服务端定义一个 DTO 类并使用 @RequestBody 接收:
java
public class UserInfo {
private String name;
private String gender;
private String idNumber;
// getters and setters
}
@PostMapping("/api/user/info")
public String receiveUser(@RequestBody UserInfo user) {
return "Received: " + user.getName();
}
Content-Type: application/json 是一个典型的实体头,它告诉服务端"请求体的内容是 JSON"。如果缺失或填写错误,会收到 415 Unsupported Media Type。
完整可运行代码示例
下面给出一个可直接运行的 Spring Boot 项目结构,包含安全配置与示例 Controller。
dir
src/main/java/com/example/demo/
├── DemoApplication.java
├── config
│ └── SecurityConfig.java
└── controller
└── DemoController.java
主启动类:
java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
安全配置(关闭 CSRF,启用 HTTP Basic,设定内存用户):
java
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
控制器(涵盖 GET、POST 及不同参数传递方式):
java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class DemoController {
// 简单 GET,测试基本认证
@GetMapping("/api/hello")
public String hello() {
return "Hello, authenticated user!";
}
// POST + 查询参数
@PostMapping("/api/greet")
public String greet(@RequestParam String name) {
return "Hello, " + name;
}
// 路径参数
@GetMapping("/users/{name}")
public String getUser(@PathVariable String name) {
return "User: " + name;
}
// POST + JSON 请求体
@PostMapping("/api/user/info")
public String receiveUser(@RequestBody UserInfo user) {
return "Received user: " + user.getName();
}
// 内部静态 DTO
static class UserInfo {
private String name;
private String gender;
private String idNumber;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getIdNumber() { return idNumber; }
public void setIdNumber(String idNumber) { this.idNumber = idNumber; }
}
}
将上述代码组织到 Spring Boot 工程中,启动后即可使用本文前述的 HTTP Client 请求进行测试。所有示例请求均与安全配置兼容,可以直接复制到 .http 文件中运行。
总结
HTTP 请求的结构是认证与授权信息传递的物理基础。
理解起始行、各类请求头以及实体的作用,能帮助我们更精确地调试安全集成问题。从 HTTP Basic 到 OAuth2 的 Bearer Token,本质都是在 Authorization 头中放入不同格式的凭证。
而 CSRF 保护、Content-Type 等机制则是安全与数据解析的边界守门员。后续进入 OAuth2 的令牌传递、刷新、撤销流程时,这些 HTTP 层面的细节将继续扮演关键角色。