Spring Security 7 企业级配置:认证、授权与过滤器链
前言
安全配置最容易出现两个极端:所有接口都公开,或者把网上的一段旧版 WebSecurityConfigurerAdapter 直接粘进项目。前者没有边界,后者在 Spring Security 7 中已经无法编译。
这一章从订单项目的最小安全闭环开始:公开接口无需登录,普通用户能读取自己的身份,管理员接口只有 ADMIN 角色可以访问。认证失败返回 JSON 401,权限不足返回 JSON 403。
读者需要完成前面的 Spring Boot 4 和 Web API 教程,并能运行 Maven 与 curl。完整代码在 dvsusan/spring-boot4-demo,第六章默认关闭,不影响前五章接口。
最终结果
完成后会得到一条明确的请求边界:
GET /api/v1/security/public -> 允许匿名访问
GET /api/v1/security/me -> 必须认证
GET /api/v1/security/admin -> 必须拥有 ROLE_ADMIN安全链只匹配 /api/v1/security/** 和 /api/v1/auth/**。项目中原有订单、数据库、缓存等接口仍然公开,因此学习本章不需要改动前 30 篇代码。

图 1:请求先经过过滤器链,再由方法级权限决定是否执行 Controller。
第 1 步:添加 Spring Security 依赖
在 demo-web/pom.xml 中加入三个依赖。本章真正用到的是 Security Starter,Resource Server 和 OAuth2 Client 会留给后续两章。
<!-- 第六章使用 Security 过滤器链、方法权限和密码编码器。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Resource Server 负责验证 JWT 签名、过期时间与声明。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- OAuth2 Client 用于企业 OIDC 单点登录,没有配置时不会启用登录链。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>依赖加入后,Boot 会自动启用安全过滤。为了避免影响前面章节,项目必须同时提供一条“关闭时全部放行”的链和一条“开启时执行认证”的链。
第 2 步:配置开关和教学账号
公共配置先保持关闭:
demo:
chapter6:
# 默认关闭安全链,保证前五章接口仍可直接访问。
enabled: falseapplication-chapter6.yml 在启用 Profile 后打开安全链:
demo:
chapter6:
enabled: true
oauth2:
# 企业 OIDC 参数齐全后再打开。
enabled: ${DEMO_OAUTH2_ENABLED:false}教学账号位于 Chapter6UserDetailsService,密码全部经过 BCrypt,不保存明文:
this.users = Map.of(
"admin", new Chapter6UserDetails(
"admin",
passwordEncoder.encode("admin123"),
"tenant_acme",
"HQ",
List.of("ADMIN"),
List.of(
"order:read",
"order:write",
"order:delete",
"payment:write",
"data:all",
"data:pii:read",
"data:cost:read"
)
),
"analyst", new Chapter6UserDetails(
"analyst",
passwordEncoder.encode("analyst123"),
"tenant_acme",
"SALES",
List.of("ANALYST"),
List.of(
"order:read",
"data:department",
"data:pii:read"
)
)
);角色通常表达“这个人是谁”,例如管理员或分析员;权限表达“能做什么”,例如 order:read、order:delete。企业系统应优先检查权限,角色只用于稳定的粗粒度入口。
第 3 步:编写 Security 7 过滤器链
安全配置的核心片段如下:
http.securityMatcher("/api/v1/security/**", "/api/v1/auth/**")
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(
"/api/v1/security/public",
"/api/v1/auth/login",
"/api/v1/auth/refresh"
).permitAll()
.requestMatchers("/api/v1/security/admin")
.hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(
jwtAuthenticationConverter
))
)
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint((request, response, exception) ->
writeSecurityError(
response,
objectMapper,
401,
"UNAUTHORIZED",
"请先登录"
)
)
.accessDeniedHandler((request, response, exception) ->
writeSecurityError(
response,
objectMapper,
403,
"FORBIDDEN",
"当前身份没有访问权限"
)
)
);securityMatcher 限定这条链的职责范围。permitAll 必须放在 authenticated 前面,规则按声明顺序匹配。hasRole("ADMIN") 会自动补上 ROLE_ 前缀,因此账号权限必须保存为 ROLE_ADMIN。
本章保留 HTTP Basic 只是为了便于用 curl 观察认证流程。下一章会接入 JWT;生产系统通常只保留一种主要认证方式。
第 4 步:增加身份和角色接口
SecurityController 完整代码如下:
@RestController
@RequestMapping("/api/v1/security")
@ConditionalOnProperty(
prefix = "demo.chapter6",
name = "enabled",
havingValue = "true"
)
public class SecurityController {
@GetMapping("/public")
public ApiResponse<String> publicEndpoint() {
return ApiResponse.success("public");
}
@GetMapping("/me")
public ApiResponse<SecurityIdentity> me(Authentication authentication) {
return ApiResponse.success(SecurityIdentity.from(authentication));
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public ApiResponse<String> admin() {
return ApiResponse.success("admin-only");
}
}Authentication 来自可信的 SecurityContext,不能接受客户端传来的 username 或 role 参数。@PreAuthorize 让权限规则贴近业务方法,但必须配合 @EnableMethodSecurity。
第 5 步:启动项目
在项目根目录执行:
mvn clean package -DskipTests
java -jar demo-web/target/demo-web-1.0.0-SNAPSHOT.jar \
--spring.profiles.active=chapter6 \
--server.port=18106看到 Started DemoApplication 后,先验证公开和未登录路径:
curl -i http://localhost:18106/api/v1/security/public
curl -i http://localhost:18106/api/v1/security/me预期状态码分别是 200 和 401。未登录响应使用统一结构:
{
"code": "UNAUTHORIZED",
"message": "请先登录",
"data": null
}第 6 步:验证角色边界
用分析员账号访问个人身份:
curl -i -u analyst:analyst123 \
http://localhost:18106/api/v1/security/me实际响应中的关键字段:
{
"code": "0",
"data": {
"username": "analyst",
"tenantId": "tenant_acme",
"departmentId": "SALES",
"roles": ["ANALYST"],
"permissions": ["order:read", "data:department", "data:pii:read"]
}
}再验证权限不足和管理员成功:
curl -i -u clerk:clerk123 \
http://localhost:18106/api/v1/security/admin
curl -i -u admin:admin123 \
http://localhost:18106/api/v1/security/admin第一条返回 403,第二条返回 200。到这里,URL 级规则、认证和角色授权已经成为可运行功能。
第 7 步:运行自动化测试
mvn -pl demo-web -am \
-Dtest=Chapter6SecurityIntegrationTest \
-Dsurefire.failIfNoSpecifiedTests=false \
test当前测试覆盖公开路径、未登录、Basic 登录、角色拒绝和管理员成功。第六章完整安全测试共 8 个,额外覆盖 OIDC 跳转、JWT、租户、数据权限、签名与幂等场景。
为什么安全配置默认关闭?
教程项目包含多种技术,每个读者可能只想运行其中一章。如果 Security Starter 一加入就拦截所有 URL,前五章的订单、数据库和缓存示例会突然返回 401,读者无法判断是新功能还是旧功能坏了。
课程使用 @ConditionalOnProperty 明确切换模式。生产项目不能长期保留“一键全放行”的配置;上线前应把安全链改为默认拒绝,并且让关闭开关只存在于测试环境。
常见问题排查
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 所有旧接口突然返回 401 | 没有提供关闭状态的安全链 | 检查 chapter6.enabled=false 的 permitAll 链 |
| 管理员仍返回 403 | 角色没有 ROLE_ 前缀 | 使用 hasAuthority("ADMIN") 或保存 ROLE_ADMIN |
| 方法权限仍返回 500 | 没有处理 AuthorizationDeniedException | 增加 403 统一异常映射 |
| Basic 账号始终认证失败 | 密码编码器不一致 | 确认存储和校验共用同一个 PasswordEncoder |
permitAll 没有生效 | 规则顺序写反 | 将公开路径放在 anyRequest 之前 |
练习题
增加 auditor 用户,只授予 order:read,然后创建 /api/v1/security/rbac/orders/{orderNo} 接口。验收标准是 auditor 能查询、不能删除;clerk 没有读取权限时返回 403;未登录返回 401。
总结
Spring Security 7 的核心模型没有消失,只是旧式继承配置被淘汰。SecurityFilterChain 负责 URL 边界,UserDetailsService 负责加载身份,PasswordEncoder 负责密码校验,@PreAuthorize 负责方法权限。
下一篇把 Basic 认证替换为短期 JWT 访问令牌,并增加刷新令牌轮换和退出撤销。
上一节:Excel 百万级批处理
下一节:JWT 登录与刷新
相关阅读:全局异常处理
返回课程目录
13 个企业级实战项目 · 源码 + 教程 + 答疑
简历包装指导AI Agent 实战高并发·微服务Offer 导向持续更新3000+ 同行交流1V1 答疑3 天包退
覆盖高并发、微服务、分布式与 AI Agent:100万QPS短链、企业智能知识库、商城微服务、SaaS点餐、秒杀、刷题小程序、代码生成器、AI Agent 等。券后 ¥129(原价 ¥159),不满意 3 天包退。
