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

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

相关推荐
IT_陈寒2 分钟前
Python线程池吞了异常还不告诉我,这谁顶得住啊
前端·人工智能·后端
小强库计算机毕业设计12 分钟前
SpringBoot+Vue3 学生宿舍管理系统实战
java·spring boot·后端·vue·学生宿舍管理系统
小狼18317 分钟前
实践6|SDD 实战:AI 写的规格被推翻了四次
后端·claude
篮框坏了18 分钟前
"DeepSeek Harness 实测:一毛钱干三活,但默认配置是个坑"
后端·ai编程
jobBridge2129 分钟前
大模型到底是怎么"想"的?我把 Transformer 拆开,发现它其实是个"接词狂魔"
人工智能·后端·编程语言
唐青枫1 小时前
看懂内存地址之后,才算真正入门 Zig:指针、切片与实战
后端
朋克洛德的码农1 小时前
Go并发-sync包四剑客:Mutex、RWMutex、WaitGroup、Once-从入门到原理
开发语言·后端·golang
fulton2 小时前
为什么不用现成的开源工具?NovelOps与6大AI写作工具横向对比
后端
fulton2 小时前
3个月踩坑实录:从想法到270章规划,AI写长篇到底要花多少成本?
后端
fulton2 小时前
为什么AI写到20章就开始"复制粘贴"自己?创意扰动机制详解
后端