From 13295e33512943a0a21ddde1c28e33742e8ad94c Mon Sep 17 00:00:00 2001 From: Sajad Mehrabi Date: Mon, 22 Jan 2024 19:39:31 +0330 Subject: [PATCH 1/9] Add an interceptor to support AuthenticationManagerResolver --- docs/en/server/security.md | 30 +++++ .../GrpcServerSecurityAutoConfiguration.java | 21 +++- ...efaultAuthenticatingServerInterceptor.java | 2 + .../interceptors/GrpcServerRequest.java | 53 +++++++++ ...solverAuthenticatingServerInterceptor.java | 106 ++++++++++++++++++ 5 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java create mode 100644 grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java diff --git a/docs/en/server/security.md b/docs/en/server/security.md index 56c0563f8..06e36070a 100644 --- a/docs/en/server/security.md +++ b/docs/en/server/security.md @@ -209,6 +209,36 @@ GrpcAuthenticationReader authenticationReader() { See also [Mutual Certificate Authentication](#mutual-certificate-authentication). +#### Using AuthenticationManagerResolver +You can also use the `AuthenticationManagerResolver` to dynamically determine the authentication +manager to use for a particular request. This can be useful for applications that support multiple authentication +mechanisms, such as OAuth and OpenID Connect, or that want to delegate authentication to external services. + +To use `AuthenticationManagerResolver`, you first need to create a bean that implements +the `AuthenticationManagerResolver` interface instead of `AuthenticationManager`. The `resolve()` method of this bean should +return the AuthenticationManager to use for a particular request. + +````java +@Bean +AuthenticationManagerResolver grpcAuthenticationManagerResolver() { + return new AuthenticationManagerResolver() { + @Override + public AuthenticationManager resolve(GrpcServerRequest grpcServerRequest) { + AuthenticationManager authenticationManager = // Check the grpc request and return an authenticationManager + return authenticationManager; + } + }; +} + +@Bean +GrpcAuthenticationReader authenticationReader() { + final List readers = new ArrayList<>(); + // The actual token class is dependent on your spring-security library (OAuth2/JWT/...) + readers.add(new BearerAuthenticationReader(accessToken -> new BearerTokenAuthenticationToken(accessToken))); + return new CompositeGrpcAuthenticationReader(readers); +} +```` + ### Configure Authorization This step is very important as it actually secures your application against unwanted access. You can secure your diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java index 5e8146b66..8fe5cd640 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.core.AuthenticationException; import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; @@ -32,6 +33,8 @@ import net.devh.boot.grpc.server.security.interceptors.AuthenticatingServerInterceptor; import net.devh.boot.grpc.server.security.interceptors.AuthorizationCheckingServerInterceptor; import net.devh.boot.grpc.server.security.interceptors.DefaultAuthenticatingServerInterceptor; +import net.devh.boot.grpc.server.security.interceptors.GrpcServerRequest; +import net.devh.boot.grpc.server.security.interceptors.ManagerResolverAuthenticatingServerInterceptor; import net.devh.boot.grpc.server.security.interceptors.ExceptionTranslatingServerInterceptor; /** @@ -59,7 +62,6 @@ * @author Daniel Theuke (daniel.theuke@heuboe.de) */ @Configuration(proxyBeanMethods = false) -@ConditionalOnBean(AuthenticationManager.class) @AutoConfigureAfter(SecurityAutoConfiguration.class) public class GrpcServerSecurityAutoConfiguration { @@ -83,6 +85,7 @@ public ExceptionTranslatingServerInterceptor exceptionTranslatingServerIntercept * @return The authenticatingServerInterceptor bean. */ @Bean + @ConditionalOnBean(AuthenticationManager.class) @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) public DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor( final AuthenticationManager authenticationManager, @@ -90,6 +93,22 @@ public DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor( return new DefaultAuthenticatingServerInterceptor(authenticationManager, authenticationReader); } + /** + * The security interceptor that handles the authentication of requests. + * + * @param grpcAuthenticationManagerResolver The authentication manager resolver used to verify the credentials. + * @param authenticationReader The authentication reader used to extract the credentials from the call. + * @return The authenticatingServerInterceptor bean. + */ + @Bean + @ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) + @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) + public ManagerResolverAuthenticatingServerInterceptor managerResolverAuthenticatingServerInterceptor( + final AuthenticationManagerResolver grpcAuthenticationManagerResolver, + final GrpcAuthenticationReader authenticationReader) { + return new ManagerResolverAuthenticatingServerInterceptor(grpcAuthenticationManagerResolver, authenticationReader); + } + /** * The security interceptor that handles the authorization of requests. * diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java index 45b1a969a..03096abe8 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java @@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.core.annotation.Order; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AbstractAuthenticationToken; @@ -58,6 +59,7 @@ * @author Daniel Theuke (daniel.theuke@heuboe.de) */ @Slf4j +@ConditionalOnBean(AuthenticationManager.class) @GrpcGlobalServerInterceptor @Order(InterceptorOrder.ORDER_SECURITY_AUTHENTICATION) public class DefaultAuthenticatingServerInterceptor implements AuthenticatingServerInterceptor { diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java new file mode 100644 index 000000000..9c8caa501 --- /dev/null +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java @@ -0,0 +1,53 @@ +package net.devh.boot.grpc.server.security.interceptors; + +import brave.grpc.GrpcRequest; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerCall; +import io.grpc.ServerInterceptor; + +/** + * Allows access gRPC specific aspects of a server request during sampling and parsing. + * + * @see GrpcRequest for a parsing example + * @since 5.12 + */ +public class GrpcServerRequest { + final ServerCall call; + final Metadata headers; + + public GrpcServerRequest(ServerCall call, Metadata headers) { + if (call == null) throw new NullPointerException("call == null"); + if (headers == null) throw new NullPointerException("headers == null"); + this.call = call; + this.headers = headers; + } + + /** + * Returns the {@linkplain ServerCall server call} passed to {@link + * ServerInterceptor#interceptCall}. + * + * @since 5.12 + */ + public ServerCall call() { + return call; + } + + /** + * Returns {@linkplain ServerCall#getMethodDescriptor()}} from the {@link #call()}. + * + * @since 5.12 + */ + public MethodDescriptor methodDescriptor() { + return call.getMethodDescriptor(); + } + + /** + * Returns the {@linkplain Metadata headers} passed to {@link ServerInterceptor#interceptCall}. + * + * @since 5.12 + */ + public Metadata headers() { + return headers; + } +} diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java new file mode 100644 index 000000000..444b0e449 --- /dev/null +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 + * + * http://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 net.devh.boot.grpc.server.security.interceptors; + +import io.grpc.*; +import io.grpc.ServerCall.Listener; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.common.util.InterceptorOrder; +import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.core.annotation.Order; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.*; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; + + +import static java.util.Objects.requireNonNull; + +/** + * A server interceptor that tries to {@link GrpcAuthenticationReader read} the credentials from the client and + * {@link AuthenticationManagerResolver#resolve (Context) grpcServerRequest} them. This interceptor create new {@link DefaultAuthenticatingServerInterceptor} to sets the + * authentication to both grpc's {@link Context} and {@link SecurityContextHolder}. + * + *

+ * This works similar to the {@code org.springframework.security.web.authentication.AuthenticationFilter}. + *

+ * + *

+ * Note: This interceptor works similar to + * {@link Contexts#interceptCall(Context, ServerCall, Metadata, ServerCallHandler)}. + *

+ * + * @author Sajad Mehrabi (mehrabisajad@gmail.com) + */ +@Slf4j +@ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) +@GrpcGlobalServerInterceptor +@Order(InterceptorOrder.ORDER_SECURITY_AUTHENTICATION) +public class ManagerResolverAuthenticatingServerInterceptor implements AuthenticatingServerInterceptor { + + private final AuthenticationManagerResolver authenticationManagerResolver; + private final GrpcAuthenticationReader grpcAuthenticationReader; + + /** + * Creates a new ManagerResolverAuthenticatingServerInterceptor with the given authentication manager resolver and reader. + * + * @param authenticationManagerResolver The authentication manager resolver used to verify the credentials. + * @param authenticationReader The authentication reader used to extract the credentials from the call. + */ + @Autowired + public ManagerResolverAuthenticatingServerInterceptor(final AuthenticationManagerResolver authenticationManagerResolver, + final GrpcAuthenticationReader authenticationReader) { + this.authenticationManagerResolver = requireNonNull(authenticationManagerResolver, "authenticationManagerResolver"); + this.grpcAuthenticationReader = requireNonNull(authenticationReader, "authenticationReader"); + } + + @Override + public Listener interceptCall(final ServerCall call, + final Metadata headers, final ServerCallHandler next) { + + GrpcServerRequest grpcServerRequest = new GrpcServerRequest(call, headers); + AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(grpcServerRequest); + + if (authenticationManager == null) { + log.debug("No authenticationManager found: Continuing unauthenticated"); + try { + return next.startCall(call, headers); + } catch (final AccessDeniedException e) { + throw newNoAuthenticationManagerException(e); + } + } + DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor = new DefaultAuthenticatingServerInterceptor(authenticationManager, this.grpcAuthenticationReader); + return authenticatingServerInterceptor.interceptCall(call, headers, next); + + } + + + /** + * Wraps the given {@link AccessDeniedException} in an {@link AuthenticationException} to reflect, that no + * authentication was originally present in the request. + * + * @param denied The caught exception. + * @return The newly created {@link AuthenticationException}. + */ + private static AuthenticationException newNoAuthenticationManagerException(final AccessDeniedException denied) { + return new BadCredentialsException("No credentials found in the request", denied); + } + +} From 4f73dcc437b7f34a5a4692b5e278d57f801cd3a9 Mon Sep 17 00:00:00 2001 From: Sajad Mehrabi Date: Tue, 23 Jan 2024 18:22:20 +0330 Subject: [PATCH 2/9] Create an abstract class for AuthenticatingServerInterceptor and use it on subclasses --- docs/en/server/security.md | 1 + ...stractAuthenticatingServerInterceptor.java | 259 ++++++++++++++++++ ...efaultAuthenticatingServerInterceptor.java | 203 +------------- .../interceptors/GrpcServerRequest.java | 22 +- ...solverAuthenticatingServerInterceptor.java | 46 +--- 5 files changed, 287 insertions(+), 244 deletions(-) create mode 100644 grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java diff --git a/docs/en/server/security.md b/docs/en/server/security.md index 06e36070a..7d5038860 100644 --- a/docs/en/server/security.md +++ b/docs/en/server/security.md @@ -210,6 +210,7 @@ GrpcAuthenticationReader authenticationReader() { See also [Mutual Certificate Authentication](#mutual-certificate-authentication). #### Using AuthenticationManagerResolver + You can also use the `AuthenticationManagerResolver` to dynamically determine the authentication manager to use for a particular request. This can be useful for applications that support multiple authentication mechanisms, such as OAuth and OpenID Connect, or that want to delegate authentication to external services. diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java new file mode 100644 index 000000000..a9785edbe --- /dev/null +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 + * + * http://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 net.devh.boot.grpc.server.security.interceptors; + +import io.grpc.*; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static java.util.Objects.requireNonNull; + + +/** + * A server interceptor that tries to {@link GrpcAuthenticationReader read} the credentials from the client and + * {@link AuthenticationManager#authenticate(Authentication) authenticate} them. This interceptor sets the + * authentication to both grpc's {@link Context} and {@link SecurityContextHolder}. + * + *

+ * This works similar to the {@code org.springframework.security.web.authentication.AuthenticationFilter}. + *

+ * + *

+ * Note: This interceptor works similar to + * {@link Contexts#interceptCall(Context, ServerCall, Metadata, ServerCallHandler)}. + *

+ * + */ +@Slf4j +public abstract class AbstractAuthenticatingServerInterceptor implements AuthenticatingServerInterceptor { + + private final GrpcAuthenticationReader grpcAuthenticationReader; + + /** + * Creates a new DefaultAuthenticatingServerInterceptor with the given authentication manager and reader. + * + * @param authenticationReader The authentication reader used to extract the credentials from the call. + */ + @Autowired + protected AbstractAuthenticatingServerInterceptor(final GrpcAuthenticationReader authenticationReader) { + this.grpcAuthenticationReader = requireNonNull(authenticationReader, "authenticationReader"); + } + + @Override + public ServerCall.Listener interceptCall(final ServerCall call, + final Metadata headers, final ServerCallHandler next) { + Authentication authentication; + try { + authentication = this.grpcAuthenticationReader.readAuthentication(call, headers); + } catch (final AuthenticationException e) { + log.debug("Failed to read authentication: {}", e.getMessage()); + throw e; + } + if (authentication == null) { + log.debug("No credentials found: Continuing unauthenticated"); + try { + return next.startCall(call, headers); + } catch (final AccessDeniedException e) { + throw newNoCredentialsException(e); + } + } + if (authentication.getDetails() == null && authentication instanceof AbstractAuthenticationToken) { + // Append call attributes to the authentication request. + // This gives the AuthenticationManager access to information like remote and local address. + // It can then decide whether it wants to use its own user details or the attributes. + ((AbstractAuthenticationToken) authentication).setDetails(call.getAttributes()); + } + log.debug("Credentials found: Authenticating '{}'", authentication.getName()); + + AuthenticationManager authenticationManager = this.getAuthenticationManager(call, headers); + if (authenticationManager == null) { + log.debug("No authentication manager found: Continuing unauthenticated"); + try { + return next.startCall(call, headers); + } catch (final AccessDeniedException e) { + throw newNoCredentialsException(e); + } + } + + try { + authentication = authenticationManager.authenticate(authentication); + } catch (final AuthenticationException e) { + log.debug("Authentication request failed: {}", e.getMessage()); + onUnsuccessfulAuthentication(call, headers, e); + throw e; + } + + final SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); + @SuppressWarnings("deprecation") + final Context grpcContext = Context.current().withValues( + SECURITY_CONTEXT_KEY, securityContext, + AUTHENTICATION_CONTEXT_KEY, authentication); + final Context previousContext = grpcContext.attach(); + log.debug("Authentication successful: Continuing as {} ({})", authentication.getName(), + authentication.getAuthorities()); + onSuccessfulAuthentication(call, headers, authentication); + try { + return new AuthenticatingServerCallListener<>(next.startCall(call, headers), grpcContext, securityContext); + } catch (final AccessDeniedException e) { + if (authentication instanceof AnonymousAuthenticationToken) { + throw newNoCredentialsException(e); + } else { + throw e; + } + } finally { + SecurityContextHolder.clearContext(); + grpcContext.detach(previousContext); + log.debug("startCall - Authentication cleared"); + } + } + + /** + * Retrieves the appropriate AuthenticationManager to handle authentication for the given gRPC request. + * Subclasses must implement this method to provide a mechanism for determining the appropriate AuthenticationManager + * based on the specific request context. This allows for dynamic selection of authentication strategies based on + * factors such as request headers, request payload, or other criteria. + * + * @param call The gRPC ServerCall representing the incoming request. + * @param headers The metadata associated with the request, containing potentially relevant authentication information. + * @return The AuthenticationManager responsible for authenticating the request. + */ + protected abstract AuthenticationManager getAuthenticationManager(final ServerCall call, + final Metadata headers); + + /** + * Hook that will be called on successful authentication. Implementations may only use the call instance in a + * non-disruptive manor, that is accessing call attributes or the call descriptor. Implementations must not pollute + * the current thread/context with any call-related state, including authentication, beyond the duration of the + * method invocation. At the time of calling both the grpc context and the security context have been updated to + * reflect the state of the authentication and thus don't have to be setup manually. + * + *

+ * Note: This method is called regardless of whether the authenticated user is authorized or not to perform + * the requested action. + *

+ * + *

+ * By default, this method does nothing. + *

+ * + * @param call The call instance to receive response messages. + * @param headers The headers associated with the call. + * @param authentication The successful authentication instance. + */ + protected void onSuccessfulAuthentication( + final ServerCall call, + final Metadata headers, + final Authentication authentication) { + // Overwrite to add custom behavior. + } + + /** + * Hook that will be called on unsuccessful authentication. Implementations must use the call instance only in a + * non-disruptive manner, i.e. to access call attributes or the call descriptor. Implementations must not close the + * call and must not pollute the current thread/context with any call-related state, including authentication, + * beyond the duration of the method invocation. + * + *

+ * Note: This method is called only if the request contains an authentication but the + * {@link AuthenticationManager} considers it invalid. This method is not called if an authenticated user is not + * authorized to perform the requested action. + *

+ * + *

+ * By default, this method does nothing. + *

+ * + * @param call The call instance to receive response messages. + * @param headers The headers associated with the call. + * @param failed The exception related to the unsuccessful authentication. + */ + protected void onUnsuccessfulAuthentication( + final ServerCall call, + final Metadata headers, + final AuthenticationException failed) { + // Overwrite to add custom behavior. + } + + /** + * Wraps the given {@link AccessDeniedException} in an {@link AuthenticationException} to reflect, that no + * authentication was originally present in the request. + * + * @param denied The caught exception. + * @return The newly created {@link AuthenticationException}. + */ + private static AuthenticationException newNoCredentialsException(final AccessDeniedException denied) { + return new BadCredentialsException("No credentials found in the request", denied); + } + + /** + * A call listener that will set the authentication context using {@link SecurityContextHolder} before each + * invocation and clear it afterwards. + * + * @param The type of the request. + */ + private static class AuthenticatingServerCallListener extends AbstractAuthenticatingServerCallListener { + + private final SecurityContext securityContext; + + /** + * Creates a new AuthenticatingServerCallListener which will attach the given security context before delegating + * to the given listener. + * + * @param delegate The listener to delegate to. + * @param grpcContext The context to attach. + * @param securityContext The security context instance to attach. + */ + public AuthenticatingServerCallListener(final ServerCall.Listener delegate, final Context grpcContext, + final SecurityContext securityContext) { + super(delegate, grpcContext); + this.securityContext = securityContext; + } + + @Override + protected void attachAuthenticationContext() { + SecurityContextHolder.setContext(this.securityContext); + } + + @Override + protected void detachAuthenticationContext() { + SecurityContextHolder.clearContext(); + } + + @Override + public void onHalfClose() { + try { + super.onHalfClose(); + } catch (final AccessDeniedException e) { + if (this.securityContext.getAuthentication() instanceof AnonymousAuthenticationToken) { + throw newNoCredentialsException(e); + } else { + throw e; + } + } + } + + } +} diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java index 03096abe8..3648371ec 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java @@ -16,31 +16,19 @@ package net.devh.boot.grpc.server.security.interceptors; -import static java.util.Objects.requireNonNull; - +import io.grpc.*; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.common.util.InterceptorOrder; +import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.core.annotation.Order; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import io.grpc.Context; -import io.grpc.Contexts; -import io.grpc.Metadata; -import io.grpc.ServerCall; -import io.grpc.ServerCall.Listener; -import io.grpc.ServerCallHandler; -import lombok.extern.slf4j.Slf4j; -import net.devh.boot.grpc.common.util.InterceptorOrder; -import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; -import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; +import static java.util.Objects.requireNonNull; /** * A server interceptor that tries to {@link GrpcAuthenticationReader read} the credentials from the client and @@ -62,10 +50,9 @@ @ConditionalOnBean(AuthenticationManager.class) @GrpcGlobalServerInterceptor @Order(InterceptorOrder.ORDER_SECURITY_AUTHENTICATION) -public class DefaultAuthenticatingServerInterceptor implements AuthenticatingServerInterceptor { +public class DefaultAuthenticatingServerInterceptor extends AbstractAuthenticatingServerInterceptor { private final AuthenticationManager authenticationManager; - private final GrpcAuthenticationReader grpcAuthenticationReader; /** * Creates a new DefaultAuthenticatingServerInterceptor with the given authentication manager and reader. @@ -76,181 +63,13 @@ public class DefaultAuthenticatingServerInterceptor implements AuthenticatingSer @Autowired public DefaultAuthenticatingServerInterceptor(final AuthenticationManager authenticationManager, final GrpcAuthenticationReader authenticationReader) { + super(authenticationReader); this.authenticationManager = requireNonNull(authenticationManager, "authenticationManager"); - this.grpcAuthenticationReader = requireNonNull(authenticationReader, "authenticationReader"); } @Override - public ServerCall.Listener interceptCall(final ServerCall call, - final Metadata headers, final ServerCallHandler next) { - Authentication authentication; - try { - authentication = this.grpcAuthenticationReader.readAuthentication(call, headers); - } catch (final AuthenticationException e) { - log.debug("Failed to read authentication: {}", e.getMessage()); - throw e; - } - if (authentication == null) { - log.debug("No credentials found: Continuing unauthenticated"); - try { - return next.startCall(call, headers); - } catch (final AccessDeniedException e) { - throw newNoCredentialsException(e); - } - } - if (authentication.getDetails() == null && authentication instanceof AbstractAuthenticationToken) { - // Append call attributes to the authentication request. - // This gives the AuthenticationManager access to information like remote and local address. - // It can then decide whether it wants to use its own user details or the attributes. - ((AbstractAuthenticationToken) authentication).setDetails(call.getAttributes()); - } - log.debug("Credentials found: Authenticating '{}'", authentication.getName()); - try { - authentication = this.authenticationManager.authenticate(authentication); - } catch (final AuthenticationException e) { - log.debug("Authentication request failed: {}", e.getMessage()); - onUnsuccessfulAuthentication(call, headers, e); - throw e; - } - - final SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(authentication); - SecurityContextHolder.setContext(securityContext); - @SuppressWarnings("deprecation") - final Context grpcContext = Context.current().withValues( - SECURITY_CONTEXT_KEY, securityContext, - AUTHENTICATION_CONTEXT_KEY, authentication); - final Context previousContext = grpcContext.attach(); - log.debug("Authentication successful: Continuing as {} ({})", authentication.getName(), - authentication.getAuthorities()); - onSuccessfulAuthentication(call, headers, authentication); - try { - return new AuthenticatingServerCallListener<>(next.startCall(call, headers), grpcContext, securityContext); - } catch (final AccessDeniedException e) { - if (authentication instanceof AnonymousAuthenticationToken) { - throw newNoCredentialsException(e); - } else { - throw e; - } - } finally { - SecurityContextHolder.clearContext(); - grpcContext.detach(previousContext); - log.debug("startCall - Authentication cleared"); - } + public AuthenticationManager getAuthenticationManager(final ServerCall call, + final Metadata headers) { + return authenticationManager; } - - /** - * Hook that will be called on successful authentication. Implementations may only use the call instance in a - * non-disruptive manor, that is accessing call attributes or the call descriptor. Implementations must not pollute - * the current thread/context with any call-related state, including authentication, beyond the duration of the - * method invocation. At the time of calling both the grpc context and the security context have been updated to - * reflect the state of the authentication and thus don't have to be setup manually. - * - *

- * Note: This method is called regardless of whether the authenticated user is authorized or not to perform - * the requested action. - *

- * - *

- * By default, this method does nothing. - *

- * - * @param call The call instance to receive response messages. - * @param headers The headers associated with the call. - * @param authentication The successful authentication instance. - */ - protected void onSuccessfulAuthentication( - final ServerCall call, - final Metadata headers, - final Authentication authentication) { - // Overwrite to add custom behavior. - } - - /** - * Hook that will be called on unsuccessful authentication. Implementations must use the call instance only in a - * non-disruptive manner, i.e. to access call attributes or the call descriptor. Implementations must not close the - * call and must not pollute the current thread/context with any call-related state, including authentication, - * beyond the duration of the method invocation. - * - *

- * Note: This method is called only if the request contains an authentication but the - * {@link AuthenticationManager} considers it invalid. This method is not called if an authenticated user is not - * authorized to perform the requested action. - *

- * - *

- * By default, this method does nothing. - *

- * - * @param call The call instance to receive response messages. - * @param headers The headers associated with the call. - * @param failed The exception related to the unsuccessful authentication. - */ - protected void onUnsuccessfulAuthentication( - final ServerCall call, - final Metadata headers, - final AuthenticationException failed) { - // Overwrite to add custom behavior. - } - - /** - * Wraps the given {@link AccessDeniedException} in an {@link AuthenticationException} to reflect, that no - * authentication was originally present in the request. - * - * @param denied The caught exception. - * @return The newly created {@link AuthenticationException}. - */ - private static AuthenticationException newNoCredentialsException(final AccessDeniedException denied) { - return new BadCredentialsException("No credentials found in the request", denied); - } - - /** - * A call listener that will set the authentication context using {@link SecurityContextHolder} before each - * invocation and clear it afterwards. - * - * @param The type of the request. - */ - private static class AuthenticatingServerCallListener extends AbstractAuthenticatingServerCallListener { - - private final SecurityContext securityContext; - - /** - * Creates a new AuthenticatingServerCallListener which will attach the given security context before delegating - * to the given listener. - * - * @param delegate The listener to delegate to. - * @param grpcContext The context to attach. - * @param securityContext The security context instance to attach. - */ - public AuthenticatingServerCallListener(final Listener delegate, final Context grpcContext, - final SecurityContext securityContext) { - super(delegate, grpcContext); - this.securityContext = securityContext; - } - - @Override - protected void attachAuthenticationContext() { - SecurityContextHolder.setContext(this.securityContext); - } - - @Override - protected void detachAuthenticationContext() { - SecurityContextHolder.clearContext(); - } - - @Override - public void onHalfClose() { - try { - super.onHalfClose(); - } catch (final AccessDeniedException e) { - if (this.securityContext.getAuthentication() instanceof AnonymousAuthenticationToken) { - throw newNoCredentialsException(e); - } else { - throw e; - } - } - } - - } - } diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java index 9c8caa501..f775d9b31 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java @@ -1,33 +1,29 @@ package net.devh.boot.grpc.server.security.interceptors; -import brave.grpc.GrpcRequest; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.ServerCall; import io.grpc.ServerInterceptor; +import static java.util.Objects.requireNonNull; + /** * Allows access gRPC specific aspects of a server request during sampling and parsing. * - * @see GrpcRequest for a parsing example - * @since 5.12 + * @author Sajad Mehrabi (mehrabisajad@gmail.com) */ public class GrpcServerRequest { - final ServerCall call; - final Metadata headers; + private final ServerCall call; + private final Metadata headers; public GrpcServerRequest(ServerCall call, Metadata headers) { - if (call == null) throw new NullPointerException("call == null"); - if (headers == null) throw new NullPointerException("headers == null"); - this.call = call; - this.headers = headers; + this.call = requireNonNull(call, "call"); + this.headers = requireNonNull(headers, "headers"); } /** * Returns the {@linkplain ServerCall server call} passed to {@link * ServerInterceptor#interceptCall}. - * - * @since 5.12 */ public ServerCall call() { return call; @@ -35,8 +31,6 @@ public GrpcServerRequest(ServerCall call, Metadata headers) { /** * Returns {@linkplain ServerCall#getMethodDescriptor()}} from the {@link #call()}. - * - * @since 5.12 */ public MethodDescriptor methodDescriptor() { return call.getMethodDescriptor(); @@ -44,8 +38,6 @@ public GrpcServerRequest(ServerCall call, Metadata headers) { /** * Returns the {@linkplain Metadata headers} passed to {@link ServerInterceptor#interceptCall}. - * - * @since 5.12 */ public Metadata headers() { return headers; diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java index 444b0e449..c22e881f4 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -17,7 +17,6 @@ package net.devh.boot.grpc.server.security.interceptors; import io.grpc.*; -import io.grpc.ServerCall.Listener; import lombok.extern.slf4j.Slf4j; import net.devh.boot.grpc.common.util.InterceptorOrder; import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; @@ -25,9 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.core.annotation.Order; -import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.*; -import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -35,7 +33,7 @@ /** * A server interceptor that tries to {@link GrpcAuthenticationReader read} the credentials from the client and - * {@link AuthenticationManagerResolver#resolve (Context) grpcServerRequest} them. This interceptor create new {@link DefaultAuthenticatingServerInterceptor} to sets the + * {@link AuthenticationManager#authenticate(Authentication) authenticate} them. This interceptor sets the * authentication to both grpc's {@link Context} and {@link SecurityContextHolder}. * *

@@ -53,54 +51,28 @@ @ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) @GrpcGlobalServerInterceptor @Order(InterceptorOrder.ORDER_SECURITY_AUTHENTICATION) -public class ManagerResolverAuthenticatingServerInterceptor implements AuthenticatingServerInterceptor { +public class ManagerResolverAuthenticatingServerInterceptor extends AbstractAuthenticatingServerInterceptor { private final AuthenticationManagerResolver authenticationManagerResolver; - private final GrpcAuthenticationReader grpcAuthenticationReader; /** * Creates a new ManagerResolverAuthenticatingServerInterceptor with the given authentication manager resolver and reader. * * @param authenticationManagerResolver The authentication manager resolver used to verify the credentials. - * @param authenticationReader The authentication reader used to extract the credentials from the call. + * @param authenticationReader The authentication reader used to extract the credentials from the call. */ @Autowired public ManagerResolverAuthenticatingServerInterceptor(final AuthenticationManagerResolver authenticationManagerResolver, final GrpcAuthenticationReader authenticationReader) { + super(authenticationReader); this.authenticationManagerResolver = requireNonNull(authenticationManagerResolver, "authenticationManagerResolver"); - this.grpcAuthenticationReader = requireNonNull(authenticationReader, "authenticationReader"); } - @Override - public Listener interceptCall(final ServerCall call, - final Metadata headers, final ServerCallHandler next) { + @Override + protected AuthenticationManager getAuthenticationManager(final ServerCall call, + final Metadata headers) { GrpcServerRequest grpcServerRequest = new GrpcServerRequest(call, headers); - AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(grpcServerRequest); - - if (authenticationManager == null) { - log.debug("No authenticationManager found: Continuing unauthenticated"); - try { - return next.startCall(call, headers); - } catch (final AccessDeniedException e) { - throw newNoAuthenticationManagerException(e); - } - } - DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor = new DefaultAuthenticatingServerInterceptor(authenticationManager, this.grpcAuthenticationReader); - return authenticatingServerInterceptor.interceptCall(call, headers, next); - + return this.authenticationManagerResolver.resolve(grpcServerRequest); } - - - /** - * Wraps the given {@link AccessDeniedException} in an {@link AuthenticationException} to reflect, that no - * authentication was originally present in the request. - * - * @param denied The caught exception. - * @return The newly created {@link AuthenticationException}. - */ - private static AuthenticationException newNoAuthenticationManagerException(final AccessDeniedException denied) { - return new BadCredentialsException("No credentials found in the request", denied); - } - } From 802ad1f8e5c4a988318fcb06ffb5425681b4d48d Mon Sep 17 00:00:00 2001 From: Sajad Mehrabi Date: Sun, 28 Jan 2024 15:52:43 +0330 Subject: [PATCH 3/9] Add test for ManagerResolverAuthenticatingServerInterceptor and refactor related codes --- docs/en/server/security.md | 9 +- ...stractAuthenticatingServerInterceptor.java | 24 ++-- ...efaultAuthenticatingServerInterceptor.java | 11 +- .../interceptors/GrpcServerRequest.java | 22 +--- ...solverAuthenticatingServerInterceptor.java | 12 +- ...dManagerResolverSecurityConfiguration.java | 117 +++++++++++++++++ ...tyWithBasicAuthAndManagerResolverTest.java | 123 ++++++++++++++++++ 7 files changed, 267 insertions(+), 51 deletions(-) create mode 100644 tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java create mode 100644 tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java diff --git a/docs/en/server/security.md b/docs/en/server/security.md index 7d5038860..df4ee5276 100644 --- a/docs/en/server/security.md +++ b/docs/en/server/security.md @@ -222,12 +222,9 @@ return the AuthenticationManager to use for a particular request. ````java @Bean AuthenticationManagerResolver grpcAuthenticationManagerResolver() { - return new AuthenticationManagerResolver() { - @Override - public AuthenticationManager resolve(GrpcServerRequest grpcServerRequest) { - AuthenticationManager authenticationManager = // Check the grpc request and return an authenticationManager - return authenticationManager; - } + return grpcServerRequest -> { + AuthenticationManager authenticationManager = // Check the grpc request and return an authenticationManager + return authenticationManager; }; } diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java index a9785edbe..ebf55de6c 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java @@ -43,7 +43,6 @@ * Note: This interceptor works similar to * {@link Contexts#interceptCall(Context, ServerCall, Metadata, ServerCallHandler)}. *

- * */ @Slf4j public abstract class AbstractAuthenticatingServerInterceptor implements AuthenticatingServerInterceptor { @@ -55,7 +54,6 @@ public abstract class AbstractAuthenticatingServerInterceptor implements Authent * * @param authenticationReader The authentication reader used to extract the credentials from the call. */ - @Autowired protected AbstractAuthenticatingServerInterceptor(final GrpcAuthenticationReader authenticationReader) { this.grpcAuthenticationReader = requireNonNull(authenticationReader, "authenticationReader"); } @@ -107,8 +105,7 @@ public ServerCall.Listener interceptCall(final ServerCall ServerCall.Listener interceptCall(final ServerCall AuthenticationManager getAuthenticationManager(final ServerCall call, - final Metadata headers); + protected abstract AuthenticationManager getAuthenticationManager( + final ServerCall call, + final Metadata headers); /** * Hook that will be called on successful authentication. Implementations may only use the call instance in a @@ -159,8 +157,8 @@ protected abstract AuthenticationManager getAuthenticationManager( * By default, this method does nothing. *

* - * @param call The call instance to receive response messages. - * @param headers The headers associated with the call. + * @param call The call instance to receive response messages. + * @param headers The headers associated with the call. * @param authentication The successful authentication instance. */ protected void onSuccessfulAuthentication( @@ -186,9 +184,9 @@ protected void onSuccessfulAuthentication( * By default, this method does nothing. *

* - * @param call The call instance to receive response messages. + * @param call The call instance to receive response messages. * @param headers The headers associated with the call. - * @param failed The exception related to the unsuccessful authentication. + * @param failed The exception related to the unsuccessful authentication. */ protected void onUnsuccessfulAuthentication( final ServerCall call, @@ -222,8 +220,8 @@ private static class AuthenticatingServerCallListener extends AbstractAuth * Creates a new AuthenticatingServerCallListener which will attach the given security context before delegating * to the given listener. * - * @param delegate The listener to delegate to. - * @param grpcContext The context to attach. + * @param delegate The listener to delegate to. + * @param grpcContext The context to attach. * @param securityContext The security context instance to attach. */ public AuthenticatingServerCallListener(final ServerCall.Listener delegate, final Context grpcContext, diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java index 3648371ec..50ce2f538 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java @@ -22,7 +22,6 @@ import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; @@ -47,7 +46,6 @@ * @author Daniel Theuke (daniel.theuke@heuboe.de) */ @Slf4j -@ConditionalOnBean(AuthenticationManager.class) @GrpcGlobalServerInterceptor @Order(InterceptorOrder.ORDER_SECURITY_AUTHENTICATION) public class DefaultAuthenticatingServerInterceptor extends AbstractAuthenticatingServerInterceptor { @@ -58,18 +56,19 @@ public class DefaultAuthenticatingServerInterceptor extends AbstractAuthenticati * Creates a new DefaultAuthenticatingServerInterceptor with the given authentication manager and reader. * * @param authenticationManager The authentication manager used to verify the credentials. - * @param authenticationReader The authentication reader used to extract the credentials from the call. + * @param authenticationReader The authentication reader used to extract the credentials from the call. */ @Autowired public DefaultAuthenticatingServerInterceptor(final AuthenticationManager authenticationManager, - final GrpcAuthenticationReader authenticationReader) { + final GrpcAuthenticationReader authenticationReader) { super(authenticationReader); this.authenticationManager = requireNonNull(authenticationManager, "authenticationManager"); } @Override - public AuthenticationManager getAuthenticationManager(final ServerCall call, - final Metadata headers) { + public AuthenticationManager getAuthenticationManager( + final ServerCall call, + final Metadata headers) { return authenticationManager; } } diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java index f775d9b31..6dc9c23da 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java @@ -3,7 +3,6 @@ import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.ServerCall; -import io.grpc.ServerInterceptor; import static java.util.Objects.requireNonNull; @@ -12,23 +11,12 @@ * * @author Sajad Mehrabi (mehrabisajad@gmail.com) */ -public class GrpcServerRequest { - private final ServerCall call; - private final Metadata headers; - +public record GrpcServerRequest(ServerCall call, Metadata headers) { public GrpcServerRequest(ServerCall call, Metadata headers) { this.call = requireNonNull(call, "call"); this.headers = requireNonNull(headers, "headers"); } - /** - * Returns the {@linkplain ServerCall server call} passed to {@link - * ServerInterceptor#interceptCall}. - */ - public ServerCall call() { - return call; - } - /** * Returns {@linkplain ServerCall#getMethodDescriptor()}} from the {@link #call()}. */ @@ -36,10 +24,4 @@ public GrpcServerRequest(ServerCall call, Metadata headers) { return call.getMethodDescriptor(); } - /** - * Returns the {@linkplain Metadata headers} passed to {@link ServerInterceptor#interceptCall}. - */ - public Metadata headers() { - return headers; - } -} +} \ No newline at end of file diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java index c22e881f4..d73bfe298 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -22,7 +22,6 @@ import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; @@ -48,7 +47,6 @@ * @author Sajad Mehrabi (mehrabisajad@gmail.com) */ @Slf4j -@ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) @GrpcGlobalServerInterceptor @Order(InterceptorOrder.ORDER_SECURITY_AUTHENTICATION) public class ManagerResolverAuthenticatingServerInterceptor extends AbstractAuthenticatingServerInterceptor { @@ -62,16 +60,18 @@ public class ManagerResolverAuthenticatingServerInterceptor extends AbstractAuth * @param authenticationReader The authentication reader used to extract the credentials from the call. */ @Autowired - public ManagerResolverAuthenticatingServerInterceptor(final AuthenticationManagerResolver authenticationManagerResolver, - final GrpcAuthenticationReader authenticationReader) { + public ManagerResolverAuthenticatingServerInterceptor( + final AuthenticationManagerResolver authenticationManagerResolver, + final GrpcAuthenticationReader authenticationReader) { super(authenticationReader); this.authenticationManagerResolver = requireNonNull(authenticationManagerResolver, "authenticationManagerResolver"); } @Override - protected AuthenticationManager getAuthenticationManager(final ServerCall call, - final Metadata headers) { + protected AuthenticationManager getAuthenticationManager( + final ServerCall call, + final Metadata headers) { GrpcServerRequest grpcServerRequest = new GrpcServerRequest(call, headers); return this.authenticationManagerResolver.resolve(grpcServerRequest); } diff --git a/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java b/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java new file mode 100644 index 000000000..fb512a2dd --- /dev/null +++ b/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 + * + * http://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 net.devh.boot.grpc.test.config; + +import com.google.common.collect.ImmutableMap; +import io.grpc.CallCredentials; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.client.inject.StubTransformer; +import net.devh.boot.grpc.client.security.CallCredentialsHelper; +import net.devh.boot.grpc.server.security.authentication.*; +import net.devh.boot.grpc.server.security.interceptors.GrpcServerRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AnonymousAuthenticationProvider; +import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static net.devh.boot.grpc.client.security.CallCredentialsHelper.basicAuth; +import static net.devh.boot.grpc.common.security.SecurityConstants.AUTHORIZATION_HEADER; + +@Slf4j +@Configuration +public class WithBasicAuthAndManagerResolverSecurityConfiguration { + + @Bean + PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(10); + } + + @Bean + UserDetailsService userDetailsService() { + return username -> { + log.debug("Searching user: {}", username); + if (username.length() > 10) { + throw new UsernameNotFoundException("Could not find user!"); + } + final List authorities = + Arrays.asList(new SimpleGrantedAuthority("ROLE_" + username.toUpperCase())); + return new User(username, passwordEncoder().encode(username), authorities); + }; + } + + @Bean + DaoAuthenticationProvider daoAuthenticationProvider() { + final DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService()); + provider.setPasswordEncoder(passwordEncoder()); + return provider; + } + + + @Bean + AuthenticationManagerResolver authenticationManager() { + return context -> { + String token = context.headers().get(AUTHORIZATION_HEADER); + if (token != null && token.startsWith("Basic")) { + final List providers = new ArrayList<>(); + providers.add(daoAuthenticationProvider()); + return new ProviderManager(providers); + } else { + return null; + } + }; + } + + @Bean + GrpcAuthenticationReader authenticationReader() { + final List readers = new ArrayList<>(); + readers.add(new BasicGrpcAuthenticationReader()); + return new CompositeGrpcAuthenticationReader(readers); + } + + // Client-Side + + @Bean + StubTransformer mappedCredentialsStubTransformer() { + return CallCredentialsHelper.mappedCredentialsStubTransformer(ImmutableMap.builder() + .put("test", testCallCredentials("client1")) + .put("test-secondary", testCallCredentials("client1")) + .put("noPerm", testCallCredentials("client2")) + .put("noPerm-secondary", testCallCredentials("client2")) + .put("unknownUser", testCallCredentials("unknownUser")) + // .put("noAuth", null) + .build()); + } + + private CallCredentials testCallCredentials(final String username) { + return basicAuth(username, username); + } + +} diff --git a/tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java b/tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java new file mode 100644 index 000000000..55cd3a416 --- /dev/null +++ b/tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 + * + * http://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 net.devh.boot.grpc.test.security; + +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.client.inject.GrpcClient; +import net.devh.boot.grpc.test.config.*; +import net.devh.boot.grpc.test.proto.TestServiceGrpc.TestServiceBlockingStub; +import net.devh.boot.grpc.test.proto.TestServiceGrpc.TestServiceFutureStub; +import net.devh.boot.grpc.test.proto.TestServiceGrpc.TestServiceStub; +import net.devh.boot.grpc.test.util.DynamicTestCollection; +import org.junit.jupiter.api.TestFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static io.grpc.Status.Code.PERMISSION_DENIED; + +/** + * A test checking that the server and client can start and connect to each other with minimal config by authentication manager resolver. + */ +@Slf4j +@SpringBootTest +@SpringJUnitConfig( + classes = {ServiceConfiguration.class, DualInProcessConfiguration.class, BaseAutoConfiguration.class, + ManualSecurityConfiguration.class, WithBasicAuthAndManagerResolverSecurityConfiguration.class}) +@DirtiesContext +class ManualSecurityWithBasicAuthAndManagerResolverTest extends AbstractSecurityWithBasicAuthTest { + + // The secondary stubs use the secondary server + + @GrpcClient("test-secondary") + protected TestServiceStub serviceStubSecondary; + @GrpcClient("test-secondary") + protected TestServiceBlockingStub blockingStubSecondary; + @GrpcClient("test-secondary") + protected TestServiceFutureStub futureStubSecondary; + + @GrpcClient("noPerm-secondary") + protected TestServiceStub noPermStubSecondary; + @GrpcClient("noPerm-secondary") + protected TestServiceBlockingStub noPermBlockingStubSecondary; + @GrpcClient("noPerm-secondary") + protected TestServiceFutureStub noPermFutureStubSecondary; + + ManualSecurityWithBasicAuthAndManagerResolverTest() { + log.info("--- ManualSecurityWithBasicAuthAndManagerResolverTest ---"); + } + + @Override + @DirtiesContext + @TestFactory + DynamicTestCollection unprotectedCallTests() { + return super.unprotectedCallTests() + .add("unprotected-secondary", + () -> assertNormalCallSuccess(this.serviceStubSecondary, this.blockingStubSecondary, + this.futureStubSecondary)) + .add("unprotected-noPerm-secondary", + () -> assertNormalCallSuccess(this.noPermStubSecondary, this.noPermBlockingStubSecondary, + this.noPermFutureStubSecondary)); + } + + @Override + @DirtiesContext + @TestFactory + DynamicTestCollection unaryCallTest() { + return super.unaryCallTest() + .add("unary-secondary", + () -> assertUnaryCallSuccess(this.serviceStubSecondary, this.blockingStubSecondary, + this.futureStubSecondary)) + .add("unary-noPerm-secondary", + () -> assertUnaryCallFailure(this.noPermStubSecondary, this.noPermBlockingStubSecondary, + this.noPermFutureStubSecondary, PERMISSION_DENIED)); + } + + @Override + @DirtiesContext + @TestFactory + DynamicTestCollection clientStreamingCallTests() { + return super.clientStreamingCallTests() + .add("clientStreaming-secondary", + () -> assertClientStreamingCallFailure(this.serviceStubSecondary, PERMISSION_DENIED)) + .add("clientStreaming-noPerm-secondary", + () -> assertClientStreamingCallFailure(this.noPermStubSecondary, PERMISSION_DENIED)); + } + + @Override + @DirtiesContext + @TestFactory + DynamicTestCollection serverStreamingCallTests() { + return super.serverStreamingCallTests() + .add("serverStreaming-secondary", + () -> assertServerStreamingCallSuccess(this.serviceStubSecondary)) + .add("serverStreaming-noPerm-secondary", + () -> assertServerStreamingCallSuccess(this.noPermStubSecondary)); + } + + @Override + @DirtiesContext + @TestFactory + DynamicTestCollection bidiStreamingCallTests() { + return super.bidiStreamingCallTests() + .add("bidiStreaming-secondary", + () -> assertServerStreamingCallSuccess(this.serviceStubSecondary)) + .add("bidiStreaming-noPerm-secondary", + () -> assertServerStreamingCallSuccess(this.noPermStubSecondary)); + } + +} From 9a39d9b688459f13b9c123df39a290b5c7cda7fb Mon Sep 17 00:00:00 2001 From: Sajad Mehrabi Date: Sat, 3 Feb 2024 20:54:47 +0330 Subject: [PATCH 4/9] Refactor using spotlessApply and Add new autoconfiguration --- docs/en/server/security.md | 6 +- .../GrpcServerSecurityAutoConfiguration.java | 21 +--- ...yWithManagerResolverAutoConfiguration.java | 108 ++++++++++++++++++ ...stractAuthenticatingServerInterceptor.java | 42 +++---- ...efaultAuthenticatingServerInterceptor.java | 17 +-- .../interceptors/GrpcServerRequest.java | 19 ++- ...solverAuthenticatingServerInterceptor.java | 22 ++-- .../test/config/BaseAutoConfiguration.java | 2 + ...dManagerResolverSecurityConfiguration.java | 27 ++--- ...tyWithBasicAuthAndManagerResolverTest.java | 16 +-- 10 files changed, 197 insertions(+), 83 deletions(-) create mode 100644 grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java diff --git a/docs/en/server/security.md b/docs/en/server/security.md index df4ee5276..722ce7177 100644 --- a/docs/en/server/security.md +++ b/docs/en/server/security.md @@ -211,11 +211,11 @@ See also [Mutual Certificate Authentication](#mutual-certificate-authentication) #### Using AuthenticationManagerResolver -You can also use the `AuthenticationManagerResolver` to dynamically determine the authentication -manager to use for a particular request. This can be useful for applications that support multiple authentication +You can also use the `AuthenticationManagerResolver` to dynamically determine the authentication manager to use for +a particular request. This can be useful for applications that support multiple authentication mechanisms, such as OAuth and OpenID Connect, or that want to delegate authentication to external services. -To use `AuthenticationManagerResolver`, you first need to create a bean that implements +To use `AuthenticationManagerResolver`, you first need to create a bean that implements the `AuthenticationManagerResolver` interface instead of `AuthenticationManager`. The `resolve()` method of this bean should return the AuthenticationManager to use for a particular request. diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java index 8fe5cd640..5e8146b66 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java @@ -25,7 +25,6 @@ import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.core.AuthenticationException; import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; @@ -33,8 +32,6 @@ import net.devh.boot.grpc.server.security.interceptors.AuthenticatingServerInterceptor; import net.devh.boot.grpc.server.security.interceptors.AuthorizationCheckingServerInterceptor; import net.devh.boot.grpc.server.security.interceptors.DefaultAuthenticatingServerInterceptor; -import net.devh.boot.grpc.server.security.interceptors.GrpcServerRequest; -import net.devh.boot.grpc.server.security.interceptors.ManagerResolverAuthenticatingServerInterceptor; import net.devh.boot.grpc.server.security.interceptors.ExceptionTranslatingServerInterceptor; /** @@ -62,6 +59,7 @@ * @author Daniel Theuke (daniel.theuke@heuboe.de) */ @Configuration(proxyBeanMethods = false) +@ConditionalOnBean(AuthenticationManager.class) @AutoConfigureAfter(SecurityAutoConfiguration.class) public class GrpcServerSecurityAutoConfiguration { @@ -85,7 +83,6 @@ public ExceptionTranslatingServerInterceptor exceptionTranslatingServerIntercept * @return The authenticatingServerInterceptor bean. */ @Bean - @ConditionalOnBean(AuthenticationManager.class) @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) public DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor( final AuthenticationManager authenticationManager, @@ -93,22 +90,6 @@ public DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor( return new DefaultAuthenticatingServerInterceptor(authenticationManager, authenticationReader); } - /** - * The security interceptor that handles the authentication of requests. - * - * @param grpcAuthenticationManagerResolver The authentication manager resolver used to verify the credentials. - * @param authenticationReader The authentication reader used to extract the credentials from the call. - * @return The authenticatingServerInterceptor bean. - */ - @Bean - @ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) - @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) - public ManagerResolverAuthenticatingServerInterceptor managerResolverAuthenticatingServerInterceptor( - final AuthenticationManagerResolver grpcAuthenticationManagerResolver, - final GrpcAuthenticationReader authenticationReader) { - return new ManagerResolverAuthenticatingServerInterceptor(grpcAuthenticationManagerResolver, authenticationReader); - } - /** * The security interceptor that handles the authorization of requests. * diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java new file mode 100644 index 000000000..1d4b11078 --- /dev/null +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 + * + * http://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 net.devh.boot.grpc.server.autoconfigure; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.core.AuthenticationException; + +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; +import net.devh.boot.grpc.server.security.check.GrpcSecurityMetadataSource; +import net.devh.boot.grpc.server.security.interceptors.*; + +/** + * Auto configuration class with the required beans for the spring-security configuration of the grpc server. + * + *

+ * To enable security add both an {@link AuthenticationManager} and a {@link GrpcAuthenticationReader} to the + * application context. The authentication reader obtains the credentials from the requests which then will be validated + * by the authentication manager. After that, you can decide how you want to secure your application. Currently these + * options are available: + *

+ * + *
    + *
  • Use Spring Security's annotations. This requires + * {@code @EnableGlobalMethodSecurity(proxyTargetClass = true, ...)}.
  • + *
  • Having both an {@link AccessDecisionManager} and a {@link GrpcSecurityMetadataSource} in the application context. + *
+ * + *

+ * Note: The order of the beans is important! First the exception translating interceptor, then the + * authenticating interceptor and finally the authorization checking interceptor. That is necessary because they are + * executed in the same order as their order. + *

+ * + * @author Daniel Theuke (daniel.theuke@heuboe.de) + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) +@AutoConfigureAfter(SecurityAutoConfiguration.class) +public class GrpcServerSecurityWithManagerResolverAutoConfiguration { + + /** + * The interceptor for handling security related exceptions such as {@link AuthenticationException} and + * {@link AccessDeniedException}. + * + * @return The exceptionTranslatingServerInterceptor bean. + */ + @Bean + @ConditionalOnMissingBean + public ExceptionTranslatingServerInterceptor exceptionTranslatingServerInterceptor() { + return new ExceptionTranslatingServerInterceptor(); + } + + /** + * The security interceptor that handles the authentication of requests. + * + * @param grpcAuthenticationManagerResolver The authentication manager resolver used to verify the credentials. + * @param authenticationReader The authentication reader used to extract the credentials from the call. + * @return The authenticatingServerInterceptor bean. + */ + @Bean + @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) + public ManagerResolverAuthenticatingServerInterceptor managerResolverAuthenticatingServerInterceptor( + final AuthenticationManagerResolver grpcAuthenticationManagerResolver, + final GrpcAuthenticationReader authenticationReader) { + return new ManagerResolverAuthenticatingServerInterceptor(grpcAuthenticationManagerResolver, + authenticationReader); + } + + /** + * The security interceptor that handles the authorization of requests. + * + * @param accessDecisionManager The access decision manager used to check the requesting user. + * @param securityMetadataSource The source for the security metadata (access constraints). + * @return The authorizationCheckingServerInterceptor bean. + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean({AccessDecisionManager.class, GrpcSecurityMetadataSource.class}) + public AuthorizationCheckingServerInterceptor authorizationCheckingServerInterceptor( + final AccessDecisionManager accessDecisionManager, + final GrpcSecurityMetadataSource securityMetadataSource) { + return new AuthorizationCheckingServerInterceptor(accessDecisionManager, securityMetadataSource); + } + +} diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java index ebf55de6c..f1617006c 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java @@ -16,10 +16,8 @@ package net.devh.boot.grpc.server.security.interceptors; -import io.grpc.*; -import lombok.extern.slf4j.Slf4j; -import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; -import org.springframework.beans.factory.annotation.Autowired; +import static java.util.Objects.requireNonNull; + import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; @@ -27,7 +25,9 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import static java.util.Objects.requireNonNull; +import io.grpc.*; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; /** @@ -60,7 +60,7 @@ protected AbstractAuthenticatingServerInterceptor(final GrpcAuthenticationReader @Override public ServerCall.Listener interceptCall(final ServerCall call, - final Metadata headers, final ServerCallHandler next) { + final Metadata headers, final ServerCallHandler next) { Authentication authentication; try { authentication = this.grpcAuthenticationReader.readAuthentication(call, headers); @@ -105,7 +105,8 @@ public ServerCall.Listener interceptCall(final ServerCall ServerCall.Listener interceptCall(final ServerCall * - * @param call The call instance to receive response messages. - * @param headers The headers associated with the call. + * @param call The call instance to receive response messages. + * @param headers The headers associated with the call. * @param authentication The successful authentication instance. */ protected void onSuccessfulAuthentication( @@ -184,9 +186,9 @@ protected void onSuccessfulAuthentication( * By default, this method does nothing. *

* - * @param call The call instance to receive response messages. + * @param call The call instance to receive response messages. * @param headers The headers associated with the call. - * @param failed The exception related to the unsuccessful authentication. + * @param failed The exception related to the unsuccessful authentication. */ protected void onUnsuccessfulAuthentication( final ServerCall call, @@ -220,12 +222,12 @@ private static class AuthenticatingServerCallListener extends AbstractAuth * Creates a new AuthenticatingServerCallListener which will attach the given security context before delegating * to the given listener. * - * @param delegate The listener to delegate to. - * @param grpcContext The context to attach. + * @param delegate The listener to delegate to. + * @param grpcContext The context to attach. * @param securityContext The security context instance to attach. */ public AuthenticatingServerCallListener(final ServerCall.Listener delegate, final Context grpcContext, - final SecurityContext securityContext) { + final SecurityContext securityContext) { super(delegate, grpcContext); this.securityContext = securityContext; } diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java index 50ce2f538..aec5dd0d6 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java @@ -16,18 +16,19 @@ package net.devh.boot.grpc.server.security.interceptors; -import io.grpc.*; -import lombok.extern.slf4j.Slf4j; -import net.devh.boot.grpc.common.util.InterceptorOrder; -import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; -import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; +import static java.util.Objects.requireNonNull; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import static java.util.Objects.requireNonNull; +import io.grpc.*; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.common.util.InterceptorOrder; +import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; /** * A server interceptor that tries to {@link GrpcAuthenticationReader read} the credentials from the client and @@ -56,11 +57,11 @@ public class DefaultAuthenticatingServerInterceptor extends AbstractAuthenticati * Creates a new DefaultAuthenticatingServerInterceptor with the given authentication manager and reader. * * @param authenticationManager The authentication manager used to verify the credentials. - * @param authenticationReader The authentication reader used to extract the credentials from the call. + * @param authenticationReader The authentication reader used to extract the credentials from the call. */ @Autowired public DefaultAuthenticatingServerInterceptor(final AuthenticationManager authenticationManager, - final GrpcAuthenticationReader authenticationReader) { + final GrpcAuthenticationReader authenticationReader) { super(authenticationReader); this.authenticationManager = requireNonNull(authenticationManager, "authenticationManager"); } diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java index 6dc9c23da..5ae2bc1e8 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/GrpcServerRequest.java @@ -1,11 +1,26 @@ +/* + * Copyright (c) 2016-2024 The gRPC-Spring 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 + * + * http://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 net.devh.boot.grpc.server.security.interceptors; +import static java.util.Objects.requireNonNull; + import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.ServerCall; -import static java.util.Objects.requireNonNull; - /** * Allows access gRPC specific aspects of a server request during sampling and parsing. * diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java index d73bfe298..69347c7a2 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -16,19 +16,19 @@ package net.devh.boot.grpc.server.security.interceptors; -import io.grpc.*; -import lombok.extern.slf4j.Slf4j; -import net.devh.boot.grpc.common.util.InterceptorOrder; -import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; -import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; +import static java.util.Objects.requireNonNull; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; - -import static java.util.Objects.requireNonNull; +import io.grpc.*; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.common.util.InterceptorOrder; +import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; /** * A server interceptor that tries to {@link GrpcAuthenticationReader read} the credentials from the client and @@ -54,17 +54,19 @@ public class ManagerResolverAuthenticatingServerInterceptor extends AbstractAuth private final AuthenticationManagerResolver authenticationManagerResolver; /** - * Creates a new ManagerResolverAuthenticatingServerInterceptor with the given authentication manager resolver and reader. + * Creates a new ManagerResolverAuthenticatingServerInterceptor with the given authentication manager resolver and + * reader. * * @param authenticationManagerResolver The authentication manager resolver used to verify the credentials. - * @param authenticationReader The authentication reader used to extract the credentials from the call. + * @param authenticationReader The authentication reader used to extract the credentials from the call. */ @Autowired public ManagerResolverAuthenticatingServerInterceptor( final AuthenticationManagerResolver authenticationManagerResolver, final GrpcAuthenticationReader authenticationReader) { super(authenticationReader); - this.authenticationManagerResolver = requireNonNull(authenticationManagerResolver, "authenticationManagerResolver"); + this.authenticationManagerResolver = + requireNonNull(authenticationManagerResolver, "authenticationManagerResolver"); } diff --git a/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java b/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java index bea9d69ff..88a76bd10 100644 --- a/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java +++ b/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java @@ -24,10 +24,12 @@ import net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration; import net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration; import net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityAutoConfiguration; +import net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityWithManagerResolverAutoConfiguration; @Configuration @ImportAutoConfiguration({GrpcCommonCodecAutoConfiguration.class, GrpcServerAutoConfiguration.class, GrpcServerFactoryAutoConfiguration.class, GrpcServerSecurityAutoConfiguration.class, + GrpcServerSecurityWithManagerResolverAutoConfiguration.class, GrpcClientAutoConfiguration.class}) public class BaseAutoConfiguration { diff --git a/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java b/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java index fb512a2dd..128f531ab 100644 --- a/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java +++ b/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java @@ -16,16 +16,15 @@ package net.devh.boot.grpc.test.config; -import com.google.common.collect.ImmutableMap; -import io.grpc.CallCredentials; -import lombok.extern.slf4j.Slf4j; -import net.devh.boot.grpc.client.inject.StubTransformer; -import net.devh.boot.grpc.client.security.CallCredentialsHelper; -import net.devh.boot.grpc.server.security.authentication.*; -import net.devh.boot.grpc.server.security.interceptors.GrpcServerRequest; +import static net.devh.boot.grpc.client.security.CallCredentialsHelper.basicAuth; +import static net.devh.boot.grpc.common.security.SecurityConstants.AUTHORIZATION_HEADER; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AnonymousAuthenticationProvider; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; @@ -37,12 +36,14 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import com.google.common.collect.ImmutableMap; -import static net.devh.boot.grpc.client.security.CallCredentialsHelper.basicAuth; -import static net.devh.boot.grpc.common.security.SecurityConstants.AUTHORIZATION_HEADER; +import io.grpc.CallCredentials; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.client.inject.StubTransformer; +import net.devh.boot.grpc.client.security.CallCredentialsHelper; +import net.devh.boot.grpc.server.security.authentication.*; +import net.devh.boot.grpc.server.security.interceptors.GrpcServerRequest; @Slf4j @Configuration diff --git a/tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java b/tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java index 55cd3a416..165738832 100644 --- a/tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java +++ b/tests/src/test/java/net/devh/boot/grpc/test/security/ManualSecurityWithBasicAuthAndManagerResolverTest.java @@ -16,6 +16,13 @@ package net.devh.boot.grpc.test.security; +import static io.grpc.Status.Code.PERMISSION_DENIED; + +import org.junit.jupiter.api.TestFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + import lombok.extern.slf4j.Slf4j; import net.devh.boot.grpc.client.inject.GrpcClient; import net.devh.boot.grpc.test.config.*; @@ -23,15 +30,10 @@ import net.devh.boot.grpc.test.proto.TestServiceGrpc.TestServiceFutureStub; import net.devh.boot.grpc.test.proto.TestServiceGrpc.TestServiceStub; import net.devh.boot.grpc.test.util.DynamicTestCollection; -import org.junit.jupiter.api.TestFactory; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static io.grpc.Status.Code.PERMISSION_DENIED; /** - * A test checking that the server and client can start and connect to each other with minimal config by authentication manager resolver. + * A test checking that the server and client can start and connect to each other with minimal config by authentication + * manager resolver. */ @Slf4j @SpringBootTest From 23f5fa167b08da89e847345c311512ed21c41a16 Mon Sep 17 00:00:00 2001 From: Sajad Mehrabi Date: Mon, 5 Feb 2024 14:44:57 +0330 Subject: [PATCH 5/9] Merge AutoConfig and Edit AuthenticationManagerResolver behavior --- .../GrpcServerSecurityAutoConfiguration.java | 26 ++++- ...yWithManagerResolverAutoConfiguration.java | 108 ------------------ ...stractAuthenticatingServerInterceptor.java | 10 +- .../interceptors/GrpcServerRequest.java | 2 +- .../test/config/BaseAutoConfiguration.java | 2 - ...dManagerResolverSecurityConfiguration.java | 26 ++++- 6 files changed, 46 insertions(+), 128 deletions(-) delete mode 100644 grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java index 5e8146b66..f4676e38c 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java @@ -25,14 +25,12 @@ import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.core.AuthenticationException; import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; import net.devh.boot.grpc.server.security.check.GrpcSecurityMetadataSource; -import net.devh.boot.grpc.server.security.interceptors.AuthenticatingServerInterceptor; -import net.devh.boot.grpc.server.security.interceptors.AuthorizationCheckingServerInterceptor; -import net.devh.boot.grpc.server.security.interceptors.DefaultAuthenticatingServerInterceptor; -import net.devh.boot.grpc.server.security.interceptors.ExceptionTranslatingServerInterceptor; +import net.devh.boot.grpc.server.security.interceptors.*; /** * Auto configuration class with the required beans for the spring-security configuration of the grpc server. @@ -59,7 +57,7 @@ * @author Daniel Theuke (daniel.theuke@heuboe.de) */ @Configuration(proxyBeanMethods = false) -@ConditionalOnBean(AuthenticationManager.class) +@ConditionalOnBean(GrpcAuthenticationReader.class) @AutoConfigureAfter(SecurityAutoConfiguration.class) public class GrpcServerSecurityAutoConfiguration { @@ -83,6 +81,7 @@ public ExceptionTranslatingServerInterceptor exceptionTranslatingServerIntercept * @return The authenticatingServerInterceptor bean. */ @Bean + @ConditionalOnBean(AuthenticationManager.class) @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) public DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor( final AuthenticationManager authenticationManager, @@ -90,6 +89,23 @@ public DefaultAuthenticatingServerInterceptor authenticatingServerInterceptor( return new DefaultAuthenticatingServerInterceptor(authenticationManager, authenticationReader); } + /** + * The security interceptor that handles the authentication of requests. + * + * @param grpcAuthenticationManagerResolver The authentication manager resolver used to verify the credentials. + * @param authenticationReader The authentication reader used to extract the credentials from the call. + * @return The authenticatingServerInterceptor bean. + */ + @Bean + @ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) + @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) + public ManagerResolverAuthenticatingServerInterceptor managerResolverAuthenticatingServerInterceptor( + final AuthenticationManagerResolver grpcAuthenticationManagerResolver, + final GrpcAuthenticationReader authenticationReader) { + return new ManagerResolverAuthenticatingServerInterceptor(grpcAuthenticationManagerResolver, + authenticationReader); + } + /** * The security interceptor that handles the authorization of requests. * diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java deleted file mode 100644 index 1d4b11078..000000000 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityWithManagerResolverAutoConfiguration.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2016-2023 The gRPC-Spring 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 - * - * http://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 net.devh.boot.grpc.server.autoconfigure; - -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationManagerResolver; -import org.springframework.security.core.AuthenticationException; - -import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; -import net.devh.boot.grpc.server.security.check.GrpcSecurityMetadataSource; -import net.devh.boot.grpc.server.security.interceptors.*; - -/** - * Auto configuration class with the required beans for the spring-security configuration of the grpc server. - * - *

- * To enable security add both an {@link AuthenticationManager} and a {@link GrpcAuthenticationReader} to the - * application context. The authentication reader obtains the credentials from the requests which then will be validated - * by the authentication manager. After that, you can decide how you want to secure your application. Currently these - * options are available: - *

- * - *
    - *
  • Use Spring Security's annotations. This requires - * {@code @EnableGlobalMethodSecurity(proxyTargetClass = true, ...)}.
  • - *
  • Having both an {@link AccessDecisionManager} and a {@link GrpcSecurityMetadataSource} in the application context. - *
- * - *

- * Note: The order of the beans is important! First the exception translating interceptor, then the - * authenticating interceptor and finally the authorization checking interceptor. That is necessary because they are - * executed in the same order as their order. - *

- * - * @author Daniel Theuke (daniel.theuke@heuboe.de) - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnBean(parameterizedContainer = AuthenticationManagerResolver.class, value = GrpcServerRequest.class) -@AutoConfigureAfter(SecurityAutoConfiguration.class) -public class GrpcServerSecurityWithManagerResolverAutoConfiguration { - - /** - * The interceptor for handling security related exceptions such as {@link AuthenticationException} and - * {@link AccessDeniedException}. - * - * @return The exceptionTranslatingServerInterceptor bean. - */ - @Bean - @ConditionalOnMissingBean - public ExceptionTranslatingServerInterceptor exceptionTranslatingServerInterceptor() { - return new ExceptionTranslatingServerInterceptor(); - } - - /** - * The security interceptor that handles the authentication of requests. - * - * @param grpcAuthenticationManagerResolver The authentication manager resolver used to verify the credentials. - * @param authenticationReader The authentication reader used to extract the credentials from the call. - * @return The authenticatingServerInterceptor bean. - */ - @Bean - @ConditionalOnMissingBean(AuthenticatingServerInterceptor.class) - public ManagerResolverAuthenticatingServerInterceptor managerResolverAuthenticatingServerInterceptor( - final AuthenticationManagerResolver grpcAuthenticationManagerResolver, - final GrpcAuthenticationReader authenticationReader) { - return new ManagerResolverAuthenticatingServerInterceptor(grpcAuthenticationManagerResolver, - authenticationReader); - } - - /** - * The security interceptor that handles the authorization of requests. - * - * @param accessDecisionManager The access decision manager used to check the requesting user. - * @param securityMetadataSource The source for the security metadata (access constraints). - * @return The authorizationCheckingServerInterceptor bean. - */ - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({AccessDecisionManager.class, GrpcSecurityMetadataSource.class}) - public AuthorizationCheckingServerInterceptor authorizationCheckingServerInterceptor( - final AccessDecisionManager accessDecisionManager, - final GrpcSecurityMetadataSource securityMetadataSource) { - return new AuthorizationCheckingServerInterceptor(accessDecisionManager, securityMetadataSource); - } - -} diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java index f1617006c..866068279 100644 --- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerInterceptor.java @@ -84,14 +84,10 @@ public ServerCall.Listener interceptCall(final ServerCall call, Metadata headers) { return call.getMethodDescriptor(); } -} \ No newline at end of file +} diff --git a/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java b/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java index 88a76bd10..bea9d69ff 100644 --- a/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java +++ b/tests/src/test/java/net/devh/boot/grpc/test/config/BaseAutoConfiguration.java @@ -24,12 +24,10 @@ import net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration; import net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration; import net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityAutoConfiguration; -import net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityWithManagerResolverAutoConfiguration; @Configuration @ImportAutoConfiguration({GrpcCommonCodecAutoConfiguration.class, GrpcServerAutoConfiguration.class, GrpcServerFactoryAutoConfiguration.class, GrpcServerSecurityAutoConfiguration.class, - GrpcServerSecurityWithManagerResolverAutoConfiguration.class, GrpcClientAutoConfiguration.class}) public class BaseAutoConfiguration { diff --git a/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java b/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java index 128f531ab..e2ba65a2e 100644 --- a/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java +++ b/tests/src/test/java/net/devh/boot/grpc/test/config/WithBasicAuthAndManagerResolverSecurityConfiguration.java @@ -17,7 +17,6 @@ package net.devh.boot.grpc.test.config; import static net.devh.boot.grpc.client.security.CallCredentialsHelper.basicAuth; -import static net.devh.boot.grpc.common.security.SecurityConstants.AUTHORIZATION_HEADER; import java.util.ArrayList; import java.util.Arrays; @@ -35,6 +34,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import com.google.common.collect.ImmutableMap; @@ -75,17 +75,33 @@ DaoAuthenticationProvider daoAuthenticationProvider() { return provider; } + InMemoryUserDetailsManager inMemoryUserDetailsManager() { + PasswordEncoder passwordEncoder = passwordEncoder(); + InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(); + userDetailsService.createUser(new User("client1", passwordEncoder.encode("client1"), + List.of(new SimpleGrantedAuthority("ROLE_CLIENT1")))); + userDetailsService.createUser(new User("client2", passwordEncoder.encode("client2"), + List.of(new SimpleGrantedAuthority("ROLE_CLIENT2")))); + return userDetailsService; + } + + DaoAuthenticationProvider daoAuthenticationProviderInMemory() { + final DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(inMemoryUserDetailsManager()); + provider.setPasswordEncoder(passwordEncoder()); + return provider; + } @Bean AuthenticationManagerResolver authenticationManager() { return context -> { - String token = context.headers().get(AUTHORIZATION_HEADER); - if (token != null && token.startsWith("Basic")) { + String methodName = context.methodDescriptor().getFullMethodName(); + if (methodName.equals("TestService/normal")) { + return daoAuthenticationProviderInMemory()::authenticate; + } else { final List providers = new ArrayList<>(); providers.add(daoAuthenticationProvider()); return new ProviderManager(providers); - } else { - return null; } }; } From 93058372a0b78aa51e6005122d04039942d8affe Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 13 Apr 2024 09:14:15 +0200 Subject: [PATCH 6/9] Update grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java --- .../ManagerResolverAuthenticatingServerInterceptor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java index 69347c7a2..7e9c565e6 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -68,8 +68,6 @@ public ManagerResolverAuthenticatingServerInterceptor( this.authenticationManagerResolver = requireNonNull(authenticationManagerResolver, "authenticationManagerResolver"); } - - @Override protected AuthenticationManager getAuthenticationManager( final ServerCall call, From 4a754c2f240554d4941246c7539948893287ff74 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 13 Apr 2024 09:22:42 +0200 Subject: [PATCH 7/9] Update grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java --- .../ManagerResolverAuthenticatingServerInterceptor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java index 7e9c565e6..904eb91ad 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -68,6 +68,7 @@ public ManagerResolverAuthenticatingServerInterceptor( this.authenticationManagerResolver = requireNonNull(authenticationManagerResolver, "authenticationManagerResolver"); } + @Override protected AuthenticationManager getAuthenticationManager( final ServerCall call, From 58b356eaf519640bb64d7e7ce3e4431addf7bab5 Mon Sep 17 00:00:00 2001 From: Sajad Mehrabi Date: Mon, 15 Apr 2024 15:10:07 +0330 Subject: [PATCH 8/9] Remove unnecessary Autowired annotation --- .../interceptors/DefaultAuthenticatingServerInterceptor.java | 1 - .../ManagerResolverAuthenticatingServerInterceptor.java | 1 - 2 files changed, 2 deletions(-) diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java index aec5dd0d6..0ac9257f3 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java @@ -59,7 +59,6 @@ public class DefaultAuthenticatingServerInterceptor extends AbstractAuthenticati * @param authenticationManager The authentication manager used to verify the credentials. * @param authenticationReader The authentication reader used to extract the credentials from the call. */ - @Autowired public DefaultAuthenticatingServerInterceptor(final AuthenticationManager authenticationManager, final GrpcAuthenticationReader authenticationReader) { super(authenticationReader); diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java index 904eb91ad..d02340a5f 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -60,7 +60,6 @@ public class ManagerResolverAuthenticatingServerInterceptor extends AbstractAuth * @param authenticationManagerResolver The authentication manager resolver used to verify the credentials. * @param authenticationReader The authentication reader used to extract the credentials from the call. */ - @Autowired public ManagerResolverAuthenticatingServerInterceptor( final AuthenticationManagerResolver authenticationManagerResolver, final GrpcAuthenticationReader authenticationReader) { From 0c863e8e17aff98a125c1245cbc5d879c18e86a3 Mon Sep 17 00:00:00 2001 From: Sajad Mehrabi Date: Mon, 15 Apr 2024 17:20:06 +0330 Subject: [PATCH 9/9] Remove unused import statement --- .../interceptors/DefaultAuthenticatingServerInterceptor.java | 1 - .../ManagerResolverAuthenticatingServerInterceptor.java | 1 - 2 files changed, 2 deletions(-) diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java index 0ac9257f3..c539de3cd 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java @@ -18,7 +18,6 @@ import static java.util.Objects.requireNonNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java index d02340a5f..e32793f85 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ManagerResolverAuthenticatingServerInterceptor.java @@ -18,7 +18,6 @@ import static java.util.Objects.requireNonNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication;