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. 实体体 :对于 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 层面的细节将继续扮演关键角色。

相关推荐
xcl09253 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐5 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长5 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn5 小时前
Spring 注解使用详解
java·spring
一条小小yu6 小时前
Spring IoC的理解
java·后端·spring
落魄实习生6 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈6 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白8 小时前
核心功能(Service接口)
java·mybatis
乌暮9 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习