Java框架快速入门: Spring Security+OAuth2之HTTP请求结构与认证基础

概述

在认证与授权的技术栈中,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 请求由纯文本构成,主要分为三大部分:

  1. 起始行 :包含请求方法、URI 和协议版本,例如 GET /api/demo HTTP/1.1
  2. 请求头:一组键值对,用于描述客户端环境、期望的响应格式、身份信息等。根据用途可细分为通用头、请求头、实体头,但它们在格式上完全一致
  3. 实体体 :对于 POSTPUTPATCH 等方法,请求可携带数据体,例如 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 防护,对所有状态改变的请求(如 POSTPUTDELETE)均要求携带一个由服务端生成的 _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 层面的细节将继续扮演关键角色。

相关推荐
君顾12 小时前
本地折扣卡 CPS 平台搭建:从系统架构到落地实战
java·开发语言·折扣卡
Java小白笔记3 小时前
Java 单元测试怎么写:JUnit 5、Mockito 与 Spring Boot 方法级实战
java·junit·单元测试
CoderYanger4 小时前
前端基础——JavaScript(WebAPI)(下篇)
java·开发语言·前端·javascript·程序人生·面试·职场和发展
lisin-lee-cooper4 小时前
简单聊聊 Spring 框架源码
java·后端·spring
一木 之林4 小时前
七、一-AI 工程实践、插件化调试与软件交付
java·linux·c++
边境悍匪4 小时前
蜗牛学苑 Java 智能体学习 Day38|Spring AI Alibaba2 思维导图复盘
java·学习·spring
Co_Hui4 小时前
Java 静态内部类、非静态内部类、匿名内部类的区别?
java
小白羊丨4 小时前
HTTP/2 如何缓解 HTTP/1.1 阻塞;长连接对 Nginx 有什么影响?
网络协议·nginx·http
Wang's Blog5 小时前
Java框架快速入门: Spring Security+OAuth2之自定义认证过滤器实现JSON登录
java·spring·json