From ff40f5b3231da57026aea5f94f9b4b8e97be41c8 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 22 Jan 2025 12:16:16 +0000 Subject: [PATCH] Add authorize requests filters --- .../sample/GrpcServerApplicationTests.java | 3 - samples/grpc-secure/build.gradle | 6 +- samples/grpc-secure/pom.xml | 6 +- .../grpc/sample/GrpcServerApplication.java | 32 ++-- .../sample/GrpcServerApplicationTests.java | 5 +- samples/pom.xml | 1 + spring-grpc-core/pom.xml | 5 + .../AuthenticationServerInterceptor.java | 59 +++--- .../grpc/server/security/CallContext.java | 17 +- .../grpc/server/security/CallMatcher.java | 2 +- .../grpc/server/security/GrpcSecurity.java | 173 +++++++++++++++++- .../security/RequestMapperConfigurer.java | 18 +- .../SecurityContextServerInterceptor.java | 8 +- spring-grpc-spring-boot-autoconfigure/pom.xml | 2 +- .../GrpcSecurityAutoConfiguration.java | 19 +- 15 files changed, 282 insertions(+), 74 deletions(-) diff --git a/samples/grpc-reactive/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java b/samples/grpc-reactive/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java index 494c8d8..31615fe 100644 --- a/samples/grpc-reactive/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java +++ b/samples/grpc-reactive/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java @@ -1,7 +1,5 @@ package org.springframework.grpc.sample; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.Test; @@ -16,7 +14,6 @@ import org.springframework.grpc.sample.proto.HelloReply; import org.springframework.grpc.sample.proto.HelloRequest; import org.springframework.grpc.sample.proto.ReactorSimpleGrpc; import org.springframework.grpc.sample.proto.ReactorSimpleGrpc.ReactorSimpleStub; -import org.springframework.grpc.sample.proto.SimpleGrpc; import org.springframework.grpc.test.AutoConfigureInProcessTransport; import org.springframework.grpc.test.LocalGrpcPort; import org.springframework.test.annotation.DirtiesContext; diff --git a/samples/grpc-secure/build.gradle b/samples/grpc-secure/build.gradle index 6b748a5..ae43684 100644 --- a/samples/grpc-secure/build.gradle +++ b/samples/grpc-secure/build.gradle @@ -1,13 +1,13 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.4.0' + id 'org.springframework.boot' version '3.4.1' id 'io.spring.dependency-management' version '1.1.6' id 'org.graalvm.buildtools.native' version '0.10.3' id 'com.google.protobuf' version '0.9.4' } group = 'com.example' -version = '0.3.0-SNAPSHOT' +version = '0.4.0-SNAPSHOT' java { toolchain { @@ -24,7 +24,7 @@ repositories { dependencyManagement { imports { - mavenBom 'org.springframework.grpc:spring-grpc-dependencies:0.3.0-SNAPSHOT' + mavenBom 'org.springframework.grpc:spring-grpc-dependencies:0.4.0-SNAPSHOT' } } diff --git a/samples/grpc-secure/pom.xml b/samples/grpc-secure/pom.xml index 692053d..226cd34 100644 --- a/samples/grpc-secure/pom.xml +++ b/samples/grpc-secure/pom.xml @@ -6,12 +6,12 @@ org.springframework.boot spring-boot-starter-parent - 3.4.0 + 3.4.1 org.springframework.grpc grpc-secure-sample - 0.3.0-SNAPSHOT + 0.4.0-SNAPSHOT Spring gRPC Server Sample Demo project for Spring gRPC @@ -38,7 +38,7 @@ org.springframework.grpc spring-grpc-dependencies - 0.3.0-SNAPSHOT + 0.4.0-SNAPSHOT pom import diff --git a/samples/grpc-secure/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java b/samples/grpc-secure/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java index e11b211..7a6bc98 100644 --- a/samples/grpc-secure/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java +++ b/samples/grpc-secure/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java @@ -40,21 +40,23 @@ public class GrpcServerApplication { @GlobalServerInterceptor public ServerInterceptor securityInterceptor(GrpcSecurity security) throws Exception { return security - .authorizeRequests(requests -> requests - .methods("Simple/StreamHello").hasAuthority("ROLE_ADMIN") - .methods("Simple/SayHello").hasAuthority("ROLE_USER") - .allRequests().permitAll()) - .httpBasic(withDefaults()) - .preauth(withDefaults()) - .authenticationExtractor((headers, attributes) -> { - String user = headers.get(USER_KEY); - if (user != null) { - return new PreAuthenticatedAuthenticationToken(user, "N/A", - AuthorityUtils.createAuthorityList("ROLE_" + user.toUpperCase())); - } - return null; - }) - .build(); + .authorizeRequests(requests -> requests.methods("Simple/StreamHello") + .hasAuthority("ROLE_ADMIN") + .methods("Simple/SayHello") + .hasAuthority("ROLE_USER") + .allRequests() + .permitAll()) + .httpBasic(withDefaults()) + .preauth(withDefaults()) + .authenticationExtractor((headers, attributes) -> { + String user = headers.get(USER_KEY); + if (user != null) { + return new PreAuthenticatedAuthenticationToken(user, "N/A", + AuthorityUtils.createAuthorityList("ROLE_" + user.toUpperCase())); + } + return null; + }) + .build(); } diff --git a/samples/grpc-secure/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java b/samples/grpc-secure/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java index 3349ee1..bc8bcc4 100644 --- a/samples/grpc-secure/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java +++ b/samples/grpc-secure/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java @@ -38,8 +38,7 @@ import io.grpc.StatusRuntimeException; public class GrpcServerApplicationTests { public static void main(String[] args) { - new SpringApplicationBuilder(GrpcServerApplication.class, ExtraConfiguration.class) - .run(args); + new SpringApplicationBuilder(GrpcServerApplication.class, ExtraConfiguration.class).run(args); } @Autowired @@ -115,7 +114,7 @@ public class GrpcServerApplicationTests { @Lazy SimpleGrpc.SimpleBlockingStub basic(GrpcChannelFactory channels) { return SimpleGrpc.newBlockingStub(channels.createChannel("basic", ChannelBuilderOptions.defaults() - .withInterceptors(List.of(new BasicAuthenticationInterceptor("user", "user"))))); + .withInterceptors(List.of(new BasicAuthenticationInterceptor("user", "user"))))); } @Bean diff --git a/samples/pom.xml b/samples/pom.xml index 7c27676..d30666a 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -17,6 +17,7 @@ grpc-server + grpc-secure grpc-reactive grpc-server-netty-shaded grpc-tomcat diff --git a/spring-grpc-core/pom.xml b/spring-grpc-core/pom.xml index 065d761..b4d7acc 100644 --- a/spring-grpc-core/pom.xml +++ b/spring-grpc-core/pom.xml @@ -32,6 +32,11 @@ spring-security-web true + + org.springframework.security + spring-security-config + true + io.grpc grpc-netty diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationServerInterceptor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationServerInterceptor.java index 091d94d..7dc4684 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationServerInterceptor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationServerInterceptor.java @@ -19,6 +19,7 @@ import org.springframework.core.Ordered; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; @@ -33,12 +34,9 @@ import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; /** - * An interceptor that extracts the authentication credentials from the gRPC - * request - * headers and metadata, authenticates the user, and sets the authentication in - * the - * SecurityContext. This interceptor should be registered with the gRPC server - * to handle + * An interceptor that extracts the authentication credentials from the gRPC request + * headers and metadata, authenticates the user, and sets the authentication in the + * SecurityContext. This interceptor should be registered with the gRPC server to handle * authentication. * * @author Dave Syer @@ -49,7 +47,7 @@ public class AuthenticationServerInterceptor implements ServerInterceptor, Order private final GrpcAuthenticationExtractor extractor; - private final AuthorizationManager authorizationManager; + private AuthorizationManager authorizationManager; @Override public int getOrder() { @@ -68,42 +66,51 @@ public class AuthenticationServerInterceptor implements ServerInterceptor, Order ServerCallHandler next) { SecurityContext securityContext = SecurityContextHolder.getContext(); Authentication user = this.extractor.extract(headers, call.getAttributes()); - Authentication authenticated; if (user != null) { - authenticated = this.authenticationManager.authenticate(user); - } else { - authenticated = new AnonymousAuthenticationToken("anonymous", "anonymous", - AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); + user = this.authenticationManager.authenticate(user); + securityContext.setAuthentication(user); } - securityContext.setAuthentication(authenticated); - CallContext context = new CallContext(headers, call.getAttributes(), call.getMethodDescriptor()); - if (this.authorizationManager != null && authenticated != null) { - return new AuthenticationListener(next.startCall(call, headers), this.authorizationManager, context, - authenticated); + + if (this.authorizationManager != null) { + if (user == null) { + user = new AnonymousAuthenticationToken("anonymous", "anonymous", + AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); + } + return new AuthenticatedListener(next.startCall(call, headers), this.authorizationManager, + new CallContext(headers, call.getAttributes(), call.getMethodDescriptor()), user); } - return next.startCall(call, headers); + return new AuthenticatedListener(next.startCall(call, headers), null, + new CallContext(headers, call.getAttributes(), call.getMethodDescriptor()), user); } - static class AuthenticationListener extends ForwardingServerCallListener { + static class AuthenticatedListener extends ForwardingServerCallListener { private final Listener delegate; - private final AuthorizationManager authorizationManager; + private final CallContext context; + private final Authentication authentication; - AuthenticationListener(io.grpc.ServerCall.Listener delegate, - AuthorizationManager authorizationManager, CallContext context, - Authentication authenticated) { + private final AuthorizationManager authorizationManager; + + AuthenticatedListener(io.grpc.ServerCall.Listener delegate, + AuthorizationManager authorizationManager, CallContext context, Authentication user) { this.delegate = delegate; this.authorizationManager = authorizationManager; this.context = context; - this.authentication = authenticated; + this.authentication = user; } @Override public void onReady() { - if (!this.authorizationManager.authorize(() -> authentication, this.context).isGranted()) { - throw new AccessDeniedException("not allowed"); + if (this.authentication == null || !this.authentication.isAuthenticated() + || this.authentication instanceof AnonymousAuthenticationToken) { + throw new BadCredentialsException("not authenticated"); + } + else { + if (!this.authorizationManager.authorize(() -> this.authentication, this.context).isGranted()) { + throw new AccessDeniedException("not allowed"); + } } super.onReady(); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallContext.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallContext.java index 2a85dbe..5ce674e 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallContext.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallContext.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.grpc.server.security; import io.grpc.Attributes; @@ -5,4 +20,4 @@ import io.grpc.Metadata; import io.grpc.MethodDescriptor; public record CallContext(Metadata headers, Attributes attributes, MethodDescriptor method) { -} \ No newline at end of file +} diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallMatcher.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallMatcher.java index 368b3e5..6f757c4 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallMatcher.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/CallMatcher.java @@ -15,7 +15,7 @@ */ package org.springframework.grpc.server.security; -public interface CallMatcher { +interface CallMatcher { CallMatcher ALL = (context) -> true; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java index 34bcdf2..78f5fd2 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java @@ -15,22 +15,187 @@ */ package org.springframework.grpc.server.security; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ObservationAuthenticationManager; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.ObjectPostProcessor; +import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.util.Assert; + +import io.grpc.Attributes; import io.grpc.Metadata; +import io.micrometer.observation.ObservationRegistry; /** - * Defines constants and utilities for working with gRPC security. + * The GrpcSecurity class is responsible for configuring the security + * settings for a gRPC server. It provides methods to configure authentication providers, + * user details service, and authentication extractors. + * + * The class also defines some static constants, such as the + * AUTHORIZATION_KEY can be used in your security configuration. + * + * The class also provides various methods to configure different authentication + * mechanisms, such as pre-authentication, HTTP basic authentication, and custom + * authentication extractors. * * @author Dave Syer */ -public final class GrpcSecurity { +public final class GrpcSecurity + extends AbstractConfiguredSecurityBuilder { /** - * Constant for the Authorization header. + * A constant key used for storing and retrieving the "Authorization" header from gRPC + * metadata. This key is used to handle authorization information in gRPC requests. + * + *

+ * The key is defined with the name "Authorization" and uses the ASCII string + * marshaller for encoding and decoding the header value. + *

*/ public static final Metadata.Key AUTHORIZATION_KEY = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); - private GrpcSecurity() { + /** + * The order value for the context filter in the gRPC security framework. This + * constant defines the position of the context filter in the filter chain. A lower + * value indicates higher precedence. + */ + public static final int CONTEXT_FILTER_ORDER = 0; + + private AuthenticationManager authenticationManager; + + private List authenticationExtractors = new ArrayList<>(); + + private AuthorizationManager authorizationManager; + + public GrpcSecurity(ObjectPostProcessor objectPostProcessor, + AuthenticationManagerBuilder authenticationBuilder, ApplicationContext context) { + super(objectPostProcessor); + setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder); + setSharedObject(ApplicationContext.class, context); + } + + private ObservationRegistry getObservationRegistry() { + ApplicationContext context = getContext(); + String[] names = context.getBeanNamesForType(ObservationRegistry.class); + if (names.length == 1) { + return (ObservationRegistry) context.getBean(names[0]); + } + return ObservationRegistry.NOOP; + } + + private ApplicationContext getContext() { + return getSharedObject(ApplicationContext.class); + } + + @Override + protected AuthenticationServerInterceptor performBuild() throws Exception { + if (this.authenticationManager != null) { + setSharedObject(AuthenticationManager.class, this.authenticationManager); + } + else { + ObservationRegistry registry = getObservationRegistry(); + AuthenticationManager manager = getAuthenticationRegistry().build(); + if (!registry.isNoop() && manager != null) { + setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager)); + } + else { + setSharedObject(AuthenticationManager.class, manager); + } + } + this.authenticationExtractors.sort(AnnotationAwareOrderComparator.INSTANCE); + return new AuthenticationServerInterceptor(getSharedObject(AuthenticationManager.class), + new CompositeAuthenticationExtractor(this.authenticationExtractors), this.authorizationManager); + } + + public GrpcSecurity authenticationProvider(AuthenticationProvider authenticationProvider) { + getAuthenticationRegistry().authenticationProvider(authenticationProvider); + return this; + } + + public GrpcSecurity userDetailsService(UserDetailsService userDetailsService) throws Exception { + getAuthenticationRegistry().userDetailsService(userDetailsService); + return this; + } + + public GrpcSecurity preauth(Customizer> customizer) throws Exception { + customizer.customize(getOrApply(new PreAuthConfigurer<>(getAuthenticationRegistry(), getContext()))); + authenticationExtractor(new SslContextPreAuthenticationExtractor()); + return this; + } + + public GrpcSecurity httpBasic(Customizer> customizer) throws Exception { + customizer.customize(getOrApply(new HttpBasicConfigurer<>(getAuthenticationRegistry(), getContext()))); + authenticationExtractor(new HttpBasicAuthenticationExtractor()); + return this; + } + + public GrpcSecurity authorizeRequests(Customizer customizer) throws Exception { + customizer.customize(getOrApply(new RequestMapperConfigurer(getContext()))); + return this; + } + + @SuppressWarnings({ "unchecked", "removal" }) + private > C getOrApply( + C configurer) throws Exception { + C existingConfig = (C) getConfigurer(configurer.getClass()); + if (existingConfig != null) { + return existingConfig; + } + return apply(configurer); + } + + public GrpcSecurity authenticationManager(AuthenticationManager authenticationManager) { + Assert.notNull(authenticationManager, "authenticationManager cannot be null"); + this.authenticationManager = authenticationManager; + return this; + } + + public GrpcSecurity authenticationExtractor(GrpcAuthenticationExtractor authenticationExtractor) { + Assert.notNull(authenticationExtractor, "authenticationExtractor cannot be null"); + this.authenticationExtractors.add(authenticationExtractor); + return this; + } + + public GrpcSecurity authorizationManager(AuthorizationManager authorizationManager) { + this.authorizationManager = authorizationManager; + return this; + } + + private AuthenticationManagerBuilder getAuthenticationRegistry() { + return getSharedObject(AuthenticationManagerBuilder.class); + } + + private static class CompositeAuthenticationExtractor implements GrpcAuthenticationExtractor { + + private final List extractors; + + CompositeAuthenticationExtractor(List extractors) { + this.extractors = extractors; + } + + @Override + public Authentication extract(Metadata headers, Attributes attributes) { + for (GrpcAuthenticationExtractor extractor : this.extractors) { + Authentication authentication = extractor.extract(headers, attributes); + if (authentication != null) { + return authentication; + } + } + return null; + } + } } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/RequestMapperConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/RequestMapperConfigurer.java index 8069513..cdb4a1a 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/RequestMapperConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/RequestMapperConfigurer.java @@ -32,8 +32,7 @@ import org.springframework.util.Assert; import org.springframework.util.PatternMatchUtils; import org.springframework.util.function.SingletonSupplier; -public class RequestMapperConfigurer - extends SecurityConfigurerAdapter { +public class RequestMapperConfigurer extends SecurityConfigurerAdapter { private List authorizedCalls = new ArrayList<>(); @@ -41,8 +40,7 @@ public class RequestMapperConfigurer public RequestMapperConfigurer(ApplicationContext context) { this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0) - ? context.getBean(RoleHierarchy.class) - : new NullRoleHierarchy()); + ? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy()); } @Override @@ -66,14 +64,15 @@ public class RequestMapperConfigurer private String[] patterns; - public MethodCallMatcher(String... patterns) { + MethodCallMatcher(String... patterns) { this.patterns = patterns; } @Override public boolean matches(CallContext context) { - return PatternMatchUtils.simpleMatch(patterns, context.method().getFullMethodName()); + return PatternMatchUtils.simpleMatch(this.patterns, context.method().getFullMethodName()); } + } public class AuthorizedCall { @@ -114,14 +113,11 @@ public class RequestMapperConfigurer public RequestMapperConfigurer access(AuthorizationManager manager) { Assert.notNull(manager, "manager cannot be null"); - this.authorizationManager = (this.not) - ? AuthorizationManagers.not(manager) - : manager; + this.authorizationManager = (this.not) ? AuthorizationManagers.not(manager) : manager; return RequestMapperConfigurer.this; } - private AuthorityAuthorizationManager withRoleHierarchy( - AuthorityAuthorizationManager manager) { + private AuthorityAuthorizationManager withRoleHierarchy(AuthorityAuthorizationManager manager) { manager.setRoleHierarchy(RequestMapperConfigurer.this.roleHierarchy.get()); return manager; } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityContextServerInterceptor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityContextServerInterceptor.java index f879d80..684a76c 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityContextServerInterceptor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityContextServerInterceptor.java @@ -15,6 +15,7 @@ */ package org.springframework.grpc.server.security; +import org.springframework.core.Ordered; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -25,7 +26,12 @@ import io.grpc.ServerCall.Listener; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; -public class SecurityContextServerInterceptor implements ServerInterceptor { +public class SecurityContextServerInterceptor implements ServerInterceptor, Ordered { + + @Override + public int getOrder() { + return GrpcSecurity.CONTEXT_FILTER_ORDER; + } @Override public Listener interceptCall(ServerCall call, Metadata headers, diff --git a/spring-grpc-spring-boot-autoconfigure/pom.xml b/spring-grpc-spring-boot-autoconfigure/pom.xml index 5eeb96e..fb33fff 100644 --- a/spring-grpc-spring-boot-autoconfigure/pom.xml +++ b/spring-grpc-spring-boot-autoconfigure/pom.xml @@ -122,6 +122,6 @@ test - + diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/security/GrpcSecurityAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/security/GrpcSecurityAutoConfiguration.java index a3330ad..dced3b0 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/security/GrpcSecurityAutoConfiguration.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/security/GrpcSecurityAutoConfiguration.java @@ -19,6 +19,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -27,10 +28,12 @@ import org.springframework.grpc.autoconfigure.server.exception.GrpcExceptionHand import org.springframework.grpc.server.GlobalServerInterceptor; import org.springframework.grpc.server.ServerBuilderCustomizer; import org.springframework.grpc.server.exception.GrpcExceptionHandler; +import org.springframework.grpc.server.security.GrpcSecurity; import org.springframework.grpc.server.security.SecurityContextServerInterceptor; import org.springframework.grpc.server.security.SecurityGrpcExceptionHandler; import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; import org.springframework.security.config.ObjectPostProcessor; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.web.SecurityFilterChain; import io.grpc.ServerBuilder; @@ -50,10 +53,22 @@ public class GrpcSecurityAutoConfiguration { } + @ConditionalOnBean(ObjectPostProcessor.class) + @Configuration(proxyBeanMethods = false) + @Conditional(GrpcServerFactoryAutoConfiguration.OnNativeGrpcServerCondition.class) + static class GrpcNativeSecurityConfigurerAutoConfiguration { + + @Bean + public GrpcSecurity grpcSecurity(ObjectPostProcessor objectPostProcessor, + AuthenticationManagerBuilder authenticationManagerBuilder, ApplicationContext context) { + return new GrpcSecurity(objectPostProcessor, authenticationManagerBuilder, context); + } + + } + @ConditionalOnBean(SecurityFilterChain.class) @Configuration(proxyBeanMethods = false) - @Conditional(GrpcServerFactoryAutoConfiguration.OnGrpcServletCondition.class) - static class GrpcSecurityConfigurerAutoConfiguration { + static class GrpcServletSecurityConfigurerAutoConfiguration { @Bean @GlobalServerInterceptor