Spring gRPC集成Spring Security OAuth2

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

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

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

49bc623d74fc43ad94d8f855c012e23f~tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAgS-elng==_q75

Spring Authorization Server

Spring Authorization Server官方文档

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

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

client_credentials

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

增加依赖
<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

@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);
        }
    }

}
OAuth2TestApp

    @SpringBootApplication
    public class OAuth2TestApp {

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

    }

application.yml

server:
  port: 9088

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

logging:
  level:
    org.springframework.security: info
测试
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)

引入依赖

    <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>

具体代码

/**
 *
 * <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

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://127.0.0.1:9088/oauth2/jwks

方法拦截示例

@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

依赖

<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>

具体代码

@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

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

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

posted @ 2026-07-07 07:57  k↑  阅读(0)  评论(0)    收藏  举报