Add support for events and anonymous
This commit is contained in:
@@ -44,8 +44,10 @@ public class GrpcServerApplication {
|
||||
.hasAuthority("ROLE_ADMIN")
|
||||
.methods("Simple/SayHello")
|
||||
.hasAuthority("ROLE_USER")
|
||||
.methods("grpc.*/*")
|
||||
.permitAll()
|
||||
.allRequests()
|
||||
.permitAll())
|
||||
.denyAll())
|
||||
.httpBasic(withDefaults())
|
||||
.preauth(withDefaults())
|
||||
.authenticationExtractor((headers, attributes) -> {
|
||||
|
||||
@@ -4,7 +4,10 @@ import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -30,6 +33,10 @@ import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.Status.Code;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.reflection.v1.ServerReflectionGrpc;
|
||||
import io.grpc.reflection.v1.ServerReflectionRequest;
|
||||
import io.grpc.reflection.v1.ServerReflectionResponse;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
@SpringBootTest(properties = { "spring.grpc.server.port=0",
|
||||
"spring.grpc.client.channels.stub.address=static://0.0.0.0:${local.grpc.port}",
|
||||
@@ -45,6 +52,10 @@ public class GrpcServerApplicationTests {
|
||||
@Qualifier("stub")
|
||||
private SimpleGrpc.SimpleBlockingStub stub;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("reflect")
|
||||
private ServerReflectionGrpc.ServerReflectionStub reflect;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("secure")
|
||||
private SimpleGrpc.SimpleBlockingStub secure;
|
||||
@@ -66,6 +77,32 @@ public class GrpcServerApplicationTests {
|
||||
assertEquals(Code.UNAUTHENTICATED, exception.getStatus().getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
void anonymous() throws Exception {
|
||||
AtomicReference<ServerReflectionResponse> response = new AtomicReference<>();
|
||||
AtomicBoolean error = new AtomicBoolean();
|
||||
StreamObserver<ServerReflectionResponse> responses = new StreamObserver<ServerReflectionResponse>() {
|
||||
@Override
|
||||
public void onNext(ServerReflectionResponse value) {
|
||||
response.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
error.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
}
|
||||
};
|
||||
StreamObserver<ServerReflectionRequest> request = reflect.serverReflectionInfo(responses);
|
||||
request.onNext(ServerReflectionRequest.newBuilder().setListServices("").build());
|
||||
request.onCompleted();
|
||||
Awaitility.await().until(() -> response.get() != null || error.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
void unauthauthorized() {
|
||||
@@ -114,7 +151,7 @@ public class GrpcServerApplicationTests {
|
||||
@Lazy
|
||||
SimpleGrpc.SimpleBlockingStub basic(GrpcChannelFactory channels) {
|
||||
return SimpleGrpc.newBlockingStub(channels.createChannel("basic", ChannelBuilderOptions.defaults()
|
||||
.withInterceptors(List.of(new BasicAuthenticationInterceptor("user", "user")))));
|
||||
.withInterceptors(List.of(new BasicAuthenticationInterceptor("user", "user")))));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -123,6 +160,12 @@ public class GrpcServerApplicationTests {
|
||||
return SimpleGrpc.newBlockingStub(channels.createChannel("stub"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
ServerReflectionGrpc.ServerReflectionStub reflect(GrpcChannelFactory channels, @LocalGrpcPort int port) {
|
||||
return ServerReflectionGrpc.newStub(channels.createChannel("stub"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -103,12 +103,14 @@ public class AuthenticationServerInterceptor implements ServerInterceptor, Order
|
||||
|
||||
@Override
|
||||
public void onReady() {
|
||||
if (this.authentication == null || !this.authentication.isAuthenticated()
|
||||
|| this.authentication instanceof AnonymousAuthenticationToken) {
|
||||
if (this.authentication == null || !this.authentication.isAuthenticated()) {
|
||||
throw new BadCredentialsException("not authenticated");
|
||||
}
|
||||
else {
|
||||
if (!this.authorizationManager.authorize(() -> this.authentication, this.context).isGranted()) {
|
||||
if (this.authentication instanceof AnonymousAuthenticationToken) {
|
||||
throw new BadCredentialsException("not authenticated");
|
||||
}
|
||||
throw new AccessDeniedException("not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,9 @@ import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
|
||||
public record CallContext(Metadata headers, Attributes attributes, MethodDescriptor<?, ?> method) {
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return "CallContext:[" + this.method.getFullMethodName() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,13 @@ import java.util.function.Supplier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManagers;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -38,14 +41,22 @@ public class RequestMapperConfigurer extends SecurityConfigurerAdapter<Authentic
|
||||
|
||||
private final Supplier<RoleHierarchy> roleHierarchy;
|
||||
|
||||
public RequestMapperConfigurer(ApplicationContext context) {
|
||||
private final AuthorizationEventPublisher publisher;
|
||||
|
||||
public RequestMapperConfigurer(ApplicationContext context) throws Exception {
|
||||
if (context.getBeanNamesForType(AuthorizationEventPublisher.class).length > 0) {
|
||||
this.publisher = context.getBean(AuthorizationEventPublisher.class);
|
||||
} else {
|
||||
this.publisher = new SpringAuthorizationEventPublisher(context);
|
||||
}
|
||||
this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0)
|
||||
? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy());
|
||||
? context.getBean(RoleHierarchy.class)
|
||||
: new NullRoleHierarchy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(GrpcSecurity builder) throws Exception {
|
||||
builder.authorizationManager(new RequestMapperAuthorizationManager(this.authorizedCalls));
|
||||
builder.authorizationManager(new RequestMapperAuthorizationManager(this.authorizedCalls, this.publisher));
|
||||
}
|
||||
|
||||
public AuthorizedCall allRequests() {
|
||||
@@ -111,6 +122,18 @@ public class RequestMapperConfigurer extends SecurityConfigurerAdapter<Authentic
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
|
||||
}
|
||||
|
||||
public RequestMapperConfigurer authenticated() {
|
||||
return access(AuthenticatedAuthorizationManager.authenticated());
|
||||
}
|
||||
|
||||
public RequestMapperConfigurer fullyAuthenticated() {
|
||||
return access(AuthenticatedAuthorizationManager.fullyAuthenticated());
|
||||
}
|
||||
|
||||
public RequestMapperConfigurer anonymous() {
|
||||
return access(AuthenticatedAuthorizationManager.anonymous());
|
||||
}
|
||||
|
||||
public RequestMapperConfigurer access(AuthorizationManager<Object> manager) {
|
||||
Assert.notNull(manager, "manager cannot be null");
|
||||
this.authorizationManager = (this.not) ? AuthorizationManagers.not(manager) : manager;
|
||||
@@ -127,20 +150,26 @@ public class RequestMapperConfigurer extends SecurityConfigurerAdapter<Authentic
|
||||
public static class RequestMapperAuthorizationManager implements AuthorizationManager<CallContext> {
|
||||
|
||||
private final List<AuthorizedCall> authorizedCalls;
|
||||
private final AuthorizationEventPublisher publisher;
|
||||
|
||||
public RequestMapperAuthorizationManager(List<AuthorizedCall> authorizedCalls) {
|
||||
public RequestMapperAuthorizationManager(List<AuthorizedCall> authorizedCalls,
|
||||
AuthorizationEventPublisher publisher) {
|
||||
this.authorizedCalls = authorizedCalls;
|
||||
this.publisher = publisher;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, CallContext context) {
|
||||
AuthorizationDecision result = new AuthorizationDecision(false);
|
||||
for (AuthorizedCall authorizedCall : this.authorizedCalls) {
|
||||
if (authorizedCall.matcher.matches(context)) {
|
||||
return authorizedCall.authorizationManager.check(authentication, context);
|
||||
result = authorizedCall.authorizationManager.check(authentication, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new AuthorizationDecision(false);
|
||||
this.publisher.publishAuthorizationEvent(authentication, context, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user