Spring gRPC集成Spring Security OAuth2

你好呀,我的老朋友!我是老寇,跟我一起学习Spring gRPC集成Spring Security OAuth2

引入Spring Security组件,能够提供统一、可声明式的方法级安全,让每个方法都可以按需进行权限控制

基于Interceptor(拦截器)实现,需要提前将Token放入

Spring Authorization Server

Spring Authorization Server官方文档

OAuth 2.1 授权服务器功能为OAuth 2.1 授权框架中定义的授权服务器角色提供支持。

授权服务器功能提供了OAuth 2.1和OpenID Connect 1.0规范以及其他相关规范的实现。它为构建 OpenID Connect 1.0 身份提供程序和 OAuth 2.1 授权服务器产品提供了一个安全、轻量级且可定制的基础。

client_credentials

实现 client_credentials 认证模式的OAuth2认证服务

增加依赖
xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security-oauth2-authorization-server</artifactId>
    </dependency>
</dependencies>
具体代码

SecurityConfig

java 复制代码
@Configuration
public class SecurityConfig {

    @Bean
    RegisteredClientRepository registeredClientRepository(
            PasswordEncoder passwordEncoder,
            @Value("${app.oauth2.client.id}") String clientId,
            @Value("${app.oauth2.client.secret}") String clientSecret) {
        RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId(clientId)
                .clientSecret(passwordEncoder.encode(clientSecret))
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .scope("read")
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofHours(1))
                        .build())
                .build();

        return new InMemoryRegisteredClientRepository(client);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, _) -> jwkSelector.select(jwkSet);
    }

    @Bean
    JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    @Bean
    AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

    @Bean
    @Order(1)
    SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();
        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
        http.securityMatcher(endpointsMatcher);
        http.with(authorizationServerConfigurer, Customizer.withDefaults());
        http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
        return http.build();
    }

    private static RSAKey generateRsa() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyUse(KeyUse.SIGNATURE)
                .keyID(UUID.randomUUID().toString())
                .build();
    }

    private static KeyPair generateRsaKey() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            return keyPairGenerator.generateKeyPair();
        }
        catch (NoSuchAlgorithmException ex) {
            throw new IllegalStateException("RSA algorithm is not available", ex);
        }
    }

}
java 复制代码
OAuth2TestApp

    @SpringBootApplication
    public class OAuth2TestApp {

        public static void main(String[] args) {
            SpringApplication.run(OAuth2TestApp.class, args);
        }

    }

application.yml

yaml 复制代码
server:
  port: 9088

app:
  oauth2:
    client:
      id: client-id
      secret: client-secret

logging:
  level:
    org.springframework.security: info
测试
bash 复制代码
curl -u machine-client:machine-secret \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&scope=message.read" \
  http://localhost:9088/oauth2/token

Spring gRPC Server集成Spring Security

Spring gRPC Server Security文档地址

采用全局拦截@GlobalServerInterceptor,Spring OAuth2 Resource Server拦截Token并请求Spring Authorization Server的JWK Set Endpoint(/oauth2/jwks)

引入依赖

xml 复制代码
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-server</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.grpc</groupId>
    <artifactId>spring-grpc-core</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  </dependency>
</dependencies>

具体代码

java 复制代码
/**
 *
 * <a href=
 * "https://docs.springframework.org.cn/spring-security/reference/reactive/oauth2/resource-server/opaque-token.html">OAuth
 * 2.0 资源服务器不透明令牌</a>.
 * <p>
 * <a href="https://docs.spring.io/spring-grpc/reference/server.html">Spring gRPC 安全</a>
 * <p>
 * <a href=
 * "https://github.com/spring-projects/spring-grpc/tree/main/samples/grpc-oauth2">Spring
 * gRPC 安全例子</a>
 *
 * @author laokou
 */
@Configuration
@EnableMethodSecurity
class GrpcOAuth2Config {

    @Bean
    @GlobalServerInterceptor
    AuthenticationProcessInterceptor jwtAuthenticationProcessInterceptor(GrpcSecurity grpc) throws Exception {
       return grpc.authorizeRequests(requests -> requests.allRequests().authenticated())
          .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()))
          .build();
    }

}

appcation.yml

yaml 复制代码
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://127.0.0.1:9088/oauth2/jwks

方法拦截示例

java 复制代码
@Slf4j
@GrpcService
@RequiredArgsConstructor
public class xxxImpl extends xxxGrpc.xxxImplBase {

    private final String resultMsg = MessageUtils.getMessage(StatusCode.OK);

    @Override
    @PreAuthorize("hasAuthority('SCOPE_read')")
    public void generateId(GenerateIdRequest request, StreamObserver<xxxResponse> responseObserver) {
       GenerateIdResponse response = GenerateIdResponse.newBuilder()
          .setCode(StatusCode.OK)
          .setMsg(resultMsg)
          .setData(1)
          .build();
       responseObserver.onNext(response);
       responseObserver.onCompleted();
    }

}

Spring gRPC Client集成Spring Security

Spring gRPC Client Security文档地址

采用全局拦截@GlobalClientInterceptor,从Spring Authorization Server获取Token并塞入Interceptor

依赖

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

具体代码

java 复制代码
@Configuration
final class GrpcClientConfig {

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository registrations,
          OAuth2AuthorizedClientService service) {
       OAuth2AuthorizedClientProvider provider = OAuth2AuthorizedClientProviderBuilder.builder()
          .clientCredentials()
          .build();
       AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
             registrations, service);
       manager.setAuthorizedClientProvider(provider);
       return manager;
    }

    @Bean
    @GlobalClientInterceptor
    ClientInterceptor clientInterceptor(OAuth2AuthorizedClientManager authorizedClientManager) {
       return new BearerTokenAuthenticationInterceptor(() -> {
          // 塞入token(default来源于yaml配置,请查看yaml配置)
          OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId("default")
             .principal("system")
             .build();
          OAuth2AuthorizedClient client = authorizedClientManager.authorize(request);
          Assert.notNull(client, "authorized client is null");
          return client.getAccessToken().getTokenValue();
       });
    }

}

application.yml

yaml 复制代码
spring:
  security:
    oauth2:
      client:
        registration:
          default:
            client-id: client-id
            client-secret: client-secret
            client-name: OAuth2.1 M2M认证客户端
            authorization-grant-type: client_credentials
            client-authentication-method: client_secret_basic
            scope:
              - read
            provider: local
        provider:
          local:
            token-uri: http://127.0.0.1:9088/oauth2/token

我是老寇,我们下次再见啦!

相关推荐
凤山老林8 小时前
Spring Boot 启动冲刺:耗时剖析、懒加载策略与事件驱动初始化优化
spring boot·后端·python
朱涛的自习室9 小时前
我给 Gemini 4 和 DeepSeek 搞了套「末位淘汰」,AI 干活会更猛吗?(Day 3)
android·前端·后端
woniu_maggie11 小时前
SAP MDG主数据治理实战:从架构原理到项目实施全解析
后端
xiaoqiMikko11 小时前
Redis 实战(一):内存满了 —— 拒写、淘汰、被系统杀掉,以及删了 key 内存为什么不降(6.2.4 实测)
redis·后端
工程师小爱12 小时前
电芯尺寸合格,不等于电池包尺寸合格
后端
geovindu12 小时前
rust:Memento Pattern(续)
开发语言·后端·设计模式·rust·行为模式·备忘录模
闲云lazycloud12 小时前
第 1 课:Harness 与 Agent 运行时
人工智能·后端
熊哈哈O_o12 小时前
260+异构同步链路:DDL语法自动翻译在大规模数据同步中的实现原理
后端
用户98883218717413 小时前
别把 IOptionsSnapshot 注入单例:从生命周期看 .NET 配置读取
后端