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;