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

}

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)

引入依赖
复制代码
<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

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

相关推荐
西凉的悲伤1 个月前
Spring Security + JWT 登录认证完整实践指南
java·后端·spring·spring security·jwt
Micro麦可乐1 个月前
最新Spring Security实战教程(十)权限表达式进阶 - 在SpEL在安全控制中的高阶魔法
java·spring boot·后端·spring·spring security·spel表达式
消失的旧时光-19431 个月前
企业认证与安全体系(五):Spring Security + JWT + Redis 企业级认证实战
redis·安全·spring·spring security·jwt
消失的旧时光-19431 个月前
企业认证与安全体系(四):企业登录认证流程全解析——JWT、Redis、Spring Security 如何协同工作?
redis·安全·spring·spring security·jwt
智研数智工坊1 个月前
SpringBoot4.0.6 + Security7.x + JWT 最新完整实战|无状态权限认证、统一异常处理、可直接落地
java·spring boot·spring security·jwt·权限认证
段ヤシ.1 个月前
回顾Java知识点,面试题汇总Day17(持续更新)
java·springboot·spring security·shiro·mybatis-plus·jdbctemplate·spring data jpa
梵得儿SHI1 个月前
SpringCloud 进阶拓展:Spring Security OAuth2+JWT 微服务统一认证授权全实战|生产级方案 + 源码解析 + 踩坑实录
spring·spring cloud·微服务·spring security·jwt·oauth2·统一认证授权
Devin~Y2 个月前
互联网大厂 Java 面试实录:JVM、Spring Boot、MyBatis、Redis、Kafka、Spring AI、K8s 全链路追问小Y
java·jvm·spring boot·redis·kafka·mybatis·spring security
小坏讲微服务2 个月前
SpringBoot4.0整合Spring Security+MyBatis Plus完整权限框架实现
java·spring·mybatis·spring security·mybatis plus·springboot4.0