diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 6e712cb2..40c2b1e2 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -25,4 +25,4 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
- run: ./gradlew build
+ run: ./gradlew build ; docker images
diff --git a/README.adoc b/README.adoc
index 2dc20b6a..b4e03aae 100644
--- a/README.adoc
+++ b/README.adoc
@@ -38,7 +38,7 @@ repositories {
}
dependencies {
- compile 'io.github.lognet:grpc-spring-boot-starter:4.5.7'
+ compile 'io.github.lognet:grpc-spring-boot-starter:4.5.9'
}
@@ -48,10 +48,10 @@ By default, starter pulls `io.grpc:grpc-netty-shaded` as transitive dependency
[source,groovy]
----
- compile ('io.github.lognet:grpc-spring-boot-starter:4.5.7') {
+ compile ('io.github.lognet:grpc-spring-boot-starter:4.5.8') {
exclude group: 'io.grpc', module: 'grpc-netty-shaded'
}
- compile 'io.grpc:grpc-netty:1.40.0' // <1>
+ compile 'io.grpc:grpc-netty:1.41.0' // <1>
----
<1> Make sure to pull the version that matches the release.
@@ -281,11 +281,8 @@ Error handling interceptor has the highest precedence.
The way grpc interceptor works is that it intercepts the call and returns the server call listener, which in turn can intercept the request message as well, before forwarding it to the actual service call handler :
-****
-`interceptor_1(interceptCall)` -> `interceptor_2(interceptCall)` -> `interceptor_3(interceptCall)` -> +
-`interceptor_1(On_Message)`-> `interceptor_2(On_Message)`-> `interceptor_3(On_Message)`-> +
-`actual service call`
-****
+image:./images/interceptors_001.png[]
+
By setting `grpc.security.auth.fail-fast` property to `false` all downstream interceptors as well as all upstream interceptors (On_Message) will still be executed in case of authentication/authorization failure +
@@ -293,20 +290,13 @@ Assuming `interceptor_2` is `securityInterceptor` :
* For failed authentication/authorization with `grpc.security.auth.fail-fast=true`(default): +
+
+image:./images/interceptors_002.png[]
-****
-`interceptor_1(interceptCall)` -> `securityInterceptor(interceptCall)` - *Call is Closed* -> ++++++ `interceptor_3(interceptCall)` -> +
-`interceptor_1(On_Message)`-> `securityInterceptor(On_Message)`->`interceptor_3(On_Message)`-> +
-`actual service call`++++++
-****
* For failed authentication/authorization with `grpc.security.auth.fail-fast=false`: +
+
-****
-`interceptor_1(interceptCall)` -> `securityInterceptor(interceptCall)` -> `interceptor_3(interceptCall)` ->
-`interceptor_1(On_Message)`-> `securityInterceptor(On_Message)` - *Call is Closed* ++++++-> `interceptor_3(On_Message)`-> +
-`actual service call`++++++
-****
+image:./images/interceptors_003.png[]
+
=== Distributed tracing support (Spring Cloud Sleuth integration)
@@ -693,24 +683,24 @@ By following this approach you also decouple the transport layer and business lo
=== Setup
.Dependencies to implement authentiction scheme (to be added to server-side project)
-[cols="a,a"]
+[cols="1,4"]
|===
|Scheme |Dependencies
|Basic
-|
+a|
* `org.springframework.security:spring-security-config`
|Bearer
-|
+a|
* `org.springframework.security:spring-security-config`
* `org.springframework.security:spring-security-oauth2-jose`
* `org.springframework.security:spring-security-oauth2-resource-server`
|_Custom_
-|
+a|
* `org.springframework.security:spring-security-config`
* `your.custom.lib`
@@ -815,6 +805,71 @@ public AuthenticationSchemeSelector myCustomSchemeSelector(){
<> section explains how to pass custom authorization scheme and claim from GRPC client.
+=== @PreAuthorize() and @PostAuthorize() support
+Starting from version `4.5.9` you can also use standard `@PreAuthorize` and `@PostAuthorize` annotations on grpc service methods and grpc service types.
+
+.Referencing input/output object in expression
+[cols="1,1,2,6"]
+|===
+|Call Type |Input object ref |Output object ref | Sample
+
+|Unary +
+(request-response)
+|By parameter name
+a|`returnObject`
+a|
+[source,java]
+----
+@Override
+@PreAuthorize("#person.age<12")
+@PostAuthorize("returnObject.description.length()>0")
+public void unary(Person person, StreamObserver responseObserver) {
+ }
+----
+
+|Input stream, +
+single response
+a|`#p0` or `#a0`
+a|`returnObject`
+a|
+[source,java]
+----
+@Override
+@PreAuthorize("#p0.getAge()<12")
+@PostAuthorize("returnObject.description.length()>0")
+public StreamObserver inStream(StreamObserver responseObserver) {
+ }
+----
+
+|Single request, +
+output stream
+|By parameter name
+a|`returnObject`
+a|
+[source,java]
+----
+@Override
+@PreAuthorize("#person.age<12")
+@PostAuthorize("returnObject.description.length()>0")
+public void outStream(Person person, StreamObserver responseObserver) {
+}
+----
+
+|Bidi stream
+|`#p0` or `#a0`
+a|`returnObject`
+a|
+[source,java]
+----
+@Override
+@PreAuthorize("#p0.age<12")
+@PostAuthorize("returnObject.description.length()>0")
+public StreamObserver bidiStream(StreamObserver responseObserver) {
+}
+----
+|===
+
+
=== Obtaining Authentication details
To obtain `Authentication` object in the implementation of *secured method*, please use below snippet
@@ -912,9 +967,30 @@ Starting from version `3.3.0`, the starter will auto-register the running grpc s
The registered service name will be prefixed with `grpc-` ,i.e. `grpc-${spring.application.name}` to not interfere with standard registered web-service name if you choose to run both embedded `Grpc` and `Web` servers. +
-Setting `spring.cloud.consul.discovery.register-health-check` to true will register GRPC health check service with Consul.
+`ConsulDiscoveryProperties` are bound from configuration properties prefixed by `spring.cloud.consul.discovery` and then the values are overwritten by `grpc.consul.discovery` prefixed properties (if set). This allows you to have separate consul discovery configuration for `rest` and `grpc` services if you choose to expose both from your application.
+
+[source,yml]
+----
+spring:
+ cloud:
+ consul:
+ discovery:
+ metadata:
+ myKey: myValue <1>
+ tags:
+ - myWebTag <2>
+grpc:
+ consul:
+ discovery:
+ tags:
+ - myGrpcTag <3>
+----
+<1> Both `rest` and `grpc` services are registered with metadata `myKey=myValue`
+<2> Rest services are registered with `myWebTag`
+<3> Grpc services are registered with `myGrpcTag`
+
+Setting `spring.cloud.consul.discovery.register-health-check` (or `grpc.consul.discovery.register-health-check`) to `true` will register GRPC health check service with Consul.
-Tags could be set by defining `spring.cloud.consul.discovery.tags` property.
There are 4 supported registration modes :
@@ -925,7 +1001,7 @@ Please note that default implementation https://github.com/grpc/grpc-java/blob/b
In this mode the running grpc server is registered as single service with check per each discovered `grpc` service.
. `STANDALONE_SERVICES` +
In this mode each discovered grpc service is registered as single service with single check. Each registered service is tagged by its own service name.
-. `NOOP` - no grpc services registered. This mode is usefull if you serve both `rest` and `grpc` services in your application, but for some reason, only `rest` services should be registered with Consul.
+. `NOOP` - no grpc services registered. This mode is useful if you serve both `rest` and `grpc` services in your application, but for some reason, only `rest` services should be registered with Consul.
[source,yml]
.You can control the desired mode from application.properties
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 4cad1774..c88a2f36 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -1,5 +1,6 @@
| Starter Version | gRPC versions |Spring Boot version
| -------------------- |:-------------:|:------------------:|
+| [4.5.9](#version-459)| 1.41.0 |2.5.6 |
| [4.5.8](#version-458)| 1.41.0 |2.5.0 |
| [4.5.7](#version-457)| 1.40.1 |2.5.0 |
| [4.5.6](#version-456)| 1.40.0 |2.5.0 |
@@ -27,6 +28,21 @@
| [4.0.0](#version-400)| 1.32.1 |2.3.3.RELEASE |
| [3.5.7](#version-357)| 1.31.1 |1.5.13.RELEASE |
+# Version 4.5.9
+## :star: New Features
+
+- Support separate consul discovery properties for grpc and http services [#250](https://github.com/LogNet/grpc-spring-boot-starter/issues/250)
+- Add metadata to consul service discovery [#249](https://github.com/LogNet/grpc-spring-boot-starter/issues/249)
+- Spring security SPEL expressions support (`@PreAuthorize` and `@PostAuthorize`) [#175](https://github.com/LogNet/grpc-spring-boot-starter/issues/175)
+
+## :lady_beetle: Bug Fixes
+
+- Circular bean dependency since 4.5.8 [#253](https://github.com/LogNet/grpc-spring-boot-starter/issues/253)
+
+## :hammer: Dependency Upgrades
+
+- Upgrade spring boot to 2.5.6 [#255](https://github.com/LogNet/grpc-spring-boot-starter/issues/255)
+
# Version 4.5.8
## :star: New Features
diff --git a/build.gradle b/build.gradle
index 08e6d956..061e526c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -48,10 +48,11 @@ subprojects {
apply plugin: 'net.ltgt.errorprone'
- sourceCompatibility = 1.8
- targetCompatibility = 1.8
+
tasks.withType(JavaCompile,{
- options.compilerArgs.addAll(["--release", "8"])
+ options.compilerArgs.addAll(["--release", "8"]) // jdk 11 flag
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
})
diff --git a/gradle.properties b/gradle.properties
index 2c6b967b..c8bc1331 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,11 +1,11 @@
grpcVersion=1.41.0
-springBootVersion=2.5.0
-springCloudVersion=2020.0.3
+springBootVersion=2.5.6
+springCloudVersion=2020.0.4
gradleErrorPronePluginVersion=2.0.2
errorProneVersion=2.7.1
lombokVersion=1.18.20
-version=4.5.8
+version=4.5.9
group=io.github.lognet
description=Spring Boot starter for Google RPC.
gitHubUrl=https\://github.com/LogNet/grpc-spring-boot-starter
diff --git a/grpc-spring-boot-starter-demo/build.gradle b/grpc-spring-boot-starter-demo/build.gradle
index a2767a5a..3b9071ea 100644
--- a/grpc-spring-boot-starter-demo/build.gradle
+++ b/grpc-spring-boot-starter-demo/build.gradle
@@ -22,7 +22,9 @@ facets {
kafkaStreamTest
customSecurityTest
bothPureAndShadedNettyTest
+ noConsulDependenciesTest
}
+
grpcSpringBoot {
grpcSpringBootStarterVersion.set((String)null)
}
@@ -42,26 +44,25 @@ configurations.findAll{ cfg ->
cfg.exclude group: 'org.springframework.security', module: 'spring-security-oauth2-resource-server'
cfg.exclude group: 'org.springframework.security', module: 'spring-security-oauth2-jose'
}
+ if (cfg.name.startsWith("noConsulDependencies")) {
+ cfg.exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-consul-discovery'
+ }
-}
-configurations {
- pureNettyTestCompile.extendsFrom( testCompile)
- pureNettyTestRuntime.extendsFrom(testRuntime)
+}
- customSecurityTestCompile.extendsFrom( testCompile)
- customSecurityTestRuntime.extendsFrom(testRuntime)
- bothPureAndShadedNettyTestCompile.extendsFrom( testCompile)
- bothPureAndShadedNettyTestRuntime.extendsFrom( testRuntime)
+extensions.facets.each{
+ if(it.name.endsWith("Test")) {
+ configurations.getByName("${it.name}Compile").extendsFrom(configurations.testCompile)
+ configurations.getByName("${it.name}Runtime").extendsFrom(configurations.testRuntime)
- kafkaStreamTestCompile.extendsFrom( testCompile)
- kafkaStreamTestRuntime.extendsFrom(testRuntime)
+ dependencies.add("${it.name}Compile", sourceSets.test.output)
+ }
}
-
dependencies {
implementation "org.springframework.boot:spring-boot-starter-actuator"
@@ -82,30 +83,27 @@ dependencies {
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'com.github.stefanbirkner:system-rules:1.18.0'
testCompile('org.springframework.cloud:spring-cloud-starter-consul-discovery')
- testCompile 'com.pszymczyk.consul:embedded-consul:2.2.1'
testCompile 'org.awaitility:awaitility:4.0.3'
testCompile "org.springframework.cloud:spring-cloud-config-server"
testCompile "org.springframework.cloud:spring-cloud-config-client"
testCompile "org.springframework.cloud:spring-cloud-starter-bootstrap"
- testCompile "com.playtika.testcontainers:embedded-keycloak:2.0.14"
+ testCompile "com.playtika.testcontainers:embedded-keycloak:2.0.16"
+ testCompile "com.playtika.testcontainers:embedded-consul:2.0.16"
- testImplementation 'org.hamcrest:hamcrest:2.1'
+ testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.mockito:mockito-core:2.23.0'
- customSecurityTestCompile sourceSets.test.output
- pureNettyTestCompile sourceSets.test.output
pureNettyTestCompile "io.grpc:grpc-netty"
- bothPureAndShadedNettyTestCompile sourceSets.test.output
bothPureAndShadedNettyTestCompile "io.grpc:grpc-netty"
- kafkaStreamTestCompile sourceSets.test.output
kafkaStreamTestCompile "com.playtika.testcontainers:embedded-kafka:2.0.9"
kafkaStreamTestCompile "org.springframework.cloud:spring-cloud-starter-stream-kafka"
+
//testCompile "org.testcontainers:junit-jupiter:1.14.3"
diff --git a/grpc-spring-boot-starter-demo/src/customSecurityTest/java/org/lognet/springboot/grpc/auth/CustomSecurityTest.java b/grpc-spring-boot-starter-demo/src/customSecurityTest/java/org/lognet/springboot/grpc/auth/CustomSecurityTest.java
index d56ce8f9..98d08066 100644
--- a/grpc-spring-boot-starter-demo/src/customSecurityTest/java/org/lognet/springboot/grpc/auth/CustomSecurityTest.java
+++ b/grpc-spring-boot-starter-demo/src/customSecurityTest/java/org/lognet/springboot/grpc/auth/CustomSecurityTest.java
@@ -11,7 +11,6 @@
import org.lognet.springboot.grpc.demo.DemoApp;
import org.lognet.springboot.grpc.security.AuthCallCredentials;
import org.lognet.springboot.grpc.security.AuthHeader;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.springframework.boot.test.context.SpringBootTest;
@@ -33,33 +32,27 @@
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApp.class}, webEnvironment = NONE)
-@Import(CustomSecurityTest.TestConfig.class)
+@Import(CustomSecurityTest.DemoGrpcSecurityConfig.class)
public class CustomSecurityTest extends GrpcServerTestBase {
private final static String MY_CUSTOM_SCHEME_NAME = "custom";
@TestConfiguration
- static class TestConfig {
-
- @EnableGrpcSecurity
- public class DemoGrpcSecurityConfig extends GrpcSecurityConfigurerAdapter {
-
-
- @Override
- public void configure(GrpcSecurity builder) throws Exception {
- builder.authorizeRequests()
- .withSecuredAnnotation()
- .authenticationSchemeSelector(scheme ->
- Optional.of(scheme.toString())
- .filter(s -> s.startsWith(MY_CUSTOM_SCHEME_NAME))
- .map(s -> s.substring(MY_CUSTOM_SCHEME_NAME.length() + 1))
- .map(token -> {
- final String[] chunks = token.split("#");
- return new TestingAuthenticationToken(token.split("#")[0], null, "SCOPE_" + chunks[1]);
- })
- )
- .authenticationProvider(new TestingAuthenticationProvider());
- }
-
+ public static class DemoGrpcSecurityConfig extends GrpcSecurityConfigurerAdapter {
+
+ @Override
+ public void configure(GrpcSecurity builder) throws Exception {
+ builder.authorizeRequests()
+ .withSecuredAnnotation()
+ .authenticationSchemeSelector(scheme ->
+ Optional.of(scheme.toString())
+ .filter(s -> s.startsWith(MY_CUSTOM_SCHEME_NAME))
+ .map(s -> s.substring(MY_CUSTOM_SCHEME_NAME.length() + 1))
+ .map(token -> {
+ final String[] chunks = token.split("#");
+ return new TestingAuthenticationToken(token.split("#")[0], null, "SCOPE_" + chunks[1]);
+ })
+ )
+ .authenticationProvider(new TestingAuthenticationProvider());
}
}
diff --git a/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/DemoAppConfiguration.java b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/DemoAppConfiguration.java
index 42da990b..2cedfc4a 100644
--- a/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/DemoAppConfiguration.java
+++ b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/DemoAppConfiguration.java
@@ -2,11 +2,9 @@
import io.grpc.examples.CalculatorGrpc;
import io.grpc.examples.CalculatorOuterClass;
-import io.grpc.examples.SecuredCalculatorGrpc;
import io.grpc.stub.StreamObserver;
import org.lognet.springboot.grpc.GRpcService;
import org.springframework.context.annotation.Configuration;
-import org.springframework.security.access.annotation.Secured;
@Configuration
public class DemoAppConfiguration {
@@ -21,7 +19,7 @@ public void calculate(CalculatorOuterClass.CalculatorRequest request, StreamObse
}
- static CalculatorOuterClass.CalculatorResponse calculate(CalculatorOuterClass.CalculatorRequest request){
+ public static CalculatorOuterClass.CalculatorResponse calculate(CalculatorOuterClass.CalculatorRequest request){
CalculatorOuterClass.CalculatorResponse.Builder resultBuilder = CalculatorOuterClass.CalculatorResponse.newBuilder();
switch (request.getOperation()){
case ADD:
@@ -45,17 +43,5 @@ static CalculatorOuterClass.CalculatorResponse calculate(CalculatorOuterClass.Ca
}
- @GRpcService(interceptors = NotSpringBeanInterceptor.class)
- @Secured({})
- public static class SecuredCalculatorService extends SecuredCalculatorGrpc.SecuredCalculatorImplBase{
- @Override
- public void calculate(CalculatorOuterClass.CalculatorRequest request, StreamObserver responseObserver) {
- responseObserver.onNext(CalculatorService.calculate(request));
- responseObserver.onCompleted();
-
- }
-
-
- }
}
diff --git a/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GreeterService.java b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GreeterService.java
index c92f0214..60793eac 100644
--- a/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GreeterService.java
+++ b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GreeterService.java
@@ -8,8 +8,6 @@
import org.lognet.springboot.grpc.GRpcService;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.springframework.security.access.annotation.Secured;
-import org.springframework.security.access.prepost.PostAuthorize;
-import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
@@ -68,15 +66,6 @@ public void sayAuthHello(Empty request, StreamObserver5")
- public void sayPreAuthHello(GreeterOuterClass.Person person, StreamObserver responseObserver) {
-
- responseObserver.onNext(person.toBuilder().setNickName("dummy").build());
- responseObserver.onCompleted();
-
- }
@Override
@Secured({})
diff --git a/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GrpcTaskService.java b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GrpcTaskService.java
new file mode 100644
index 00000000..1ba7d8ad
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GrpcTaskService.java
@@ -0,0 +1,101 @@
+package org.lognet.springboot.grpc.demo;
+
+import io.grpc.examples.tasks.Assignment;
+import io.grpc.examples.tasks.Person;
+import io.grpc.examples.tasks.TaskServiceGrpc;
+import io.grpc.stub.StreamObserver;
+import org.lognet.springboot.grpc.GRpcService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+import java.util.Optional;
+
+@GRpcService
+public class GrpcTaskService extends TaskServiceGrpc.TaskServiceImplBase {
+
+
+ private ITaskService service;
+
+ @Autowired
+ public void setService(Optional service) {
+ this.service = service.orElse(new ITaskService() {
+ @Override
+ public Assignment findAssignment(Person person) {
+ return null;
+ }
+ });
+ }
+
+ @Override
+ @PreAuthorize("hasAuthority('1') && #person.age<12")
+ @PostAuthorize("returnObject.description.length()>0")
+ public void findAssignmentUnary(Person person, StreamObserver responseObserver) {
+ final Assignment assignment = service.findAssignment(person);
+ responseObserver.onNext(assignment);
+ responseObserver.onCompleted();
+
+ }
+
+ @Override
+ @PreAuthorize("#p0.age<12")
+ @PostAuthorize("returnObject.description.length()>0")
+ public StreamObserver findAssignmentsBidiStream(StreamObserver responseObserver) {
+ return new StreamObserver() {
+ @Override
+ public void onNext(Person person) {
+ final Assignment assignment = service.findAssignment(person);
+ responseObserver.onNext(assignment);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ @PreAuthorize("#person.age<12")
+ @PostAuthorize("returnObject.description.length()>0")
+ public void findAssignmentOutStream(Person person, StreamObserver responseObserver) {
+ responseObserver.onNext(service.findAssignment(person));
+ responseObserver.onNext(service.findAssignment(person));
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ @PreAuthorize("#p0.getAge()<12")
+ @PostAuthorize("returnObject.description.length()>0")
+ public StreamObserver findAssignmentInStream(StreamObserver responseObserver) {
+ return new StreamObserver() {
+ private final StringBuilder assignment = new StringBuilder();
+
+ @Override
+ public void onNext(Person person) {
+ if(0!=assignment.length()){
+ assignment.append(System.lineSeparator());
+ }
+ assignment.append(service.findAssignment(person).getDescription());
+ }
+
+ @Override
+ public void onError(Throwable t) {
+
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onNext(Assignment.newBuilder()
+ .setDescription(assignment.toString())
+ .build());
+ responseObserver.onCompleted();
+ }
+ };
+ }
+}
diff --git a/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/ITaskService.java b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/ITaskService.java
new file mode 100644
index 00000000..cd5b9bd1
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/ITaskService.java
@@ -0,0 +1,9 @@
+package org.lognet.springboot.grpc.demo;
+
+
+import io.grpc.examples.tasks.Assignment;
+import io.grpc.examples.tasks.Person;
+
+public interface ITaskService {
+ Assignment findAssignment(Person person);
+}
diff --git a/grpc-spring-boot-starter-demo/src/main/proto/greeter.proto b/grpc-spring-boot-starter-demo/src/main/proto/greeter.proto
index ae1faf3f..20fb937f 100644
--- a/grpc-spring-boot-starter-demo/src/main/proto/greeter.proto
+++ b/grpc-spring-boot-starter-demo/src/main/proto/greeter.proto
@@ -11,7 +11,6 @@ service Greeter {
rpc SayManyHellos (stream HelloRequest) returns (stream HelloReply) {}
rpc SayAuthHello ( google.protobuf.Empty) returns ( HelloReply) {}
rpc SayAuthOnlyHello ( google.protobuf.Empty) returns ( HelloReply) {}
- rpc SayPreAuthHello ( Person) returns ( Person) {}
rpc HelloPersonValidResponse ( Person) returns ( Person) {}
rpc HelloPersonInvalidResponse ( Person) returns ( Person) {}
diff --git a/grpc-spring-boot-starter-demo/src/main/proto/tasks.proto b/grpc-spring-boot-starter-demo/src/main/proto/tasks.proto
new file mode 100644
index 00000000..6bd26d9f
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/main/proto/tasks.proto
@@ -0,0 +1,44 @@
+// Copyright 2015 The gRPC 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.tasks";
+
+
+
+package task;
+
+service TaskService {
+
+ rpc FindAssignmentUnary(Person) returns ( Assignment) {}
+
+ rpc FindAssignmentsBidiStream(stream Person) returns (stream Assignment) {}
+
+ rpc FindAssignmentOutStream( Person) returns (stream Assignment) {}
+
+ rpc FindAssignmentInStream(stream Person) returns ( Assignment) {}
+
+
+}
+
+
+message Person {
+ string name = 1;
+ int32 age = 2;
+}
+
+message Assignment {
+ string description = 1;
+}
\ No newline at end of file
diff --git a/grpc-spring-boot-starter-demo/src/noConsulDependenciesTest/java/org/lognet/springboot/grpc/simple/NoConsulDependencyTest.java b/grpc-spring-boot-starter-demo/src/noConsulDependenciesTest/java/org/lognet/springboot/grpc/simple/NoConsulDependencyTest.java
new file mode 100644
index 00000000..938f96ba
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/noConsulDependenciesTest/java/org/lognet/springboot/grpc/simple/NoConsulDependencyTest.java
@@ -0,0 +1,35 @@
+package org.lognet.springboot.grpc.simple;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.lognet.springboot.grpc.GrpcServerTestBase;
+import org.lognet.springboot.grpc.autoconfigure.GRpcServerProperties;
+import org.lognet.springboot.grpc.demo.DemoApp;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.util.ReflectionUtils;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = {DemoApp.class}, webEnvironment = NONE)
+public class NoConsulDependencyTest extends GrpcServerTestBase {
+
+ @Test
+ public void noConsulClassesTest() {
+
+ final NoClassDefFoundError error = assertThrows(NoClassDefFoundError.class, () -> {
+ try {
+ ReflectionUtils.findMethod(GRpcServerProperties.ConsulProperties.class, "getDiscovery");
+ }catch (IllegalStateException illegalStateException){
+ throw illegalStateException.getCause();
+ }
+ });
+ assertThat(error.getMessage(), Matchers.containsString("org/springframework/cloud/consul/discovery/ConsulDiscoveryProperties"));
+ }
+
+
+}
diff --git a/grpc-spring-boot-starter-demo/src/noConsulDependenciesTest/resources/application-test.yml b/grpc-spring-boot-starter-demo/src/noConsulDependenciesTest/resources/application-test.yml
new file mode 100644
index 00000000..a8452a6e
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/noConsulDependenciesTest/resources/application-test.yml
@@ -0,0 +1,6 @@
+grpc:
+ consul:
+ registration-mode: noop
+ discovery:
+ tags:
+ - dummy
\ No newline at end of file
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/CustomInterceptorsOrderTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/CustomInterceptorsOrderTest.java
index d10f1c71..4dcfc6ed 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/CustomInterceptorsOrderTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/CustomInterceptorsOrderTest.java
@@ -18,7 +18,6 @@
import org.lognet.springboot.grpc.demo.DemoApp;
import org.lognet.springboot.grpc.security.AuthClientInterceptor;
import org.lognet.springboot.grpc.security.AuthHeader;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.lognet.springboot.grpc.security.SecurityInterceptor;
@@ -35,8 +34,6 @@
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.test.context.junit4.SpringRunner;
@@ -57,7 +54,9 @@
"grpc.security.auth.interceptor-order=3", //third
"grpc.metrics.interceptor-order=2", //second
"grpc.validation.interceptor-order=1" //first
- })
+ }
+ ,webEnvironment = SpringBootTest.WebEnvironment.NONE
+ )
@RunWith(SpringRunner.class)
@Import({CustomInterceptorsOrderTest.TestCfg.class})
public class CustomInterceptorsOrderTest extends GrpcServerTestBase {
@@ -114,20 +113,13 @@ protected void afterGreeting() {
@TestConfiguration
static class TestCfg extends GrpcSecurityConfigurerAdapter {
-
-
static final String pwd = "strongPassword1";
@Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-
- @Bean
- public UserDetails user(PasswordEncoder passwordEncoder) {
- return User.
- withUsername("user1")
- .password(passwordEncoder.encode(pwd))
+ public UserDetails user() {
+ return User.withDefaultPasswordEncoder()
+ .username("user1")
+ .password(pwd)
.roles("reader")
.build();
}
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/GrpcServerTestBase.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/GrpcServerTestBase.java
index 5bfeeefb..42e108db 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/GrpcServerTestBase.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/GrpcServerTestBase.java
@@ -16,18 +16,31 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
+import org.springframework.test.context.ContextConfiguration;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Optional;
-import java.util.concurrent.ExecutionException;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+@ContextConfiguration(
+ initializers = GrpcServerTestBase.TessAppContextInitializer.class)
public abstract class GrpcServerTestBase {
+ static class TessAppContextInitializer implements
+ ApplicationContextInitializer {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ applicationContext.setAllowCircularReferences(false);
+ }
+ }
+
@Autowired(required = false)
@Qualifier("grpcServerRunner")
protected GRpcServerRunner grpcServerRunner;
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/AllAuthConfigTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/AllAuthConfigTest.java
index fb61f5b9..2c289ce0 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/AllAuthConfigTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/AllAuthConfigTest.java
@@ -6,7 +6,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.lognet.springboot.grpc.demo.DemoApp;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.lognet.springboot.grpc.security.jwt.JwtAuthProviderFactory;
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/ConcurrentAuthConfigTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/ConcurrentAuthConfigTest.java
index 083bed22..0943ec44 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/ConcurrentAuthConfigTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/ConcurrentAuthConfigTest.java
@@ -13,7 +13,6 @@
import org.lognet.springboot.grpc.demo.DemoApp;
import org.lognet.springboot.grpc.security.AuthCallCredentials;
import org.lognet.springboot.grpc.security.AuthHeader;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.springframework.boot.test.context.SpringBootTest;
@@ -73,7 +72,6 @@ public void configure(GrpcSecurity builder) throws Exception {
@Test
public void concurrentTest() throws InterruptedException {
- System.out.println();
final SecuredGreeterGrpc.SecuredGreeterBlockingStub unsecuredFutureStub = SecuredGreeterGrpc
.newBlockingStub(selectedChanel);
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/DisabledSecuredAnnTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/DisabledSecuredAnnTest.java
index e596bc7c..fa31e87c 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/DisabledSecuredAnnTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/DisabledSecuredAnnTest.java
@@ -7,7 +7,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.lognet.springboot.grpc.demo.DemoApp;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.springframework.boot.test.context.SpringBootTest;
@@ -16,7 +15,6 @@
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotNull;
@SpringBootTest(classes = DemoApp.class)
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/JwtAuthorityTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/JwtAuthorityTest.java
index 12ae7c92..eb0eaa7d 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/JwtAuthorityTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/JwtAuthorityTest.java
@@ -4,7 +4,6 @@
import io.grpc.examples.GreeterGrpc;
import org.junit.runner.RunWith;
import org.lognet.springboot.grpc.demo.DemoApp;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.lognet.springboot.grpc.security.jwt.JwtAuthProviderFactory;
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/PerCallDefaultAuthConfigTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/PerCallDefaultAuthConfigTest.java
index 9785b4e5..5df82942 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/PerCallDefaultAuthConfigTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/PerCallDefaultAuthConfigTest.java
@@ -10,11 +10,7 @@
import org.lognet.springboot.grpc.demo.DemoApp;
import org.lognet.springboot.grpc.security.AuthCallCredentials;
import org.lognet.springboot.grpc.security.AuthHeader;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
-import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.context.TestConfiguration;
-import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/PrePostSecurityAuthTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/PrePostSecurityAuthTest.java
new file mode 100644
index 00000000..52d76923
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/PrePostSecurityAuthTest.java
@@ -0,0 +1,434 @@
+package org.lognet.springboot.grpc.auth;
+
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.CalculatorOuterClass;
+import io.grpc.examples.SecuredCalculatorGrpc;
+import io.grpc.examples.tasks.Assignment;
+import io.grpc.examples.tasks.Person;
+import io.grpc.examples.tasks.TaskServiceGrpc;
+import io.grpc.stub.StreamObserver;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.lognet.springboot.grpc.GRpcService;
+import org.lognet.springboot.grpc.GrpcServerTestBase;
+import org.lognet.springboot.grpc.demo.DemoApp;
+import org.lognet.springboot.grpc.demo.DemoAppConfiguration;
+import org.lognet.springboot.grpc.demo.ITaskService;
+import org.lognet.springboot.grpc.demo.NotSpringBeanInterceptor;
+import org.lognet.springboot.grpc.security.AuthCallCredentials;
+import org.lognet.springboot.grpc.security.AuthHeader;
+import org.lognet.springboot.grpc.security.GrpcSecurity;
+import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
+import org.mockito.Mockito;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@SpringBootTest(classes = DemoApp.class)
+@RunWith(SpringRunner.class)
+@Import({PrePostSecurityAuthTest.TestCfg.class})
+public class PrePostSecurityAuthTest extends GrpcServerTestBase {
+
+ static class AggregatingStreamObserver implements StreamObserver {
+ private final CompletableFuture> completion = new CompletableFuture<>();
+ private final List allAssignments = new ArrayList<>();
+
+ @Override
+ public void onNext(T value) {
+ allAssignments.add(value);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ completion.completeExceptionally(t);
+ }
+
+ @Override
+ public void onCompleted() {
+ completion.complete(allAssignments);
+ }
+
+ public List get(Duration duration) throws Throwable {
+ try {
+ return completion.get(duration.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (ExecutionException exception) {
+ throw exception.getCause();
+ }
+
+ }
+ }
+
+ @TestConfiguration
+ static class TestCfg extends GrpcSecurityConfigurerAdapter {
+ @GRpcService(interceptors = NotSpringBeanInterceptor.class)
+ @PreAuthorize("isAuthenticated()")
+ public static class SecuredCalculatorService extends SecuredCalculatorGrpc.SecuredCalculatorImplBase{
+ @Override
+ public void calculate(CalculatorOuterClass.CalculatorRequest request, StreamObserver responseObserver) {
+ responseObserver.onNext(DemoAppConfiguration.CalculatorService.calculate(request));
+ responseObserver.onCompleted();
+
+
+ }
+ }
+ @Override
+ public void configure(GrpcSecurity builder) throws Exception {
+ builder.authorizeRequests()
+ .withSecuredAnnotation()
+ .userDetailsService(new InMemoryUserDetailsManager(
+ User.withDefaultPasswordEncoder()
+ .username("user1")
+ .password("user1")
+ .authorities("1")
+ .build(),
+ User.withDefaultPasswordEncoder()
+ .username("user2")
+ .password("user2")
+ .authorities("2")
+ .build()
+ ));
+ }
+ }
+
+
+ private final Person sam = Person.newBuilder()
+ .setName("Sam")
+ .setAge(13)
+ .build();
+
+ private final Person frodo = Person.newBuilder()
+ .setName("Frodo")
+ .setAge(11)
+ .build();
+
+ private final Assignment noopAssigment = Assignment.newBuilder()
+ .setDescription("")
+ .build();
+
+ private final Assignment saveTheWorld = Assignment.newBuilder()
+ .setDescription("Save the world")
+ .build();
+ private final Assignment keepTheRing = Assignment.newBuilder()
+ .setDescription("Keep the ring")
+ .build();
+
+ @MockBean
+ private ITaskService service;
+
+ @Test
+ public void preAuthAnnotationOnClassTest() {
+
+
+ final SecuredCalculatorGrpc.SecuredCalculatorBlockingStub stub = SecuredCalculatorGrpc
+ .newBlockingStub(selectedChanel);
+
+ final CalculatorOuterClass.CalculatorResponse response = stub
+ .withCallCredentials(user2Credentials())
+ .calculate(CalculatorOuterClass.CalculatorRequest.newBuilder()
+ .setNumber1(1)
+ .setNumber2(1)
+ .setOperation(CalculatorOuterClass.CalculatorRequest.OperationType.ADD)
+ .build());
+ assertThat(response.getResult(),Matchers.is(2d));
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ stub.withCallCredentials(unAuthUserCredentials())
+ .calculate(CalculatorOuterClass.CalculatorRequest.newBuilder()
+ .setNumber1(1)
+ .setNumber2(1)
+ .setOperation(CalculatorOuterClass.CalculatorRequest.OperationType.ADD)
+ .build());
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.UNAUTHENTICATED));
+ }
+
+ @Test
+ public void unaryPreAuthorizeCallTest() {
+ final TaskServiceGrpc.TaskServiceBlockingStub stub = TaskServiceGrpc.newBlockingStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld);
+
+ final Assignment assignment = stub.findAssignmentUnary(frodo);
+
+ assertThat(assignment, Matchers.is(saveTheWorld));
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ stub.findAssignmentUnary(sam);
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+
+ Mockito.verify(service, Mockito.only()).findAssignment(frodo);
+ }
+ @Test
+ public void unaryPreAuthorizeAccessDeniedCallTest() {
+ final TaskServiceGrpc.TaskServiceBlockingStub unAuthStub = TaskServiceGrpc.newBlockingStub(getChannel())
+ .withCallCredentials(user2Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld);
+
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ unAuthStub.findAssignmentUnary(frodo);
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+
+ Mockito.verify(service, Mockito.never()).findAssignment(frodo);
+ }
+ @Test
+ public void unaryPreAuthorizeUnAuthCallTest() {
+ final TaskServiceGrpc.TaskServiceBlockingStub unAuthStub = TaskServiceGrpc.newBlockingStub(getChannel())
+ .withCallCredentials(unAuthUserCredentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld);
+
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ unAuthStub.findAssignmentUnary(frodo);
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.UNAUTHENTICATED));
+
+ Mockito.verify(service, Mockito.never()).findAssignment(frodo);
+ }
+
+ @Test
+ public void unaryPostAuthorizeCallTest() {
+ final TaskServiceGrpc.TaskServiceBlockingStub stub = TaskServiceGrpc.newBlockingStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(keepTheRing)
+ .thenReturn(noopAssigment);
+
+ final Assignment assignment = stub.findAssignmentUnary(frodo);
+ assertThat(assignment, Matchers.is(keepTheRing));
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ stub.findAssignmentUnary(frodo);
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+
+ Mockito.verify(service, Mockito.times(2)).findAssignment(frodo);
+
+ }
+
+ @Test
+ public void bidiStreamPrePostAuthorizeOkCallTest() throws Throwable {
+ final TaskServiceGrpc.TaskServiceStub stub = TaskServiceGrpc.newStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld)
+ .thenReturn(keepTheRing);
+
+
+ final AggregatingStreamObserver responseObserver = new AggregatingStreamObserver<>();
+ final StreamObserver personsIn = stub.findAssignmentsBidiStream(responseObserver);
+ personsIn.onNext(frodo);
+ personsIn.onNext(frodo);
+ personsIn.onCompleted();
+
+ final List response = responseObserver.get(Duration.ofSeconds(10));
+ assertThat(response, Matchers.contains(saveTheWorld, keepTheRing));
+ Mockito.verify(service, Mockito.times(2)).findAssignment(frodo);
+
+
+ }
+
+ @Test
+ public void bidiStreamPreAuthorizeFailCallTest() {
+ final TaskServiceGrpc.TaskServiceStub stub = TaskServiceGrpc.newStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(sam))
+ .thenReturn(keepTheRing);
+
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+
+ final AggregatingStreamObserver observer = new AggregatingStreamObserver<>();
+ final StreamObserver personsIn = stub.findAssignmentsBidiStream(observer);
+ personsIn.onNext(sam);
+ personsIn.onCompleted();
+ observer.get(Duration.ofSeconds(10));
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+ Mockito.verifyZeroInteractions(service);
+
+ }
+
+ @Test
+ public void outStreamPrePostAuthorizeOkCallTest() {
+ final TaskServiceGrpc.TaskServiceBlockingStub stub = TaskServiceGrpc.newBlockingStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld)
+ .thenReturn(keepTheRing);
+
+ final Iterator assignments = stub.findAssignmentOutStream(frodo);
+ List assignmentsList = new ArrayList<>();
+ assignments.forEachRemaining(assignmentsList::add);
+
+ assertThat(assignmentsList, Matchers.contains(saveTheWorld, keepTheRing));
+ Mockito.verify(service, Mockito.times(2)).findAssignment(frodo);
+
+ }
+
+ @Test
+ public void outStreamPostAuthorizeCallFailTest() {
+ final TaskServiceGrpc.TaskServiceBlockingStub stub = TaskServiceGrpc.newBlockingStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld)
+ .thenReturn(noopAssigment);
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ stub.findAssignmentOutStream(frodo)
+ .forEachRemaining(a -> {
+ });
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+
+ Mockito.verify(service, Mockito.times(2)).findAssignment(frodo);
+ }
+
+ @Test
+ public void outStreamPreAuthorizeCallFailTest() {
+ final TaskServiceGrpc.TaskServiceBlockingStub stub = TaskServiceGrpc.newBlockingStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(sam))
+ .thenReturn(saveTheWorld)
+ .thenReturn(keepTheRing);
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ stub.findAssignmentOutStream(sam)
+ .forEachRemaining(a -> {
+ });
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+
+ Mockito.verify(service, Mockito.never()).findAssignment(sam);
+ }
+
+
+ @Test
+ public void inStreamPrePostAuthorizeOkCallTest() throws Throwable {
+ final TaskServiceGrpc.TaskServiceStub stub = TaskServiceGrpc.newStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld)
+ .thenReturn(keepTheRing);
+
+ final AggregatingStreamObserver responseObserver = new AggregatingStreamObserver<>();
+ final StreamObserver personsIn = stub.findAssignmentInStream(responseObserver);
+ personsIn.onNext(frodo);
+ personsIn.onNext(frodo);
+ personsIn.onCompleted();
+
+ final List response = responseObserver.get(Duration.ofSeconds(10));
+
+ assertThat(response, Matchers.hasSize(1));
+ assertThat(response.get(0).getDescription(), Matchers.is(
+ Stream.of(saveTheWorld, keepTheRing)
+ .map(Assignment::getDescription)
+ .collect(Collectors.joining(System.lineSeparator()))
+ ));
+ Mockito.verify(service, Mockito.times(2)).findAssignment(frodo);
+
+
+ }
+ @Test
+ public void inStreamPreAuthorizeFailCallTest() {
+ final TaskServiceGrpc.TaskServiceStub stub = TaskServiceGrpc.newStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(saveTheWorld);
+
+ Mockito.when(service.findAssignment(sam))
+ .thenReturn(keepTheRing);
+
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ final AggregatingStreamObserver responseObserver = new AggregatingStreamObserver<>();
+ final StreamObserver personsIn = stub.findAssignmentInStream(responseObserver);
+ personsIn.onNext(frodo);
+ personsIn.onNext(sam);
+ personsIn.onCompleted();
+ responseObserver.get(Duration.ofSeconds(10));
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+
+ Mockito.verify(service, Mockito.only()).findAssignment(frodo);
+
+ }
+
+ @Test
+ public void inStreamPostAuthorizeFailCallTest() {
+ final TaskServiceGrpc.TaskServiceStub stub = TaskServiceGrpc.newStub(getChannel())
+ .withCallCredentials(user1Credentials());
+
+ Mockito.when(service.findAssignment(frodo))
+ .thenReturn(noopAssigment);
+
+
+ final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
+ final AggregatingStreamObserver responseObserver = new AggregatingStreamObserver<>();
+ final StreamObserver personsIn = stub.findAssignmentInStream(responseObserver);
+ personsIn.onNext(frodo);
+ personsIn.onCompleted();
+ responseObserver.get(Duration.ofSeconds(10));
+ });
+ assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
+
+ Mockito.verify(service, Mockito.only()).findAssignment(frodo);
+
+ }
+
+ private AuthCallCredentials user1Credentials() {
+ return new AuthCallCredentials(
+ AuthHeader.builder()
+ .basic("user1", "user1".getBytes(StandardCharsets.UTF_8))
+ );
+ }
+ private AuthCallCredentials user2Credentials() {
+ return new AuthCallCredentials(
+ AuthHeader.builder()
+ .basic("user2", "user2".getBytes(StandardCharsets.UTF_8))
+ );
+ }
+ private AuthCallCredentials unAuthUserCredentials() {
+ return new AuthCallCredentials(
+ AuthHeader.builder()
+ .basic("dummy", "dummy".getBytes(StandardCharsets.UTF_8))
+ );
+ }
+}
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/SecurityInterceptorTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/SecurityInterceptorTest.java
index a155fa26..e6d8fda7 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/SecurityInterceptorTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/SecurityInterceptorTest.java
@@ -8,11 +8,9 @@
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
-import io.grpc.examples.GreeterGrpc;
-import io.grpc.examples.GreeterOuterClass;
+import io.grpc.examples.SecuredGreeterGrpc;
import org.hamcrest.Matchers;
import org.junit.Assert;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.lognet.springboot.grpc.GRpcErrorHandler;
@@ -34,7 +32,7 @@
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import io.grpc.examples.SecuredGreeterGrpc;
+
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -109,34 +107,7 @@ public void unsupportedAuthSchemeShouldThrowUnauthenticatedException() {
verify(errorHandler).handle(any(),eq(Status.UNAUTHENTICATED), any(),any(),any());
}
- @Test
- @Ignore("@PreAuthorize is not supported yet")
- public void preAuthorizeTest() {
- final GreeterGrpc.GreeterBlockingStub greeterBlockingStub = GreeterGrpc.newBlockingStub(getChannel())
- .withCallCredentials(userCredentials());
-
-
- final GreeterOuterClass.Person personIn = GreeterOuterClass.Person.newBuilder()
- .setName("Frodo")
- .setAddress(GreeterOuterClass.Address.newBuilder().setCity("Shire"))
- .setAge(11)
- .build();
- final GreeterOuterClass.Person personOut = greeterBlockingStub.sayPreAuthHello(personIn);
- assertThat(personOut.toBuilder().clearNickName().build(),
- Matchers.is(personIn)
- );
-
- final StatusRuntimeException statusRuntimeException = Assert.assertThrows(StatusRuntimeException.class, () -> {
- greeterBlockingStub.sayPreAuthHello(GreeterOuterClass.Person.newBuilder()
- .setName("Aragorn")
- .setAddress(GreeterOuterClass.Address.newBuilder().setCity("Isildur"))
- .setAge(2)
- .build());
- });
- assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
-
- }
private AuthCallCredentials userCredentials(){
return new AuthCallCredentials(
AuthHeader.builder()
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/UserDetailsAuthTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/UserDetailsAuthTest.java
index e4f550aa..b5c96142 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/UserDetailsAuthTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/UserDetailsAuthTest.java
@@ -9,19 +9,18 @@
import io.grpc.examples.CalculatorGrpc;
import io.grpc.examples.CalculatorOuterClass;
import io.grpc.examples.GreeterGrpc;
-import io.grpc.examples.GreeterOuterClass;
import io.grpc.examples.SecuredCalculatorGrpc;
-import io.grpc.examples.SecuredGreeterGrpc;
+import io.grpc.stub.StreamObserver;
import org.hamcrest.Matchers;
-import org.junit.Ignore;
import org.junit.Test;
-import org.junit.jupiter.api.Disabled;
import org.junit.runner.RunWith;
+import org.lognet.springboot.grpc.GRpcService;
import org.lognet.springboot.grpc.GrpcServerTestBase;
import org.lognet.springboot.grpc.demo.DemoApp;
+import org.lognet.springboot.grpc.demo.DemoAppConfiguration;
+import org.lognet.springboot.grpc.demo.NotSpringBeanInterceptor;
import org.lognet.springboot.grpc.security.AuthClientInterceptor;
import org.lognet.springboot.grpc.security.AuthHeader;
-import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
@@ -29,25 +28,21 @@
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.ExecutionException;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.emptyOrNullString;
-import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-@SpringBootTest(classes = DemoApp.class)
+@SpringBootTest(classes = DemoApp.class,webEnvironment = SpringBootTest.WebEnvironment.NONE)
@RunWith(SpringRunner.class)
@Import({UserDetailsAuthTest.TestCfg.class})
public class UserDetailsAuthTest extends GrpcServerTestBase {
@@ -55,17 +50,25 @@ public class UserDetailsAuthTest extends GrpcServerTestBase {
@TestConfiguration
static class TestCfg extends GrpcSecurityConfigurerAdapter {
- static final String pwd="strongPassword1";
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
+
+ @GRpcService(interceptors = NotSpringBeanInterceptor.class)
+ @Secured({})
+ public static class SecuredCalculatorService extends SecuredCalculatorGrpc.SecuredCalculatorImplBase{
+ @Override
+ public void calculate(CalculatorOuterClass.CalculatorRequest request, StreamObserver responseObserver) {
+ responseObserver.onNext(DemoAppConfiguration.CalculatorService.calculate(request));
+ responseObserver.onCompleted();
+
+
}
+ }
+ static final String pwd="strongPassword1";
@Bean
- public UserDetails user(PasswordEncoder passwordEncoder) {
- return User.
- withUsername("user1")
- .password(passwordEncoder.encode(pwd))
+ public UserDetails user() {
+ return User.withDefaultPasswordEncoder()
+ .username("user1")
+ .password(pwd)
.roles("reader")
.build();
}
@@ -110,13 +113,14 @@ public void simpleAuthHeaderFormat() throws ExecutionException, InterruptedExcep
public void shouldFailWithPermissionDenied() {
final StatusRuntimeException statusRuntimeException = assertThrows(StatusRuntimeException.class, () -> {
- CalculatorGrpc
+ final CalculatorOuterClass.CalculatorResponse response = CalculatorGrpc
.newBlockingStub(selectedChanel) //auth channel
.calculate(CalculatorOuterClass.CalculatorRequest.newBuilder()
- .setNumber1(1)
- .setNumber2(1)
- .setOperation(CalculatorOuterClass.CalculatorRequest.OperationType.ADD)
- .build());
+ .setNumber1(1)
+ .setNumber2(1)
+ .setOperation(CalculatorOuterClass.CalculatorRequest.OperationType.ADD)
+ .build());
+ System.out.println(response);
});
assertThat(statusRuntimeException.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
}
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulDefaultRegistrationTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulDefaultRegistrationTest.java
index 144855eb..31e0dc15 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulDefaultRegistrationTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulDefaultRegistrationTest.java
@@ -3,23 +3,59 @@
import com.ecwid.consul.v1.health.model.Check;
import com.ecwid.consul.v1.health.model.HealthService;
import org.hamcrest.Matchers;
+import org.junit.Test;
import org.junit.runner.RunWith;
+import org.lognet.springboot.grpc.autoconfigure.GRpcServerProperties;
import org.lognet.springboot.grpc.demo.DemoApp;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties;
+import org.springframework.cloud.consul.discovery.ConsulServiceInstance;
+import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
+import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
@SpringBootTest(classes = DemoApp.class)
@RunWith(SpringRunner.class)
+@ActiveProfiles("consul-grpc-config-test")
public class ConsulDefaultRegistrationTest extends ConsulRegistrationBaseTest{
+ @Test
+ public void consulPropertiesTest() {
+ final ConsulDiscoveryProperties cloudConsulProps = applicationContext.getBean(ConsulDiscoveryProperties.class);
+ assertThat(cloudConsulProps.getTags(),Matchers.contains("a","b"));
+ assertThat(cloudConsulProps.getInstanceZone(),Matchers.is("zone1"));
+ assertThat(cloudConsulProps.getInstanceGroup(),Matchers.nullValue(String.class));
+ final ConsulDiscoveryProperties grpcConsulProperties = applicationContext.getBean(GRpcServerProperties.class).getConsul().getDiscovery();
+ assertThat(grpcConsulProperties.getTags(), Matchers.hasSize(3));
+ assertThat(grpcConsulProperties.getTags(), Matchers.hasItem("1"));
+
+ assertThat(grpcConsulProperties.getInstanceZone(),Matchers.is("zone1"));
+ assertThat(grpcConsulProperties.getInstanceGroup(),Matchers.is("group1"));
+
+ final List instances = discoveryClient.getInstances("grpc-grpc-demo");
+
+ assertThat(instances,Matchers.hasSize(1));
+ assertThat(instances.get(0),Matchers.isA(ConsulServiceInstance.class));
+ ConsulServiceInstance consulServiceInstance = (ConsulServiceInstance) instances.get(0);
+ final Map metadata = consulServiceInstance.getMetadata();
+ assertThat(metadata.get("secure"),Matchers.is(Boolean.FALSE.toString()));
+ assertThat(metadata.get("key1"),Matchers.is("value1"));
+ assertThat(consulServiceInstance.getTags(),Matchers.containsInAnyOrder("1","grpc=true","customTagName=A"));
+
+
+
+
+ }
+
@Override
void doTest( List healthServices) {
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulRegistrationBaseTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulRegistrationBaseTest.java
index 9ef6de55..f4ea3c5e 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulRegistrationBaseTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/consul/ConsulRegistrationBaseTest.java
@@ -5,8 +5,6 @@
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.health.HealthServicesRequest;
import com.ecwid.consul.v1.health.model.HealthService;
-import com.pszymczyk.consul.ConsulProcess;
-import com.pszymczyk.consul.ConsulStarterBuilder;
import io.grpc.BindableService;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
@@ -17,22 +15,19 @@
import org.awaitility.Awaitility;
import org.hamcrest.Matchers;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.lognet.springboot.grpc.autoconfigure.GRpcServerProperties;
import org.lognet.springboot.grpc.autoconfigure.consul.ServiceRegistrationMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
-import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.consul.discovery.ConsulDiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
-import org.springframework.util.SocketUtils;
import java.time.Duration;
import java.util.List;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -40,45 +35,26 @@
@ActiveProfiles("consul-test")
+@DirtiesContext
public abstract class ConsulRegistrationBaseTest {
- private static ConsulProcess consul;
-
- @BeforeClass
- public static void startConsul() {
- int port = SocketUtils.findAvailableTcpPort();
-
-
- consul = ConsulStarterBuilder.consulStarter().withHttpPort(port).build().start();
- System.setProperty("spring.cloud.consul.port", String.valueOf(port));
-
- }
-
- @AfterClass
- public static void clear() {
- System.clearProperty("spring.cloud.consul.port");
- Optional.ofNullable(consul).ifPresent(ConsulProcess::close);
-
- }
-
@Autowired
- protected DiscoveryClient discoveryClient;
+ protected ConsulDiscoveryClient discoveryClient;
@Autowired
protected ConfigurableApplicationContext applicationContext;
protected final String serviceId = "grpc-grpc-demo";
+ @Autowired
protected ConsulClient consulClient;
+
private ManagedChannel channel;
@Before
public void setUp() throws Exception {
- consulClient = new ConsulClient("localhost", Integer.parseInt(System.getProperty("spring.cloud.consul.port")));
-
-
List instances = discoveryClient.getInstances(serviceId);
final ServiceRegistrationMode registrationMode = applicationContext.getBean(GRpcServerProperties.class)
@@ -108,7 +84,6 @@ public void tearDown() throws Exception {
channel.shutdownNow();
channel.awaitTermination(1, TimeUnit.SECONDS);
}
- applicationContext.stop();
}
@Test
@@ -130,7 +105,7 @@ public void contextLoads() {
final List healthServices = Awaitility.await()
- .atMost(Duration.ofSeconds(20))
+ .atMost(Duration.ofMinutes(1))
.pollInterval(Duration.ofSeconds(3))
.until(() -> consulClient.getHealthServices(serviceId, HealthServicesRequest.newBuilder()
.setPassing(true)
diff --git a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/recovery/GRpcRecoveryTest.java b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/recovery/GRpcRecoveryTest.java
index 2d8787c7..19586a58 100644
--- a/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/recovery/GRpcRecoveryTest.java
+++ b/grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/recovery/GRpcRecoveryTest.java
@@ -21,6 +21,8 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -150,7 +152,7 @@ public Status handleB(ExceptionB e, GRpcExceptionScope scope) {
@Test
- public void streamingServiceErrorHandlerTest() throws ExecutionException, InterruptedException {
+ public void streamingServiceErrorHandlerTest() throws ExecutionException, InterruptedException, TimeoutException {
@@ -181,7 +183,7 @@ public void onCompleted() {
- final Throwable actual = errorFuture.get();
+ final Throwable actual = errorFuture.get(20, TimeUnit.SECONDS);
assertThat(actual, notNullValue());
assertThat(actual, isA(StatusRuntimeException.class));
assertThat(((StatusRuntimeException)actual).getStatus(), is(Status.RESOURCE_EXHAUSTED));
diff --git a/grpc-spring-boot-starter-demo/src/test/resources/application-consul-grpc-config-test.yml b/grpc-spring-boot-starter-demo/src/test/resources/application-consul-grpc-config-test.yml
new file mode 100644
index 00000000..67d9deac
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/test/resources/application-consul-grpc-config-test.yml
@@ -0,0 +1,20 @@
+grpc:
+ consul:
+ discovery:
+ tags:
+ - 1
+ - grpc=true
+ - customTagName=A
+ instance-group: group1
+
+spring:
+ cloud:
+ consul:
+ discovery:
+ tags:
+ - a
+ - b
+ instance-zone: zone1
+ metadata:
+ key1: value1
+
diff --git a/grpc-spring-boot-starter-demo/src/test/resources/application-consul-test.yml b/grpc-spring-boot-starter-demo/src/test/resources/application-consul-test.yml
index 8a3a5ebb..9a32458c 100644
--- a/grpc-spring-boot-starter-demo/src/test/resources/application-consul-test.yml
+++ b/grpc-spring-boot-starter-demo/src/test/resources/application-consul-test.yml
@@ -6,6 +6,8 @@ spring:
discovery:
enabled: true
enabled: true
+ port: ${embedded.consul.port}
+ host: ${embedded.consul.host}
service-registry:
auto-registration:
enabled: true
\ No newline at end of file
diff --git a/grpc-spring-boot-starter-demo/src/test/resources/bootstrap-consul-test.yml b/grpc-spring-boot-starter-demo/src/test/resources/bootstrap-consul-test.yml
new file mode 100644
index 00000000..a8b6e2cd
--- /dev/null
+++ b/grpc-spring-boot-starter-demo/src/test/resources/bootstrap-consul-test.yml
@@ -0,0 +1,7 @@
+embedded:
+ consul:
+ enabled: true
+ containers:
+ enabled: true
+
+
diff --git a/grpc-spring-boot-starter-demo/src/test/resources/bootstrap.yml b/grpc-spring-boot-starter-demo/src/test/resources/bootstrap.yml
index ccb576c6..cc7460c4 100644
--- a/grpc-spring-boot-starter-demo/src/test/resources/bootstrap.yml
+++ b/grpc-spring-boot-starter-demo/src/test/resources/bootstrap.yml
@@ -12,6 +12,9 @@ spring:
config:
uri: http://localhost:${config.port:8888}
embedded:
+ consul:
+ enabled: false
+ reuse-container: true
keycloak:
enabled: false
wait-timeout-in-seconds: 120
diff --git a/grpc-spring-boot-starter-demo/src/test/resources/logback-test.xml b/grpc-spring-boot-starter-demo/src/test/resources/logback-test.xml
index 4278a644..081473d9 100644
--- a/grpc-spring-boot-starter-demo/src/test/resources/logback-test.xml
+++ b/grpc-spring-boot-starter-demo/src/test/resources/logback-test.xml
@@ -1,6 +1,13 @@
-
-
-
-
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/grpc-spring-boot-starter-gradle-plugin/README.adoc b/grpc-spring-boot-starter-gradle-plugin/README.adoc
index cfc1f005..25323151 100644
--- a/grpc-spring-boot-starter-gradle-plugin/README.adoc
+++ b/grpc-spring-boot-starter-gradle-plugin/README.adoc
@@ -23,7 +23,7 @@ Bootstraps the project with `com.google.protobuf` gradle plugin (including `grp
----
plugins {
id 'java'
- id "io.github.lognet.grpc-spring-boot" version '4.5.7'
+ id "io.github.lognet.grpc-spring-boot" version '4.5.9'
}
----
diff --git a/grpc-spring-boot-starter/build.gradle b/grpc-spring-boot-starter/build.gradle
index e805be7e..8de54cf0 100644
--- a/grpc-spring-boot-starter/build.gradle
+++ b/grpc-spring-boot-starter/build.gradle
@@ -55,7 +55,7 @@ task generateReleaseNotes(type: JavaExec, group: "documentation") {
)
doFirst {
download {
- src 'https://github.com/spring-io/github-changelog-generator/releases/download/v0.0.6/github-changelog-generator.jar'
+ src 'https://github.com/spring-io/github-changelog-generator/releases/download/v0.0.7/github-changelog-generator.jar'
dest generator
onlyIfModified true
}
@@ -253,11 +253,7 @@ publishing {
}
}
-tasks.compileJava {
- options.compilerArgs.addAll(["--release", "8"])
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
-}
+
signing {
required {
// signing is required if this is a release version and the artifacts are to be published
diff --git a/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/FailureHandlingSupport.java b/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/FailureHandlingSupport.java
index b9132294..88b3ca43 100644
--- a/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/FailureHandlingSupport.java
+++ b/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/FailureHandlingSupport.java
@@ -3,6 +3,7 @@
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.Status;
+import lombok.extern.slf4j.Slf4j;
import org.lognet.springboot.grpc.recovery.GRpcExceptionHandlerMethodResolver;
import org.lognet.springboot.grpc.recovery.GRpcExceptionScope;
import org.lognet.springboot.grpc.recovery.GRpcRuntimeExceptionWrapper;
@@ -10,56 +11,66 @@
import java.util.Optional;
import java.util.function.Consumer;
-
+@Slf4j
public class FailureHandlingSupport {
private final GRpcExceptionHandlerMethodResolver methodResolver;
+
+
public FailureHandlingSupport(GRpcExceptionHandlerMethodResolver methodResolver) {
this.methodResolver = methodResolver;
}
+
+
+
public void closeCall(RuntimeException e, ServerCall, ?> call, Metadata headers) throws RuntimeException {
closeCall(e,call,headers,null);
}
- public void closeCall(RuntimeException e, ServerCall, ?> call, Metadata headers, Consumer customizer) throws RuntimeException {
+ public void closeCall( RuntimeException e, ServerCall, ?> call, Metadata headers, Consumer customizer) throws RuntimeException {
- final Optional handlerMethod = methodResolver.resolveMethodByThrowable(call.getMethodDescriptor().getServiceName(), e);
- if (handlerMethod.isPresent()) {
- final GRpcExceptionScope.GRpcExceptionScopeBuilder exceptionScopeBuilder = GRpcExceptionScope.builder()
- .callHeaders(headers)
- .methodCallAttributes(call.getAttributes())
- .methodDescriptor(call.getMethodDescriptor())
- .hint(GRpcRuntimeExceptionWrapper.getHint(e));
- Optional.ofNullable(customizer)
- .ifPresent(c -> c.accept(exceptionScopeBuilder));
-
- final GRpcExceptionScope excScope = exceptionScopeBuilder.build();
-
- final HandlerMethod handler = handlerMethod.get();
Status statusToSend = Status.INTERNAL;
- try {
- statusToSend = handler.invoke(GRpcRuntimeExceptionWrapper.unwrap(e), excScope);
- } catch (Exception handlerException) {
-
- org.slf4j.LoggerFactory.getLogger(this.getClass())
- .error("Caught exception while executing handler method {}, returning {} status.",
- handler.getMethod(),
- statusToSend,
- handlerException);
-
+ Metadata metadataToSend = null;
+
+ final Optional handlerMethod = methodResolver.resolveMethodByThrowable(call.getMethodDescriptor().getServiceName(), e);
+ if (handlerMethod.isPresent()) {
+ final GRpcExceptionScope.GRpcExceptionScopeBuilder exceptionScopeBuilder = GRpcExceptionScope.builder()
+ .callHeaders(headers)
+ .methodCallAttributes(call.getAttributes())
+ .methodDescriptor(call.getMethodDescriptor())
+ .hint(GRpcRuntimeExceptionWrapper.getHint(e));
+ Optional.ofNullable(customizer)
+ .ifPresent(c -> c.accept(exceptionScopeBuilder));
+
+ final GRpcExceptionScope excScope = exceptionScopeBuilder.build();
+
+ final HandlerMethod handler = handlerMethod.get();
+
+ try {
+ statusToSend = handler.invoke(GRpcRuntimeExceptionWrapper.unwrap(e), excScope);
+ metadataToSend = excScope.getResponseHeaders();
+ } catch (Exception handlerException) {
+
+ org.slf4j.LoggerFactory.getLogger(this.getClass())
+ .error("Caught exception while executing handler method {}, returning {} status.",
+ handler.getMethod(),
+ statusToSend,
+ handlerException);
+
+ }
}
- call.close(statusToSend, excScope.getResponseHeaders());
+ log.warn("Closing call with {}",statusToSend,GRpcRuntimeExceptionWrapper.unwrap(e));
+ call.close(statusToSend, Optional.ofNullable(metadataToSend).orElseGet(Metadata::new));
- } else {
- call.close(Status.INTERNAL, new Metadata());
- }
}
+
+
}
diff --git a/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/GRpcServicesRegistry.java b/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/GRpcServicesRegistry.java
index f1782f7a..19759e6b 100644
--- a/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/GRpcServicesRegistry.java
+++ b/grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/GRpcServicesRegistry.java
@@ -1,80 +1,165 @@
package org.lognet.springboot.grpc;
import io.grpc.BindableService;
+import io.grpc.MethodDescriptor;
import io.grpc.ServerInterceptor;
+import io.grpc.ServerServiceDefinition;
+import lombok.Builder;
+import lombok.Getter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.MethodIntrospector;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.function.SingletonSupplier;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
-public class GRpcServicesRegistry implements InitializingBean , ApplicationContextAware {
+public class GRpcServicesRegistry implements InitializingBean, ApplicationContextAware {
+ @Getter
+ @Builder
+ public static class GrpcServiceMethod {
+ private BindableService service;
+ private Method method;
+
+ }
+
private ApplicationContext applicationContext;
+ private Supplier