Spring Boot: Spring Security 基础

最后更新:2026-08-26

Spring Security 是 Java 安全的行业标准——过滤器链、认证、授权三件套,守护 API 安全。

1. 你将学到


2. 一个产品经理的真实故事

(1) 痛点:接口裸奔

Charlie 在产品评审会上发现 OrderFlow 的所有 API 完全没有认证——任何人都能调用创建订单、删除商品的接口。更糟糕的是,Bob 在公网暴露了 Actuator 端点,导致生产环境数据库信息被扫描器获取。Charlie 要求立即加安全控制,Alice 需要最快的方案。

(2) Spring Security 的解法

Spring Security 几行配置就能保护 API:

JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/v1/orders/**").authenticated()
                .anyRequest().permitAll()
            )
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

(3) 收益

Alice 用 Spring Security 为 OrderFlow 加上认证授权后,管理接口只有 ADMIN 角色可访问,普通接口需要登录,Actuator 端点限制内网访问。Charlie 对安全合规表示满意。


3. Spring Security 架构

(1) 过滤器链

100%
sequenceDiagram
    participant Client
    participant Chain as SecurityFilterChain
    participant Auth as Authentication Filter
    participant Authz as Authorization Filter
    participant Controller

    Client->>Chain: HTTP Request
    Chain->>Auth: 1. Authentication
    alt Credentials Invalid
        Auth-->>Client: 401 Unauthorized
    end
    Auth->>Authz: 2. Authorization
    alt Access Denied
        Authz-->>Client: 403 Forbidden
    end
    Authz->>Controller: 3. Forward to Controller
    Controller-->>Client: 200 OK
核心概念 说明
SecurityFilterChain 一组过滤器的有序链,每个请求依次经过
Authentication 认证:确认"你是谁"
Authorization 授权:确认"你能做什么"
SecurityContext 安全上下文,持有当前用户信息
GrantedAuthority 权限/角色信息

(2) 核心组件关系

组件 职责 接口
AuthenticationManager 认证管理器 authenticate()
ProviderManager AuthenticationManager 默认实现 委托给 AuthenticationProvider
UserDetailsService 加载用户信息 loadUserByUsername()
PasswordEncoder 密码编码 encode() / matches()

4. SecurityFilterChain 配置

▶ 示例: 基础安全配置

JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/products/**").permitAll()
                .requestMatchers("/api/v1/orders/**").authenticated()
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://orderflow.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

输出:

TEXT 📖 仅展示
// 执行成功
配置项 说明 REST API 推荐
CSRF 跨站请求伪造防护 禁用(无状态 API 不需要)
CORS 跨域资源共享 配置允许的域名
Session 会话管理 STATELESS(无状态)
httpBasic HTTP 基本认证 开发/测试用
formLogin 表单登录 传统 Web 应用用

5. 基于内存的用户认证

▶ 示例: InMemoryUserDetailsManager

JAVA
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        UserDetails admin = User.builder()
            .username("alice")
            .password(encoder.encode("admin123"))
            .roles("ADMIN", "CUSTOMER")
            .build();

        UserDetails customer = User.builder()
            .username("bob")
            .password(encoder.encode("customer123"))
            .roles("CUSTOMER")
            .build();

        return new InMemoryUserDetailsManager(admin, customer);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

输出:

TEXT 📖 仅展示
// 执行成功
PasswordEncoder 安全性 适用场景
BCryptPasswordEncoder 高(自带盐值) 生产环境推荐
Argon2PasswordEncoder 最高(GPU 抵抗) 高安全要求
NoOpPasswordEncoder 无(明文) 仅测试用
🔒 安全: 永远不要在生产环境使用 NoOpPasswordEncoder。BCrypt 是最低标准,Argon2 是更高标准。

▶ 示例: 测试认证

BASH
# Access without credentials -> 401
curl http://localhost:8080/api/v1/orders

# Access with customer credentials -> 200
curl -u bob:customer123 http://localhost:8080/api/v1/orders

# Access admin endpoint with customer -> 403
curl -u bob:customer123 http://localhost:8080/api/v1/admin/dashboard

# Access admin endpoint with admin -> 200
curl -u alice:admin123 http://localhost:8080/api/v1/admin/dashboard

输出:

TEXT 📖 仅展示
{"status":"ok","data":{}}

6. 方法级授权

▶ 示例: @PreAuthorize 方法授权

JAVA
@Service
public class OrderService {

    @Transactional(readOnly = true)
    @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')")
    public Order getOrder(Long orderId) {
        return orderRepository.findById(orderId).orElseThrow();
    }

    @Transactional
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(Long orderId) {
        orderRepository.deleteById(orderId);
    }

    @Transactional
    @PreAuthorize("hasRole('ADMIN') or #customerId == authentication.principal.id")
    public List<Order> getCustomerOrders(Long customerId) {
        return orderRepository.findByCustomerId(customerId);
    }
}

输出:

TEXT 📖 仅展示
// 执行成功
注解 特点 适用场景
@PreAuthorize SpEL 表达式,访问方法前检查 最灵活,推荐
@PostAuthorize SpEL 表达式,方法执行后检查 需要根据返回值判断权限
@Secured 角色列表,不支持 SpEL 简单角色检查
@RolesAllowed JSR-250 标准注解 跨框架兼容
📌 重点: 使用方法级授权需要在配置类上加 @EnableMethodSecurity

▶ 示例: @EnableMethodSecurity 配置

JAVA
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    // ... SecurityFilterChain and UserDetailsService beans
}

输出:

TEXT 📖 仅展示
// 执行成功

7. 路径授权规则速查

匹配方法 说明 示例
requestMatchers(String) Ant 风格路径匹配 /api/v1/orders/**
requestMatchers(HttpMethod, String) 限定 HTTP 方法 POST /api/v1/orders
anyRequest() 匹配所有请求 放在最后作为兜底
授权方法 说明
permitAll() 允许所有人访问
authenticated() 需要认证
hasRole("ADMIN") 需要 ADMIN 角色
hasAnyRole("A", "B") 需要任一角色
denyAll() 拒绝所有访问

▶ 示例: 按角色限制接口

JAVA
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf -> csrf.disable())
        .authorizeHttpRequests(auth -> auth
            .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
            .requestMatchers(HttpMethod.POST, "/api/v1/products").hasRole("ADMIN")
            .requestMatchers(HttpMethod.PUT, "/api/v1/products/**").hasRole("ADMIN")
            .requestMatchers(HttpMethod.DELETE, "/api/v1/products/**").hasRole("ADMIN")
            .requestMatchers("/api/v1/orders/**").hasAnyRole("ADMIN", "CUSTOMER")
            .requestMatchers("/actuator/**").hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

输出:

TEXT 📖 仅展示
// 执行成功

8. 综合示例:OrderFlow 安全配置

JAVA
// SecurityConfig.java
package com.orderflow.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
                .requestMatchers("/api/v1/orders/**").hasAnyRole("CUSTOMER", "ADMIN")
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/actuator/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        var admin = User.builder()
            .username("alice").password(encoder.encode("admin123"))
            .roles("ADMIN", "CUSTOMER").build();
        var customer = User.builder()
            .username("bob").password(encoder.encode("pass123"))
            .roles("CUSTOMER").build();
        return new InMemoryUserDetailsManager(admin, customer);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

// OrderController with method-level security
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

    @GetMapping
    @PreAuthorize("hasAnyRole('CUSTOMER', 'ADMIN')")
    public List<Order> listOrders() { /* ... */ }

    @GetMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(#id, authentication)")
    public Order getOrder(@PathVariable Long id) { /* ... */ }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteOrder(@PathVariable Long id) { /* ... */ }
}

❓ 常见问题

Q 为什么 REST API 要禁用 CSRF?
A CSRF 保护依赖 Cookie + Session 机制。REST API 通常是无状态的(Token 认证),不使用 Cookie,CSRF 攻击无法成功,所以可以禁用。
Q hasRole 和 hasAuthority 有什么区别?
A hasRole("ADMIN") 会自动加 "ROLE_" 前缀,检查 ROLE_ADMIN 权限。hasAuthority("ADMIN") 不加前缀,直接检查 ADMIN 权限。推荐统一使用 hasRole。
Q InMemoryUserDetailsManager 能用于生产吗?
A 不能。内存用户管理仅用于开发和测试。生产环境需要自定义 UserDetailsService 从数据库加载用户信息。
Q 如何获取当前登录用户信息?
A 三种方式:1)SecurityContextHolder.getContext().getAuthentication();2)Controller 方法参数注入 Principal principal;3)@AuthenticationPrincipal UserDetails user。推荐第三种。
Q Spring Security 的过滤器顺序可以自定义吗?
A 可以,通过 @Order 控制 SecurityFilterChain 的顺序。多个 SecurityFilterChain 可以匹配不同的请求路径。
Q httpBasic 和 formLogin 该选哪个?
A REST API 用 httpBasic 或 Bearer Token;传统 Web 应用用 formLogin。本课使用 httpBasic 作为入门,后续课程切换到 JWT。

📖 小节


📝 作业

  1. 基础题(难度⭐):为 OrderFlow 配置 Spring Security,实现商品查询 permitAll、订单操作 authenticated、管理接口 hasRole("ADMIN"),使用 httpBasic 和内存用户测试。

  2. 进阶题(难度⭐⭐):实现 @PreAuthorize 方法级授权——用户只能查看自己的订单,ADMIN 可以查看所有订单。提示:创建 OrderSecurity 辅助 Bean 在 SpEL 中引用。

  3. 挑战题(难度⭐⭐⭐):实现自定义 UserDetailsService 从数据库(JPA)加载用户和角色信息,替换 InMemoryUserDetailsManager,思考密码存储和用户注册流程的安全设计。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏