利用Spring Security OAuth2 Resource Server保护内部霸王餐管理接口

利用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.rolesgroups 包含 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 Unauthorized403 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开发者团队,转载请注明出处!

<think> 我们正在讨论的是在传统Servlet模式下配置Spring Security OAuth2资源服务器(Resource Server)。根据用户需求,他想要一个使用传统Servlet模式(即基于Spring MVC,非响应式)的配置案例。 注意:由于Spring Cloud Gateway 4.0.9是响应式的,所以传统Servlet配置不适用于它。但用户明确要求传统Servlet模式,因此我们假设该配置用于一个基于Spring MVC的普通Spring Boot应用(例如一个微服务内部的安全配置)。 在传统Servlet应用中,我们使用`HttpSecurity`来配置安全,而不是响应式的`ServerHttpSecurity`。 根据引用[1]中提供的例子,我们可以看到一个使用不透明令牌(Opaque Token)的配置。但是用户可能也需要JWT的配置。我们将分别提供两种配置。 同时,引用[2]和引用[3]提供了一些关于OAuth2客户端注册和授权类型的背景信息,但我们的重点在资源服务器配置。 以下我们将分别给出使用JWT和不透明令牌(Opaque Token)的配置案例。 ### 1. 使用JWT的配置案例 ```java 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.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) ); return http.build(); } // 可选:自定义JWT转换器,将JWT中的声明转换为Spring Security的权限 private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); // 可以设置一个自定义的JWT转换器,用于从JWT中提取权限 // converter.setJwtGrantedAuthoritiesConverter(...); return converter; } } ``` 在`application.properties`或`application.yml`中配置JWT验证所需的信息(通常是issuer-uri或jwk-set-uri): ```properties spring.security.oauth2.resourceserver.jwt.issuer-uri=https://your-issuer-uri ``` ### 2. 使用不透明令牌(Opaque Token)的配置案例 ```java 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.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .opaqueToken(opaqueToken -> opaqueToken .introspector(myIntrospector()) // 需要自定义introspector或者通过配置属性 ) ); return http.build(); } // 通常我们不需要自己实现Introspector,可以通过配置文件设置introspection-uri和client信息 // 但这里为了完整,可以这样写,不过更推荐使用配置文件 /* @Bean public OpaqueTokenIntrospector introspector() { return new NimbusOpaqueTokenIntrospector("https://your-introspection-uri", "client-id", "client-secret"); } */ } ``` 然后在配置文件中配置不透明令牌的验证信息: ```properties spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://your-introspection-uri spring.security.oauth2.resourceserver.opaquetoken.client-id=your-client-id spring.security.oauth2.resourceserver.opaquetoken.client-secret=your-client-secret ``` ### 重要说明: - 上述配置适用于基于Servlet的Spring Boot应用(使用Spring MVC)。 - 在资源服务器配置中,我们使用`.oauth2ResourceServer()`来启用OAuth2资源服务器支持。 - 对于JWT,我们使用`.jwt()`配置,并可以自定义`JwtAuthenticationConverter`。 - 对于不透明令牌,我们使用`.opaqueToken()`配置,并需要指定令牌的验签端点(introspection endpoint)和客户端凭证。 ### 自定义权限验证 引用[1]中展示了使用`hasScope`来检查权限,我们可以这样使用: ```java import static org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; // 在授权规则中,我们可以这样写: .requestMatchers("/contacts/**").access(hasScope("contacts")) ``` 但是注意,`hasScope`是Spring Security OAuth2提供的一个工具方法,它要求令牌中包含相应的scope。在使用JWT时,默认情况下,scope在`scopes`声明或`scope`声明中(或者通过自定义转换器将其提取为权限)。 ### 总结 以上案例展示了在传统Servlet应用中配置OAuth2资源服务器的两种方式:JWT和不透明令牌。用户需要根据自己的授权服务器类型选择合适的方式,并在配置文件中提供相应的端点信息和客户端凭证。 §§相关问题§§ 1. 如何在传统Servlet应用中自定义JWT的权限提取? 2. 不透明令牌和JWT在资源服务器配置中的主要区别是什么? 3. 如何同时支持JWT和不透明令牌? 4. 在资源服务器中如何实现方法级安全控制? </think> 以下是传统 Servlet 模式下的 Spring Security OAuth2 资源服务器配置案例: ### 传统 Servlet 模式配置案例 ```java 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.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; import static org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; @Configuration @EnableWebSecurity public class ResourceServerConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public/**").permitAll() .requestMatchers("/contacts/**").access(hasScope("contacts")) // 使用作用域控制 .requestMatchers("/messages/**").access(hasScope("messages")) .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(jwtAuthenticationConverter()) // JWT 转换器 ) // 或者使用不透明令牌: // .opaqueToken(opaqueToken -> {...}) ); return http.build(); } // 自定义 JWT 权限转换 @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); authoritiesConverter.setAuthorityPrefix("ROLE_"); // 设置权限前缀 authoritiesConverter.setAuthoritiesClaimName("scp"); // 设置声明字段名 converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return converter; } } ``` ### 配置文件示例 (`application.yml`) ```yaml spring: security: oauth2: resourceserver: jwt: issuer-uri: https://your-auth-server.com # JWT 发行者 URI jwk-set-uri: https://your-auth-server.com/oauth2/jwks # JWK 端点 # 或者使用不透明令牌配置: # opaque-token: # introspection-uri: https://your-auth-server.com/oauth2/introspect # client-id: your-client-id # client-secret: your-client-secret ``` ### 关键配置说明: 1. **授权配置**: - `requestMatchers()` 定义路径匹配规则 - `access(hasScope())` 实现基于 OAuth2 作用域的访问控制 - `authenticated()` 要求认证访问 2. **资源服务器类型**: - **JWT 模式**:使用 `.jwt()` 配置,需要提供 `issuer-uri` 或 `jwk-set-uri` - **不透明令牌模式**:使用 `.opaqueToken()` 配置,需要提供 `introspection-uri` 和客户端凭证 3. **自定义权限映射**: - `JwtAuthenticationConverter` 将 JWT 声明转换为权限 - 可配置声明字段名 (`scp`, `roles` 等) 和权限前缀 4. **客户端配置**(引用[2]): ```yaml spring: security: oauth2: client: registration: okta: client-id: client-id client-secret: client-secret client-authentication-method: client_secret_post authorization-grant-type: authorization_code ``` ### 认证流程说明: 1. 客户端携带访问令牌访问资源 2. 资源服务器通过配置的端点验证令牌: - JWT 模式:本地验证签名和声明 - 不透明令牌模式:调用 introspection 端点验证[^1] 3. 将令牌中的权限映射为 Spring Security 权限 4. 执行配置的访问控制规则 > **重要提示**:此配置仅适用于传统 Servlet 应用(Spring MVC),**不适用于** Spring Cloud Gateway 等响应式应用。在响应式环境中必须使用 `ServerHttpSecurity`[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值