mirror of https://github.com/grpc/grpc-go.git
example: authentication (#2531)
This commit is contained in:
parent
adac1aeabd
commit
2cb2074db0
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
Loading…
Reference in New Issue