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.1OpenID 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

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

相关推荐
颜进强3 分钟前
LangChain 从入门到实践:用最小案例理解 RAG 的 5 个核心抽象
前端·后端·ai编程
颜进强5 分钟前
MCP 从入门到实践:用 TypeScript 实现第一个 MCP Server
前端·后端·ai编程
Undoom8 分钟前
用搜索引擎 API 搭一个 SEO 关键词监控工具:定时追踪排名、广告和相关搜索
后端
程序大爆炸22 分钟前
gcsfuse的TypeCache
后端
心运软件25 分钟前
Python实战:中国大学排行榜数据采集与可视化大屏
后端·python
GISer_Jing1 小时前
前端转全栈须知后端知识
前端·后端·ai·前端框架
爱勇宝1 小时前
我做了一个排版器,也踩了一遍富文本复制的坑
前端·后端·微信
布朗克1681 小时前
Go 入门到精通-33-unsafe 与 CGO
开发语言·后端·golang·unsafe·cgo
铁皮饭盒1 小时前
面试官:如何用 Bun + JS 实现安全的文件 MCP 工具集
前端·javascript·后端
小Ti客栈2 小时前
Spring Boot 整合 Swagger2 和 Knife4j实现接口文档与可视化调试
java·spring boot·后端