example: authentication (#2531)

This commit is contained in:
lyuxuan 2018-12-21 14:43:20 -08:00 committed by GitHub
parent adac1aeabd
commit 2cb2074db0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 112 additions and 23 deletions

View File

@ -0,0 +1,62 @@
# Authentication
In grpc, authentication is abstracted as
[`credentials.PerRPCCredentials`](https://godoc.org/google.golang.org/grpc/credentials#PerRPCCredentials).
It usually also encompasses authorization. Users can configure it on a
per-connection basis or a per-call basis.
The example for authentication currently includes an example for using oauth2
with grpc.
## Try it
```
go run server/main.go
```
```
go run client/main.go
```
## Explanation
### OAuth2
OAuth 2.0 Protocol is a widely used authentication and authorization mechanism
nowadays. And grpc provides convenient APIs to configure OAuth to use with grpc.
Please refer to the godoc:
https://godoc.org/google.golang.org/grpc/credentials/oauth for details.
#### Client
On client side, users should first get a valid oauth token, and then call
[`credentials.NewOauthAccess`](https://godoc.org/google.golang.org/grpc/credentials/oauth#NewOauthAccess)
to initialize a `credentials.PerRPCCredentials` with it. Next, if user wants to
apply a single OAuth token for all RPC calls on the same connection, then
configure grpc `Dial` with `DialOption`
[`WithPerRPCCredentials`](https://godoc.org/google.golang.org/grpc#WithPerRPCCredentials).
Or, if user wants to apply OAuth token per call, then configure the grpc RPC
call with `CallOption`
[`PerRPCCredentials`](https://godoc.org/google.golang.org/grpc#PerRPCCredentials).
Note that OAuth requires the underlying transport to be secure (e.g. TLS, etc.)
Inside grpc, the provided token is prefixed with the token type and a space, and
is then attached to the metadata with the key "authorization".
### Server
On server side, users usually get the token and verify it inside an interceptor.
To get the token, call
[`metadata.FromIncomingContext`](https://godoc.org/google.golang.org/grpc/metadata#FromIncomingContext)
on the given context. It returns the metadata map. Next, use the key
"authorization" to get corresponding value, which is a slice of strings. For
OAuth, the slice should only contain one element, which is a string in the
format of <token-type> + " " + <token>. Users can easily get the token by
parsing the string, and then verify the validity of it.
If the token is not valid, returns an error with error code
`codes.Unauthenticated`.
If the token is valid, then invoke the method handler to start processing the
RPC.

View File

@ -21,7 +21,8 @@ package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"time"
@ -29,11 +30,31 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/oauth"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
ecpb "google.golang.org/grpc/examples/features/proto/echo"
"google.golang.org/grpc/testdata"
)
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
func callUnaryEcho(client ecpb.EchoClient, message string) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
if err != nil {
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
}
fmt.Println("UnaryEcho: ", resp.Message)
}
func main() {
flag.Parse()
// Set up the credentials for the connection.
perRPC := oauth.NewOauthAccess(fetchToken())
creds, err := credentials.NewClientTLSFromFile(testdata.Path("ca.pem"), "x.test.youtube.com")
if err != nil {
log.Fatalf("failed to load credentials: %v", err)
}
opts := []grpc.DialOption{
// In addition to the following grpc.DialOption, callers may also use
// the grpc.CallOption grpc.PerRPCCredentials with the RPC invocation
@ -42,24 +63,17 @@ func main() {
grpc.WithPerRPCCredentials(perRPC),
// oauth.NewOauthAccess requires the configuration of transport
// credentials.
grpc.WithTransportCredentials(
credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}),
),
grpc.WithTransportCredentials(creds),
}
conn, err := grpc.Dial(":8080", opts...)
conn, err := grpc.Dial(*addr, opts...)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
rgc := ecpb.NewEchoClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "authenticated-client"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
callUnaryEcho(rgc, "hello world")
}
// fetchToken simulates a token lookup and omits the details of proper token

View File

@ -23,6 +23,8 @@ package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"strings"
@ -30,7 +32,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
ecpb "google.golang.org/grpc/examples/features/proto/echo"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/grpc/testdata"
@ -41,8 +43,12 @@ var (
errInvalidToken = status.Errorf(codes.Unauthenticated, "invalid token")
)
var port = flag.Int("port", 50051, "the port to serve on")
func main() {
log.Println("server starting on port 8080...")
flag.Parse()
fmt.Printf("server starting on port %d...\n", *port)
cert, err := tls.LoadX509KeyPair(testdata.Path("server1.pem"), testdata.Path("server1.key"))
if err != nil {
log.Fatalf("failed to load key pair: %s", err)
@ -56,8 +62,8 @@ func main() {
grpc.Creds(credentials.NewServerTLSFromCert(&cert)),
}
s := grpc.NewServer(opts...)
pb.RegisterGreeterServer(s, &server{})
lis, err := net.Listen("tcp", ":8080")
ecpb.RegisterEchoServer(s, &ecServer{})
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
@ -66,12 +72,19 @@ func main() {
}
}
// server is used to implement helloworld.GreeterServer.
type server struct{}
type ecServer struct{}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
func (s *ecServer) UnaryEcho(ctx context.Context, req *ecpb.EchoRequest) (*ecpb.EchoResponse, error) {
return &ecpb.EchoResponse{Message: req.Message}, nil
}
func (s *ecServer) ServerStreamingEcho(*ecpb.EchoRequest, ecpb.Echo_ServerStreamingEchoServer) error {
return status.Errorf(codes.Unimplemented, "not implemented")
}
func (s *ecServer) ClientStreamingEcho(ecpb.Echo_ClientStreamingEchoServer) error {
return status.Errorf(codes.Unimplemented, "not implemented")
}
func (s *ecServer) BidirectionalStreamingEcho(ecpb.Echo_BidirectionalStreamingEchoServer) error {
return status.Errorf(codes.Unimplemented, "not implemented")
}
// valid validates the authorization.