Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Examples: Add a JWT authentication example #5915

Merged
merged 18 commits into from Mar 18, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
76 changes: 76 additions & 0 deletions examples/AUTHENTICATION_EXAMPLE.md
@@ -0,0 +1,76 @@
Authentication Example
==============================================

This example illustrates a simple JWT-based authentication implementation in gRPC using
server interceptor. It uses the JJWT library to create and verify JSON Web Tokens (JWTs).

The example requires grpc-java to be pre-built. Using a release tag will download the relevant binaries
from a maven repository. But if you need the latest SNAPSHOT binaries you will need to follow
[COMPILING](../COMPILING.md) to build these.

The source code is [here](src/main/java/io/grpc/examples/authentication). Please follow the
[steps](./README.md#to-build-the-examples) to build the examples. The build creates scripts
`auth-server` and `auth-client` in the `build/install/examples/bin/` directory which can be
used to run this example. The example requires the server to be running before starting the
client.

Running auth-server is similar to the normal hello world example and there are no arguments to supply:

**auth-server**:

```text
USAGE: AuthServer
```

The auth-client accepts optional arguments for user-name and client-id:

**auth-client**:

```text
USAGE: AuthClient [user-name] [client-id]
```

The `user-name` value is simply passed in the `HelloRequest` message as payload and the value of
`client-id` is included in the JWT claims passed in the metadata header.


#### How to run the example:

```bash
# Run the server:
./build/install/examples/bin/auth-server
# In another terminal run the client
./build/install/examples/bin/auth-client userA clientB
```

That's it! The client will show the user-name reflected back in the message from the server as follows:
```
INFO: Greeting: Hello, userA
```

And on the server side you will see the message with the client's identifier:
```
Processing request from clientB
```

## Maven

If you prefer to use Maven follow these [steps](./README.md#maven). You can run the example as follows:

```
$ # Run the server
$ mvn exec:java -Dexec.mainClass=io.grpc.examples.authentication.AuthServer
$ # In another terminal run the client
$ mvn exec:java -Dexec.mainClass=io.grpc.examples.authentication.AuthClient -Dexec.args="client-userA token-valueB"
```

## Bazel

If you prefer to use Bazel:
```
$ bazel build :auth-server :auth-client
$ # Run the server
$ bazel-bin/auth-server
$ # In another terminal run the client
$ bazel-bin/auth-client client-userA token-valueB
```
18 changes: 18 additions & 0 deletions examples/BUILD.bazel
Expand Up @@ -99,6 +99,24 @@ java_binary(
],
)

java_binary(
name = "auth-client",
testonly = 1,
main_class = "io.grpc.examples.authentication.AuthClient",
runtime_deps = [
":examples",
],
)

java_binary(
name = "auth-server",
testonly = 1,
main_class = "io.grpc.examples.authentication.AuthServer",
runtime_deps = [
":examples",
],
)

java_binary(
name = "route-guide-client",
testonly = 1,
Expand Down
2 changes: 2 additions & 0 deletions examples/README.md
Expand Up @@ -25,6 +25,8 @@ before trying out the examples.

- [Json serialization](src/main/java/io/grpc/examples/advanced)

- [Authentication](AUTHENTICATION_EXAMPLE.md)

- <details>
<summary>Hedging</summary>

Expand Down
20 changes: 20 additions & 0 deletions examples/build.gradle
Expand Up @@ -33,6 +33,10 @@ dependencies {
// examples/advanced need this for JsonFormat
implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}"

// examples/authentication need this to create and verify JSON Web Tokens (JWTs)
implementation "io.jsonwebtoken:jjwt:0.9.1"
implementation "javax.xml.bind:jaxb-api:2.3.1"
anarsultanov marked this conversation as resolved.
Show resolved Hide resolved

runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}"

testImplementation "io.grpc:grpc-testing:${grpcVersion}"
Expand Down Expand Up @@ -104,6 +108,20 @@ task hedgingHelloWorldClient(type: CreateStartScripts) {
classpath = startScripts.classpath
}

task authServer(type: CreateStartScripts) {
mainClassName = 'io.grpc.examples.authentication.AuthServer'
applicationName = 'auth-server'
outputDir = new File(project.buildDir, 'tmp')
classpath = startScripts.classpath
}

task authClient(type: CreateStartScripts) {
mainClassName = 'io.grpc.examples.authentication.AuthClient'
applicationName = 'auth-client'
outputDir = new File(project.buildDir, 'tmp')
classpath = startScripts.classpath
}

task compressingHelloWorldClient(type: CreateStartScripts) {
mainClassName = 'io.grpc.examples.experimental.CompressingHelloWorldClient'
applicationName = 'compressing-hello-world-client'
Expand All @@ -118,6 +136,8 @@ applicationDistribution.into('bin') {
from(helloWorldClient)
from(hedgingHelloWorldClient)
from(hedgingHelloWorldServer)
from(authServer)
from(authClient)
from(compressingHelloWorldClient)
fileMode = 0755
}
10 changes: 10 additions & 0 deletions examples/pom.xml
Expand Up @@ -46,6 +46,16 @@
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
Expand Down
115 changes: 115 additions & 0 deletions examples/src/main/java/io/grpc/examples/authentication/AuthClient.java
@@ -0,0 +1,115 @@
/*
* Copyright 2018 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.
*/

package io.grpc.examples.authentication;

import io.grpc.CallCredentials;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
* An authenticating client that requests a greeting from the {@link AuthServer}.
*/
public class AuthClient {

private static final Logger logger = Logger.getLogger(AuthClient.class.getName());

private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;

/**
* Construct client for accessing GreeterGrpc server.
*/
AuthClient(String host, int port) {
this(ManagedChannelBuilder
.forAddress(host, port)
// Channels are secure by default (via SSL/TLS). For this example we disable TLS to avoid
// needing certificates, but it is recommended to use a secure channel while passing
// credentials.
.usePlaintext()
.build());
}

/**
* Construct client for accessing GreeterGrpc server using the existing channel.
*/
AuthClient(ManagedChannel channel) {
this.channel = channel;
this.blockingStub = GreeterGrpc.newBlockingStub(channel);
}

public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}

/**
* Say hello to server.
*
* @param name name to set in HelloRequest
* @param clientId client identifier to set in JWT subject
* @return the message in the HelloReply from the server
*/
public String greet(String name, String clientId) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();

String jwt = getJwt(clientId); // build JWT
CallCredentials credentials = new BearerToken(jwt); // Wrap JWT in CallCredentials
HelloReply response = blockingStub
.withCallCredentials(credentials) // get a new stub that uses the given call credentials
.sayHello(request); // and call the server using it

logger.info("Greeting: " + response.getMessage());
return response.getMessage();
}

private static String getJwt(String clientId) {
return Jwts.builder()
.setSubject(clientId)
.signWith(SignatureAlgorithm.HS256, Constant.JWT_SIGNING_KEY)
.compact();
}

/**
* Greet server. If provided, the first element of {@code args} is the name to use in the greeting
* and the second is the client identifier to set in JWT
*/
public static void main(String[] args) throws Exception {
AuthClient client = new AuthClient("localhost", 50051);

try {
String user = "world";
String clientId = "default-client";
if (args.length > 0) {
user = args[0]; // Use the arg as the name to greet if provided
}
if (args.length > 1) {
clientId = args[1]; // Use the second argument as the client identifier if provided
}

client.greet(user, clientId);
} finally {
client.shutdown();
}
}
}
@@ -0,0 +1,93 @@
/*
* Copyright 2018 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.
*/

package io.grpc.examples.authentication;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;

/**
* Server that manages startup/shutdown of a {@code Greeter} server. This also uses a {@link
* JwtServerInterceptor} to intercept the JWT token passed
*/
public class AuthServer {

private static final Logger logger = Logger.getLogger(AuthServer.class.getName());

private Server server;

private void start() throws IOException {
// The port on which the server should run
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.intercept(new JwtServerInterceptor()) // add the JwtServerInterceptor
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
AuthServer.this.stop();
System.err.println("*** server shut down");
}
});
}

private void stop() {
if (server != null) {
server.shutdown();
}
}

/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}

/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final AuthServer server = new AuthServer();
server.start();
server.blockUntilShutdown();
}

static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
// get client id added to context by interceptor
String clientId = Constant.CLIENT_ID_CONTEXT_KEY.get();
System.out.println("Processing request from " + clientId);
HelloReply reply = HelloReply.newBuilder().setMessage("Hello, " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}