snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
62
examples/features/authentication/README.md
Normal file
62
examples/features/authentication/README.md
Normal 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 initialize a
|
||||
[`oauth.TokenSource`](https://godoc.org/google.golang.org/grpc/credentials/oauth#TokenSource)
|
||||
which implements `credentials.PerRPCCredentials`. 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.
|
||||
86
examples/features/authentication/client/main.go
Normal file
86
examples/features/authentication/client/main.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// The client demonstrates how to supply an OAuth2 token for every RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/oauth"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
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.TokenSource{TokenSource: oauth2.StaticTokenSource(fetchToken())}
|
||||
creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.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
|
||||
// itself.
|
||||
// See: https://godoc.org/google.golang.org/grpc#PerRPCCredentials
|
||||
grpc.WithPerRPCCredentials(perRPC),
|
||||
// oauth.TokenSource requires the configuration of transport
|
||||
// credentials.
|
||||
grpc.WithTransportCredentials(creds),
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(*addr, opts...)
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
rgc := ecpb.NewEchoClient(conn)
|
||||
|
||||
callUnaryEcho(rgc, "hello world")
|
||||
}
|
||||
|
||||
// fetchToken simulates a token lookup and omits the details of proper token
|
||||
// acquisition. For examples of how to acquire an OAuth2 token, see:
|
||||
// https://godoc.org/golang.org/x/oauth2
|
||||
func fetchToken() *oauth2.Token {
|
||||
return &oauth2.Token{
|
||||
AccessToken: "some-secret-token",
|
||||
}
|
||||
}
|
||||
112
examples/features/authentication/server/main.go
Normal file
112
examples/features/authentication/server/main.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// The server demonstrates how to consume and validate OAuth2 tokens provided by
|
||||
// clients for each RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
|
||||
errInvalidToken = status.Errorf(codes.Unauthenticated, "invalid token")
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
fmt.Printf("server starting on port %d...\n", *port)
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load key pair: %s", err)
|
||||
}
|
||||
opts := []grpc.ServerOption{
|
||||
// The following grpc.ServerOption adds an interceptor for all unary
|
||||
// RPCs. To configure an interceptor for streaming RPCs, see:
|
||||
// https://godoc.org/google.golang.org/grpc#StreamInterceptor
|
||||
grpc.UnaryInterceptor(ensureValidToken),
|
||||
// Enable TLS for all incoming connections.
|
||||
grpc.Creds(credentials.NewServerTLSFromCert(&cert)),
|
||||
}
|
||||
s := grpc.NewServer(opts...)
|
||||
pb.RegisterEchoServer(s, &ecServer{})
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type ecServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
// valid validates the authorization.
|
||||
func valid(authorization []string) bool {
|
||||
if len(authorization) < 1 {
|
||||
return false
|
||||
}
|
||||
token := strings.TrimPrefix(authorization[0], "Bearer ")
|
||||
// Perform the token validation here. For the sake of this example, the code
|
||||
// here forgoes any of the usual OAuth2 token validation and instead checks
|
||||
// for a token matching an arbitrary string.
|
||||
return token == "some-secret-token"
|
||||
}
|
||||
|
||||
// ensureValidToken ensures a valid token exists within a request's metadata. If
|
||||
// the token is missing or invalid, the interceptor blocks execution of the
|
||||
// handler and returns an error. Otherwise, the interceptor invokes the unary
|
||||
// handler.
|
||||
func ensureValidToken(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, errMissingMetadata
|
||||
}
|
||||
// The keys within metadata.MD are normalized to lowercase.
|
||||
// See: https://godoc.org/google.golang.org/grpc/metadata#New
|
||||
if !valid(md["authorization"]) {
|
||||
return nil, errInvalidToken
|
||||
}
|
||||
// Continue execution of handler after ensuring a valid token.
|
||||
return handler(ctx, req)
|
||||
}
|
||||
Reference in New Issue
Block a user