利用Spring Security OAuth2 Resource Server保护内部霸王餐管理接口
在“吃喝不愁”App的后端系统中,霸王餐活动管理接口(如创建、审核、发放)属于高权限操作,必须严格限制访问来源。为避免使用传统 Session 或 API Key 的安全缺陷,我们采用 Spring Security OAuth2 Resource Server 模式,基于 JWT 令牌验证请求合法性,确保只有经过授权的服务或管理员可调用。
1. 项目依赖配置
使用 Spring Boot 3.x + Spring Security 6,Maven 依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 配置Resource Server使用JWT
在 application.yml 中指定公钥或 JWKS URI(以 Auth0 / Keycloak / 自建 Auth Server 为例):
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://your-auth-server/.well-known/jwks.json
# 或使用本地公钥:public-key-location: classpath:public.key
3. 定义受保护的霸王餐管理接口
package baodanbao.com.cn.bajie.controller;
import baodanbao.com.cn.bajie.model.FreeMealRequest;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/internal/free-meal")
public class FreeMealAdminController {
@PostMapping("/create")
public String createFreeMeal(@RequestBody FreeMealRequest request) {
// 创建霸王餐活动逻辑
return "Created";
}
@PutMapping("/approve/{id}")
public String approveFreeMeal(@PathVariable Long id) {
// 审核通过逻辑
return "Approved";
}
@DeleteMapping("/cancel/{id}")
public String cancelFreeMeal(@PathVariable Long id) {
// 取消活动逻辑
return "Cancelled";
}
}
4. 配置Security过滤链与权限规则
package baodanbao.com.cn.bajie.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.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/internal/free-meal/create").hasAuthority("SCOPE_free_meal:write")
.requestMatchers("/api/internal/free-meal/approve/**").hasRole("ADMIN")
.requestMatchers("/api/internal/free-meal/cancel/**").hasAnyRole("ADMIN", "OPERATOR")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> {}));
return http.build();
}
}
注意:
hasAuthority("SCOPE_free_meal:write")对应 JWT 中的scope声明(如"scope": "free_meal:write profile")。hasRole("ADMIN")要求 JWT 的realm_access.roles或groups包含ROLE_ADMIN(Spring Security 会自动加前缀ROLE_)。
5. 自定义JWT声明映射(适配不同IDP)
若认证服务器(如 Auth0)将角色放在 https://baodanbao.com.cn/roles 自定义字段中,需重写 GrantedAuthoritiesConverter:
package baodanbao.com.cn.bajie.config;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class CustomJwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
List<String> roles = jwt.getClaimAsStringList("https://baodanbao.com.cn/roles");
if (roles == null) {
return Collections.emptyList();
}
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
}
并在 Security 配置中注册:
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(new CustomJwtGrantedAuthoritiesConverter());
return converter;
}
// 在 filterChain 中补充:
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
)
6. 测试接口调用(带有效JWT)
使用 curl 模拟携带合法 Token 的请求:
curl -X POST http://localhost:8080/api/internal/free-meal/create \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx" \
-H "Content-Type: application/json" \
-d '{"shopId":"1001","quota":50}'
若 Token 缺失、过期或权限不足,将返回 401 Unauthorized 或 403 Forbidden。
7. 本地开发支持:Mock JWT(仅测试环境)
为便于本地调试,可启用 Mock JWT:
@Profile("dev")
@Configuration
public class DevSecurityConfig {
@Bean
@Primary
public JwtDecoder jwtDecoder() {
return token -> Jwt.withTokenValue(token)
.header("alg", "none")
.claim("sub", "dev-user")
.claim("https://baodanbao.com.cn/roles", List.of("ADMIN"))
.claim("scope", "free_meal:write")
.build();
}
}
并激活 dev profile。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
8964

被折叠的 条评论
为什么被折叠?



