Skip to content

Commit 4e98a9b

Browse files
author
Alexander Furer
committed
release 4.1.0, fixes #157
1 parent 26ebbdd commit 4e98a9b

File tree

13 files changed

+238
-89
lines changed

13 files changed

+238
-89
lines changed

README.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,17 @@ One is possible to plug in your own bespoke authentication provider by implement
387387

388388
<<Client side configuration support>> section explains how to pass custom authorization scheme and claim from GRPC client.
389389

390+
=== Obtaining Authentication details
391+
392+
To obtain `Authentication` object in the implementation of *secured method*, please use below snippet
393+
394+
[source,java]
395+
----
396+
final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get();
397+
----
398+
399+
400+
390401
=== Client side configuration support
391402

392403
By adding `io.github.lognet:grpc-client-spring-boot-starter` dependency to your *java grpc client* application you can easily configure per-channel or per-call credentials :

ReleaseNotes.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
== Version 4.1.0
2+
* gRPC version upgraded to 1.31.2
3+
* Fixed the issue with obtaining `Authentication` details in secured object implementation.
4+
* Fixed the issue with providing client-side user credentials.
5+
16
== Version 4.0.0
27
* Spring Security framework integration
38
* gRPC version upgraded to 1.32.1
49
* Spring Boot 2.3.3
510

11+
[IMPORTANT]
12+
Please use `4.1.0` version, `4.0.0` has issue with obtaining Authentication details in secured object implementation.
613

714
== Version 3.5.7
815
* gRPC version upgraded to 1.31.1

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
buildscript {
22
ext {
33
springBoot_2_X_Version = '2.3.3.RELEASE'
4-
grpcVersion = '1.32.1'
4+
grpcVersion = '1.32.2'
55
}
66
repositories {
77
mavenCentral()

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version=4.0.1-SNAPSHOT
1+
version=4.1.0
22
group=io.github.lognet
33
description=Spring Boot starter for Google RPC.
44
gitHubUrl=https\://github.com/LogNet/grpc-spring-boot-starter

grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/GreeterService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import io.grpc.stub.StreamObserver;
77
import lombok.extern.slf4j.Slf4j;
88
import org.lognet.springboot.grpc.GRpcService;
9+
import org.lognet.springboot.grpc.security.GrpcSecurity;
910
import org.springframework.security.access.annotation.Secured;
1011
import org.springframework.security.core.Authentication;
11-
import org.springframework.security.core.context.SecurityContextHolder;
1212
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
1313

1414
@Slf4j
@@ -28,7 +28,7 @@ public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<Gree
2828
public void sayAuthHello(Empty request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
2929

3030

31-
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
31+
final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get();
3232
String user = auth.getName();
3333
if(auth instanceof JwtAuthenticationToken){
3434
user = JwtAuthenticationToken.class.cast(auth).getTokenAttributes().get("preferred_username").toString();

grpc-spring-boot-starter-demo/src/main/java/org/lognet/springboot/grpc/demo/SecuredGreeterService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import io.grpc.stub.StreamObserver;
77
import lombok.extern.slf4j.Slf4j;
88
import org.lognet.springboot.grpc.GRpcService;
9+
import org.lognet.springboot.grpc.security.GrpcSecurity;
910
import org.springframework.security.access.annotation.Secured;
1011
import org.springframework.security.core.Authentication;
11-
import org.springframework.security.core.context.SecurityContextHolder;
1212
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
1313

1414
@Slf4j
@@ -18,7 +18,7 @@ public class SecuredGreeterService extends SecuredGreeterGrpc.SecuredGreeterImpl
1818

1919
@Override
2020
public void sayAuthHello(Empty request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
21-
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
21+
final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get();
2222
String user = auth.getName();
2323
if(auth instanceof JwtAuthenticationToken){
2424
user = JwtAuthenticationToken.class.cast(auth).getTokenAttributes().get("preferred_username").toString();

grpc-spring-boot-starter-demo/src/main/protoGen/io/grpc/examples/CalculatorGrpc.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/**
1111
*/
1212
@javax.annotation.Generated(
13-
value = "by gRPC proto compiler (version 1.32.1)",
13+
value = "by gRPC proto compiler (version 1.32.2)",
1414
comments = "Source: calculator.proto")
1515
public final class CalculatorGrpc {
1616

grpc-spring-boot-starter-demo/src/main/protoGen/io/grpc/examples/GreeterGrpc.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* </pre>
1414
*/
1515
@javax.annotation.Generated(
16-
value = "by gRPC proto compiler (version 1.32.1)",
16+
value = "by gRPC proto compiler (version 1.32.2)",
1717
comments = "Source: greeter.proto")
1818
public final class GreeterGrpc {
1919

grpc-spring-boot-starter-demo/src/main/protoGen/io/grpc/examples/SecuredGreeterGrpc.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/**
1111
*/
1212
@javax.annotation.Generated(
13-
value = "by gRPC proto compiler (version 1.32.1)",
13+
value = "by gRPC proto compiler (version 1.32.2)",
1414
comments = "Source: greeter.proto")
1515
public final class SecuredGreeterGrpc {
1616

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package org.lognet.springboot.grpc.auth;
2+
3+
import com.google.protobuf.Empty;
4+
import io.grpc.Status;
5+
import io.grpc.StatusRuntimeException;
6+
import io.grpc.examples.GreeterGrpc.GreeterFutureStub;
7+
import io.grpc.examples.SecuredGreeterGrpc;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.hamcrest.Matchers;
10+
import org.junit.Test;
11+
import org.junit.runner.RunWith;
12+
import org.lognet.springboot.grpc.GrpcServerTestBase;
13+
import org.lognet.springboot.grpc.demo.DemoApp;
14+
import org.lognet.springboot.grpc.security.AuthCallCredentials;
15+
import org.lognet.springboot.grpc.security.AuthHeader;
16+
import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
17+
import org.lognet.springboot.grpc.security.GrpcSecurity;
18+
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
19+
import org.springframework.boot.test.context.SpringBootTest;
20+
import org.springframework.boot.test.context.TestConfiguration;
21+
import org.springframework.context.annotation.Import;
22+
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
23+
import org.springframework.security.core.userdetails.User;
24+
import org.springframework.security.core.userdetails.UserDetailsService;
25+
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
26+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
27+
import org.springframework.test.context.junit4.SpringRunner;
28+
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.List;
32+
import java.util.concurrent.atomic.AtomicInteger;
33+
import java.util.function.Function;
34+
35+
import static org.hamcrest.MatcherAssert.assertThat;
36+
import static org.junit.Assert.assertThrows;
37+
import static org.junit.jupiter.api.Assertions.assertAll;
38+
import static org.junit.jupiter.api.Assertions.assertEquals;
39+
40+
@SpringBootTest(classes = DemoApp.class, properties = "spring.cloud.service-registry.auto-registration.enabled=false")
41+
@RunWith(SpringRunner.class)
42+
@Import({ConcurrentAuthConfigTest.TestCfg.class})
43+
@Slf4j
44+
public class ConcurrentAuthConfigTest extends GrpcServerTestBase {
45+
46+
private static User user1 = new User("test1", "test1", Collections.EMPTY_LIST);
47+
private static User user2 = new User("test2", "test2", Collections.EMPTY_LIST);
48+
49+
private AuthCallCredentials user1CallCredentials = new AuthCallCredentials(
50+
AuthHeader.builder().basic(user1.getUsername(), user1.getPassword().getBytes()));
51+
52+
private AuthCallCredentials user2CallCredentials = new AuthCallCredentials(
53+
AuthHeader.builder().basic(user2.getUsername(), user2.getPassword().getBytes()));
54+
55+
@TestConfiguration
56+
static class TestCfg {
57+
58+
@EnableGrpcSecurity
59+
public class DemoGrpcSecurityConfig extends GrpcSecurityConfigurerAdapter {
60+
61+
@Override
62+
public void configure(GrpcSecurity builder) throws Exception {
63+
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
64+
UserDetailsService users = new InMemoryUserDetailsManager(user1, user2);
65+
provider.setUserDetailsService(users);
66+
provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
67+
68+
builder
69+
.authenticationProvider(provider)
70+
.authorizeRequests()
71+
.anyMethod().authenticated();
72+
}
73+
74+
}
75+
}
76+
77+
@Test
78+
public void concurrentTest() throws InterruptedException {
79+
System.out.println();
80+
81+
final SecuredGreeterGrpc.SecuredGreeterBlockingStub unsecuredFutureStub = SecuredGreeterGrpc
82+
.newBlockingStub(selectedChanel);
83+
84+
final SecuredGreeterGrpc.SecuredGreeterBlockingStub securedFutureStub1 = unsecuredFutureStub
85+
.withCallCredentials(user1CallCredentials);
86+
87+
final SecuredGreeterGrpc.SecuredGreeterBlockingStub securedFutureStub2 = unsecuredFutureStub
88+
.withCallCredentials(user2CallCredentials);
89+
90+
91+
int parallelTests = 10;
92+
93+
List<Thread> threads = new ArrayList<>();
94+
// Number of threads that passed the test
95+
AtomicInteger successCounter = new AtomicInteger(0);
96+
AtomicInteger failureCounter = new AtomicInteger(0);
97+
98+
Function<Integer, Void> authenticated = i -> {
99+
SecuredGreeterGrpc.SecuredGreeterBlockingStub stub = null;
100+
User user = null;
101+
if (0 == i % 2) {
102+
stub = securedFutureStub1;
103+
user = user1;
104+
}else{
105+
stub = securedFutureStub2;
106+
user = user2;
107+
}
108+
final String reply = stub.sayAuthHello(Empty.getDefaultInstance()).getMessage();
109+
assertThat(reply, Matchers.containsString(user.getUsername()));
110+
return null;
111+
};
112+
Runnable unauthenticated = () -> {
113+
StatusRuntimeException err = assertThrows(StatusRuntimeException.class,
114+
() -> unsecuredFutureStub.sayAuthHello(Empty.getDefaultInstance()).getMessage());
115+
assertEquals(Status.Code.UNAUTHENTICATED, err.getStatus().getCode());
116+
};
117+
118+
// Check that the assertions work as is (single threaded)
119+
authenticated.apply(0);
120+
unauthenticated.run();
121+
122+
for (int i = 0; i < parallelTests; i++) {
123+
Thread success = new Thread(() -> {
124+
125+
for (int j = 0; j < 1000; j++) {
126+
authenticated.apply(j);
127+
}
128+
successCounter.incrementAndGet();
129+
log.info("All passed");
130+
});
131+
success.setUncaughtExceptionHandler((thread, ex) -> {
132+
log.error("SECURITY ???", ex);
133+
});
134+
threads.add(success);
135+
136+
Thread failure = new Thread(() -> {
137+
138+
for (int j = 0; j < 1000; j++) {
139+
unauthenticated.run();
140+
}
141+
failureCounter.incrementAndGet();
142+
log.info("All passed");
143+
});
144+
failure.setUncaughtExceptionHandler((thread, ex) -> {
145+
log.error("SECURITY BYPASSED", ex);
146+
});
147+
148+
threads.add(failure);
149+
}
150+
151+
Collections.shuffle(threads);
152+
for (Thread thread : threads) {
153+
thread.start();
154+
}
155+
for (Thread thread : threads) {
156+
thread.join();
157+
}
158+
159+
assertAll(() -> assertEquals(parallelTests, successCounter.get()),
160+
() -> assertEquals(parallelTests, failureCounter.get()));
161+
}
162+
163+
@Override
164+
protected GreeterFutureStub beforeGreeting(GreeterFutureStub stub) {
165+
return stub.withCallCredentials(user1CallCredentials);
166+
}
167+
168+
}

0 commit comments

Comments
 (0)