Skip to content

Commit a415d04

Browse files
author
Alexander Furer
committed
closes #176 + tests
1 parent 48f36be commit a415d04

File tree

9 files changed

+241
-46
lines changed

9 files changed

+241
-46
lines changed

README.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,19 @@ One is possible to plug in your own bespoke authentication provider by implement
640640
<4> Register your own `AuthenticationProvider` that supports validation of `MyAuthenticationObject`
641641
<5> Validate provided `authentication` and return validated and *authenticated* `Authentication` object
642642

643+
644+
`AuthenticationSchemeSelector` can also be registered by defining Spring bean in your application context:
645+
646+
[source,java]
647+
----
648+
@Bean
649+
public AuthenticationSchemeSelector myCustomSchemeSelector(){
650+
return authHeader->{
651+
// your logic here
652+
};
653+
}
654+
----
655+
643656
<<Client side configuration support>> section explains how to pass custom authorization scheme and claim from GRPC client.
644657

645658
=== Obtaining Authentication details

grpc-spring-boot-starter-demo/build.gradle

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ apply plugin: "nebula.facet"
1515

1616
facets {
1717
pureNettyTest
18+
customSecurityTest
1819
}
1920

2021
dependencyManagement {
@@ -26,15 +27,24 @@ dependencyManagement {
2627
configurations.findAll{ cfg ->
2728
if (cfg.name.startsWith("pureNetty")) {
2829
cfg.exclude group: 'io.grpc', module: 'grpc-netty-shaded'
29-
dependencies.add(cfg.name,"io.grpc:grpc-netty")
30-
}
31-
if(cfg.name.equals("pureNettyTestCompileClasspath")){
32-
cfg.extendsFrom(configurations.getByName("testCompileClasspath"))
30+
3331
}
34-
if(cfg.name.equals("pureNettyTestRuntimeClasspath")){
35-
cfg.extendsFrom(configurations.getByName("testRuntimeClasspath"))
32+
33+
if (cfg.name.startsWith("customSecurity")) {
34+
cfg.exclude group: 'org.springframework.security', module: 'spring-security-oauth2-resource-server'
35+
cfg.exclude group: 'org.springframework.security', module: 'spring-security-oauth2-jose'
3636
}
3737

38+
39+
}
40+
41+
42+
configurations {
43+
pureNettyTestCompile.extendsFrom( testCompile)
44+
pureNettyTestRuntime.extendsFrom(testRuntime)
45+
46+
customSecurityTestCompile.extendsFrom( testCompile)
47+
customSecurityTestRuntime.extendsFrom(testRuntime)
3848
}
3949

4050

@@ -70,7 +80,10 @@ dependencies {
7080

7181
testImplementation 'org.mockito:mockito-core:2.23.0'
7282

83+
customSecurityTestCompile sourceSets.test.output
7384

85+
pureNettyTestCompile sourceSets.test.output
86+
pureNettyTestCompile "io.grpc:grpc-netty"
7487
//testCompile "org.testcontainers:junit-jupiter:1.14.3"
7588

7689

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.SecuredGreeterGrpc;
7+
import org.hamcrest.Matchers;
8+
import org.junit.Assert;
9+
import org.junit.Test;
10+
import org.junit.runner.RunWith;
11+
import org.lognet.springboot.grpc.GrpcServerTestBase;
12+
import org.lognet.springboot.grpc.demo.DemoApp;
13+
import org.lognet.springboot.grpc.security.AuthCallCredentials;
14+
import org.lognet.springboot.grpc.security.AuthHeader;
15+
import org.lognet.springboot.grpc.security.EnableGrpcSecurity;
16+
import org.lognet.springboot.grpc.security.GrpcSecurity;
17+
import org.lognet.springboot.grpc.security.GrpcSecurityConfigurerAdapter;
18+
import org.springframework.boot.test.context.SpringBootTest;
19+
import org.springframework.boot.test.context.TestConfiguration;
20+
import org.springframework.context.annotation.Import;
21+
import org.springframework.security.authentication.TestingAuthenticationProvider;
22+
import org.springframework.security.authentication.TestingAuthenticationToken;
23+
import org.springframework.test.context.junit4.SpringRunner;
24+
25+
import java.nio.ByteBuffer;
26+
import java.util.Optional;
27+
import java.util.concurrent.ExecutionException;
28+
29+
import static org.hamcrest.MatcherAssert.assertThat;
30+
import static org.junit.Assert.assertNotNull;
31+
32+
import static org.junit.Assert.assertThrows;
33+
import static org.junit.Assert.assertTrue;
34+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
35+
36+
@RunWith(SpringRunner.class)
37+
@SpringBootTest(classes = {DemoApp.class}, webEnvironment = NONE)
38+
@Import(CustomSecurityTest.TestConfig.class)
39+
public class CustomSecurityTest extends GrpcServerTestBase {
40+
private final static String MY_CUSTOM_SCHEME_NAME = "custom";
41+
42+
@TestConfiguration
43+
static class TestConfig {
44+
45+
@EnableGrpcSecurity
46+
public class DemoGrpcSecurityConfig extends GrpcSecurityConfigurerAdapter {
47+
48+
49+
@Override
50+
public void configure(GrpcSecurity builder) throws Exception {
51+
52+
53+
builder.authorizeRequests()
54+
.withSecuredAnnotation()
55+
.authenticationSchemeSelector(scheme ->
56+
Optional.of(scheme.toString())
57+
.filter(s -> s.startsWith(MY_CUSTOM_SCHEME_NAME))
58+
.map(s -> s.substring(MY_CUSTOM_SCHEME_NAME.length() + 1))
59+
.map(token -> {
60+
final String[] chunks = token.split("#");
61+
return new TestingAuthenticationToken(token.split("#")[0], null, "SCOPE_" + chunks[1]);
62+
})
63+
)
64+
.authenticationProvider(new TestingAuthenticationProvider());
65+
}
66+
67+
68+
}
69+
70+
}
71+
72+
73+
@Test
74+
public void customSchemeTest() throws ExecutionException, InterruptedException {
75+
String userName = "userToken1";
76+
String reply = invoke(userName, "profile");
77+
assertNotNull("Reply should not be null", reply);
78+
assertTrue(String.format("Reply should contain name '%s'", userName), reply.contains(userName));
79+
}
80+
81+
@Test
82+
public void customSchemeAccessDeniedTest() {
83+
84+
final StatusRuntimeException e = assertThrows(StatusRuntimeException.class,
85+
() -> invoke("userToken1", "deniedProfile")
86+
);
87+
assertThat(e.getStatus().getCode(), Matchers.is(Status.Code.PERMISSION_DENIED));
88+
89+
}
90+
91+
private String invoke(String userName, String authority) {
92+
AuthCallCredentials callCredentials = new AuthCallCredentials(
93+
AuthHeader.builder().authScheme(MY_CUSTOM_SCHEME_NAME).tokenSupplier(() ->
94+
ByteBuffer.wrap(String.format("%s#%s", userName, authority).getBytes()))
95+
);
96+
97+
return SecuredGreeterGrpc.newBlockingStub(getChannel())
98+
.withCallCredentials(callCredentials)
99+
.sayAuthHello2(Empty.newBuilder().build()).getMessage();
100+
}
101+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,18 @@ public void sayAuthHello(Empty request, StreamObserver<GreeterOuterClass.HelloRe
2424
user = JwtAuthenticationToken.class.cast(auth).getTokenAttributes().get("preferred_username").toString();
2525
}
2626

27+
reply(user,responseObserver);
28+
}
29+
30+
@Override
31+
public void sayAuthHello2(Empty request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
32+
reply(GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get().getName(),responseObserver);
33+
}
34+
35+
private void reply(String userName,StreamObserver<GreeterOuterClass.HelloReply> responseObserver){
2736
responseObserver.onNext(GreeterOuterClass.HelloReply
2837
.newBuilder()
29-
.setMessage(user)
38+
.setMessage(userName)
3039
.build());
3140
responseObserver.onCompleted();
3241
}

grpc-spring-boot-starter-demo/src/main/proto/greeter.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ service Greeter {
1616
}
1717
service SecuredGreeter {
1818
rpc SayAuthHello ( google.protobuf.Empty) returns ( HelloReply) {}
19+
rpc SayAuthHello2 ( google.protobuf.Empty) returns ( HelloReply) {}
1920

2021
}
2122

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

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,37 @@ io.grpc.examples.GreeterOuterClass.HelloReply> getSayAuthHelloMethod() {
4545
return getSayAuthHelloMethod;
4646
}
4747

48+
private static volatile io.grpc.MethodDescriptor<com.google.protobuf.Empty,
49+
io.grpc.examples.GreeterOuterClass.HelloReply> getSayAuthHello2Method;
50+
51+
@io.grpc.stub.annotations.RpcMethod(
52+
fullMethodName = SERVICE_NAME + '/' + "SayAuthHello2",
53+
requestType = com.google.protobuf.Empty.class,
54+
responseType = io.grpc.examples.GreeterOuterClass.HelloReply.class,
55+
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
56+
public static io.grpc.MethodDescriptor<com.google.protobuf.Empty,
57+
io.grpc.examples.GreeterOuterClass.HelloReply> getSayAuthHello2Method() {
58+
io.grpc.MethodDescriptor<com.google.protobuf.Empty, io.grpc.examples.GreeterOuterClass.HelloReply> getSayAuthHello2Method;
59+
if ((getSayAuthHello2Method = SecuredGreeterGrpc.getSayAuthHello2Method) == null) {
60+
synchronized (SecuredGreeterGrpc.class) {
61+
if ((getSayAuthHello2Method = SecuredGreeterGrpc.getSayAuthHello2Method) == null) {
62+
SecuredGreeterGrpc.getSayAuthHello2Method = getSayAuthHello2Method =
63+
io.grpc.MethodDescriptor.<com.google.protobuf.Empty, io.grpc.examples.GreeterOuterClass.HelloReply>newBuilder()
64+
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
65+
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "SayAuthHello2"))
66+
.setSampledToLocalTracing(true)
67+
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
68+
com.google.protobuf.Empty.getDefaultInstance()))
69+
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
70+
io.grpc.examples.GreeterOuterClass.HelloReply.getDefaultInstance()))
71+
.setSchemaDescriptor(new SecuredGreeterMethodDescriptorSupplier("SayAuthHello2"))
72+
.build();
73+
}
74+
}
75+
}
76+
return getSayAuthHello2Method;
77+
}
78+
4879
/**
4980
* Creates a new async stub that supports all call types for the service
5081
*/
@@ -100,6 +131,13 @@ public void sayAuthHello(com.google.protobuf.Empty request,
100131
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getSayAuthHelloMethod(), responseObserver);
101132
}
102133

134+
/**
135+
*/
136+
public void sayAuthHello2(com.google.protobuf.Empty request,
137+
io.grpc.stub.StreamObserver<io.grpc.examples.GreeterOuterClass.HelloReply> responseObserver) {
138+
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getSayAuthHello2Method(), responseObserver);
139+
}
140+
103141
@java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
104142
return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
105143
.addMethod(
@@ -109,6 +147,13 @@ public void sayAuthHello(com.google.protobuf.Empty request,
109147
com.google.protobuf.Empty,
110148
io.grpc.examples.GreeterOuterClass.HelloReply>(
111149
this, METHODID_SAY_AUTH_HELLO)))
150+
.addMethod(
151+
getSayAuthHello2Method(),
152+
io.grpc.stub.ServerCalls.asyncUnaryCall(
153+
new MethodHandlers<
154+
com.google.protobuf.Empty,
155+
io.grpc.examples.GreeterOuterClass.HelloReply>(
156+
this, METHODID_SAY_AUTH_HELLO2)))
112157
.build();
113158
}
114159
}
@@ -134,6 +179,14 @@ public void sayAuthHello(com.google.protobuf.Empty request,
134179
io.grpc.stub.ClientCalls.asyncUnaryCall(
135180
getChannel().newCall(getSayAuthHelloMethod(), getCallOptions()), request, responseObserver);
136181
}
182+
183+
/**
184+
*/
185+
public void sayAuthHello2(com.google.protobuf.Empty request,
186+
io.grpc.stub.StreamObserver<io.grpc.examples.GreeterOuterClass.HelloReply> responseObserver) {
187+
io.grpc.stub.ClientCalls.asyncUnaryCall(
188+
getChannel().newCall(getSayAuthHello2Method(), getCallOptions()), request, responseObserver);
189+
}
137190
}
138191

139192
/**
@@ -156,6 +209,13 @@ public io.grpc.examples.GreeterOuterClass.HelloReply sayAuthHello(com.google.pro
156209
return io.grpc.stub.ClientCalls.blockingUnaryCall(
157210
getChannel(), getSayAuthHelloMethod(), getCallOptions(), request);
158211
}
212+
213+
/**
214+
*/
215+
public io.grpc.examples.GreeterOuterClass.HelloReply sayAuthHello2(com.google.protobuf.Empty request) {
216+
return io.grpc.stub.ClientCalls.blockingUnaryCall(
217+
getChannel(), getSayAuthHello2Method(), getCallOptions(), request);
218+
}
159219
}
160220

161221
/**
@@ -179,9 +239,18 @@ public com.google.common.util.concurrent.ListenableFuture<io.grpc.examples.Greet
179239
return io.grpc.stub.ClientCalls.futureUnaryCall(
180240
getChannel().newCall(getSayAuthHelloMethod(), getCallOptions()), request);
181241
}
242+
243+
/**
244+
*/
245+
public com.google.common.util.concurrent.ListenableFuture<io.grpc.examples.GreeterOuterClass.HelloReply> sayAuthHello2(
246+
com.google.protobuf.Empty request) {
247+
return io.grpc.stub.ClientCalls.futureUnaryCall(
248+
getChannel().newCall(getSayAuthHello2Method(), getCallOptions()), request);
249+
}
182250
}
183251

184252
private static final int METHODID_SAY_AUTH_HELLO = 0;
253+
private static final int METHODID_SAY_AUTH_HELLO2 = 1;
185254

186255
private static final class MethodHandlers<Req, Resp> implements
187256
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
@@ -204,6 +273,10 @@ public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserv
204273
serviceImpl.sayAuthHello((com.google.protobuf.Empty) request,
205274
(io.grpc.stub.StreamObserver<io.grpc.examples.GreeterOuterClass.HelloReply>) responseObserver);
206275
break;
276+
case METHODID_SAY_AUTH_HELLO2:
277+
serviceImpl.sayAuthHello2((com.google.protobuf.Empty) request,
278+
(io.grpc.stub.StreamObserver<io.grpc.examples.GreeterOuterClass.HelloReply>) responseObserver);
279+
break;
207280
default:
208281
throw new AssertionError();
209282
}
@@ -266,6 +339,7 @@ public static io.grpc.ServiceDescriptor getServiceDescriptor() {
266339
serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
267340
.setSchemaDescriptor(new SecuredGreeterFileDescriptorSupplier())
268341
.addMethod(getSayAuthHelloMethod())
342+
.addMethod(getSayAuthHello2Method())
269343
.build();
270344
}
271345
}

0 commit comments

Comments
 (0)