Skip to content

Commit

Permalink
release 4.1.0, fixes #157
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Furer committed Oct 15, 2020
1 parent 26ebbdd commit 4e98a9b
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 89 deletions.
11 changes: 11 additions & 0 deletions README.adoc
Expand Up @@ -387,6 +387,17 @@ One is possible to plug in your own bespoke authentication provider by implement

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

=== Obtaining Authentication details

To obtain `Authentication` object in the implementation of *secured method*, please use below snippet

[source,java]
----
final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get();
----



=== Client side configuration support

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 :
Expand Down
7 changes: 7 additions & 0 deletions ReleaseNotes.adoc
@@ -1,8 +1,15 @@
== Version 4.1.0
* gRPC version upgraded to 1.31.2
* Fixed the issue with obtaining `Authentication` details in secured object implementation.
* Fixed the issue with providing client-side user credentials.
== Version 4.0.0
* Spring Security framework integration
* gRPC version upgraded to 1.32.1
* Spring Boot 2.3.3

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

== Version 3.5.7
* gRPC version upgraded to 1.31.1
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
@@ -1,7 +1,7 @@
buildscript {
ext {
springBoot_2_X_Version = '2.3.3.RELEASE'
grpcVersion = '1.32.1'
grpcVersion = '1.32.2'
}
repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
@@ -1,4 +1,4 @@
version=4.0.1-SNAPSHOT
version=4.1.0
group=io.github.lognet
description=Spring Boot starter for Google RPC.
gitHubUrl=https\://github.com/LogNet/grpc-spring-boot-starter
Expand Down
Expand Up @@ -6,9 +6,9 @@
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import org.lognet.springboot.grpc.GRpcService;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

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


final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get();
String user = auth.getName();
if(auth instanceof JwtAuthenticationToken){
user = JwtAuthenticationToken.class.cast(auth).getTokenAttributes().get("preferred_username").toString();
Expand Down
Expand Up @@ -6,9 +6,9 @@
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import org.lognet.springboot.grpc.GRpcService;
import org.lognet.springboot.grpc.security.GrpcSecurity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

@Slf4j
Expand All @@ -18,7 +18,7 @@ public class SecuredGreeterService extends SecuredGreeterGrpc.SecuredGreeterImpl

@Override
public void sayAuthHello(Empty request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get();
String user = auth.getName();
if(auth instanceof JwtAuthenticationToken){
user = JwtAuthenticationToken.class.cast(auth).getTokenAttributes().get("preferred_username").toString();
Expand Down
Expand Up @@ -10,7 +10,7 @@
/**
*/
@javax.annotation.Generated(
value = "by gRPC proto compiler (version 1.32.1)",
value = "by gRPC proto compiler (version 1.32.2)",
comments = "Source: calculator.proto")
public final class CalculatorGrpc {

Expand Down
Expand Up @@ -13,7 +13,7 @@
* </pre>
*/
@javax.annotation.Generated(
value = "by gRPC proto compiler (version 1.32.1)",
value = "by gRPC proto compiler (version 1.32.2)",
comments = "Source: greeter.proto")
public final class GreeterGrpc {

Expand Down
Expand Up @@ -10,7 +10,7 @@
/**
*/
@javax.annotation.Generated(
value = "by gRPC proto compiler (version 1.32.1)",
value = "by gRPC proto compiler (version 1.32.2)",
comments = "Source: greeter.proto")
public final class SecuredGreeterGrpc {

Expand Down
@@ -0,0 +1,168 @@
package org.lognet.springboot.grpc.auth;

import com.google.protobuf.Empty;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.examples.GreeterGrpc.GreeterFutureStub;
import io.grpc.examples.SecuredGreeterGrpc;
import lombok.extern.slf4j.Slf4j;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.lognet.springboot.grpc.GrpcServerTestBase;
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;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(classes = DemoApp.class, properties = "spring.cloud.service-registry.auto-registration.enabled=false")
@RunWith(SpringRunner.class)
@Import({ConcurrentAuthConfigTest.TestCfg.class})
@Slf4j
public class ConcurrentAuthConfigTest extends GrpcServerTestBase {

private static User user1 = new User("test1", "test1", Collections.EMPTY_LIST);
private static User user2 = new User("test2", "test2", Collections.EMPTY_LIST);

private AuthCallCredentials user1CallCredentials = new AuthCallCredentials(
AuthHeader.builder().basic(user1.getUsername(), user1.getPassword().getBytes()));

private AuthCallCredentials user2CallCredentials = new AuthCallCredentials(
AuthHeader.builder().basic(user2.getUsername(), user2.getPassword().getBytes()));

@TestConfiguration
static class TestCfg {

@EnableGrpcSecurity
public class DemoGrpcSecurityConfig extends GrpcSecurityConfigurerAdapter {

@Override
public void configure(GrpcSecurity builder) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
UserDetailsService users = new InMemoryUserDetailsManager(user1, user2);
provider.setUserDetailsService(users);
provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());

builder
.authenticationProvider(provider)
.authorizeRequests()
.anyMethod().authenticated();
}

}
}

@Test
public void concurrentTest() throws InterruptedException {
System.out.println();

final SecuredGreeterGrpc.SecuredGreeterBlockingStub unsecuredFutureStub = SecuredGreeterGrpc
.newBlockingStub(selectedChanel);

final SecuredGreeterGrpc.SecuredGreeterBlockingStub securedFutureStub1 = unsecuredFutureStub
.withCallCredentials(user1CallCredentials);

final SecuredGreeterGrpc.SecuredGreeterBlockingStub securedFutureStub2 = unsecuredFutureStub
.withCallCredentials(user2CallCredentials);


int parallelTests = 10;

List<Thread> threads = new ArrayList<>();
// Number of threads that passed the test
AtomicInteger successCounter = new AtomicInteger(0);
AtomicInteger failureCounter = new AtomicInteger(0);

Function<Integer, Void> authenticated = i -> {
SecuredGreeterGrpc.SecuredGreeterBlockingStub stub = null;
User user = null;
if (0 == i % 2) {
stub = securedFutureStub1;
user = user1;
}else{
stub = securedFutureStub2;
user = user2;
}
final String reply = stub.sayAuthHello(Empty.getDefaultInstance()).getMessage();
assertThat(reply, Matchers.containsString(user.getUsername()));
return null;
};
Runnable unauthenticated = () -> {
StatusRuntimeException err = assertThrows(StatusRuntimeException.class,
() -> unsecuredFutureStub.sayAuthHello(Empty.getDefaultInstance()).getMessage());
assertEquals(Status.Code.UNAUTHENTICATED, err.getStatus().getCode());
};

// Check that the assertions work as is (single threaded)
authenticated.apply(0);
unauthenticated.run();

for (int i = 0; i < parallelTests; i++) {
Thread success = new Thread(() -> {

for (int j = 0; j < 1000; j++) {
authenticated.apply(j);
}
successCounter.incrementAndGet();
log.info("All passed");
});
success.setUncaughtExceptionHandler((thread, ex) -> {
log.error("SECURITY ???", ex);
});
threads.add(success);

Thread failure = new Thread(() -> {

for (int j = 0; j < 1000; j++) {
unauthenticated.run();
}
failureCounter.incrementAndGet();
log.info("All passed");
});
failure.setUncaughtExceptionHandler((thread, ex) -> {
log.error("SECURITY BYPASSED", ex);
});

threads.add(failure);
}

Collections.shuffle(threads);
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}

assertAll(() -> assertEquals(parallelTests, successCounter.get()),
() -> assertEquals(parallelTests, failureCounter.get()));
}

@Override
protected GreeterFutureStub beforeGreeting(GreeterFutureStub stub) {
return stub.withCallCredentials(user1CallCredentials);
}

}
Expand Up @@ -44,9 +44,6 @@
public class JwtRoleTest extends JwtAuthBaseTest {





@TestConfiguration
static class TestCfg {

Expand Down Expand Up @@ -82,18 +79,18 @@ public void concurrencyTest() throws InterruptedException, ExecutionException {
final CyclicBarrier barrier = new CyclicBarrier(concurrency);
final CountDownLatch endCountDownLatch = new CountDownLatch(concurrency);

AtomicInteger shouldSucceed = new AtomicInteger();
AtomicInteger shouldFail = new AtomicInteger();
AtomicInteger shouldSucceed = new AtomicInteger();
AtomicInteger shouldFail = new AtomicInteger();

final List<Future<Boolean>> result = Stream.iterate(0, i -> i + 1)
final List<Future<Boolean>> result = Stream.iterate(0, i -> i + 1)
.limit(concurrency)
.map(i ->
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
System.out.println("About to start call "+i);
System.out.println("About to start call " + i);
barrier.await();
System.out.println("Start call "+i);
System.out.println("Start call " + i);
try {
if (i % 2 == 0) {
shouldSucceed.incrementAndGet();
Expand All @@ -109,8 +106,8 @@ public Boolean call() throws Exception {
return true;
} catch (Exception e) {
return false;
}finally {
System.out.println("Call "+i+" finished");
} finally {
System.out.println("Call " + i + " finished");
endCountDownLatch.countDown();
}
}
Expand All @@ -120,21 +117,16 @@ public Boolean call() throws Exception {


endCountDownLatch.await();
int failed=0, succeeded=0;
for(Future<Boolean> res: result ){
if(res.get()){
int failed = 0, succeeded = 0;
for (Future<Boolean> res : result) {
if (res.get()) {
++succeeded;
}else {
} else {
++failed;
}
}
assertThat(succeeded,Matchers.is(shouldSucceed.get()));
assertThat(failed,Matchers.is(shouldFail.get()));





assertThat(succeeded, Matchers.is(shouldSucceed.get()));
assertThat(failed, Matchers.is(shouldFail.get()));


}
Expand Down
@@ -1,5 +1,6 @@
package org.lognet.springboot.grpc.security;

import io.grpc.Context;
import io.grpc.ServerInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
Expand All @@ -12,6 +13,7 @@
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;

import java.util.Arrays;
Expand All @@ -21,6 +23,7 @@ public class GrpcSecurity extends AbstractConfiguredSecurityBuilder<ServerInterc

private ApplicationContext applicationContext;

public static final Context.Key<Authentication> AUTHENTICATION_CONTEXT_KEY = Context.key("AUTHENTICATION");
public GrpcSecurity(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);

Expand Down

0 comments on commit 4e98a9b

Please sign in to comment.