认证
客户端和服务端之间调用,我们可以通过加入证书的方式,实现调用的安全性
TLS(Transport Layer Security,安全传输层),TLS是建立在传输层 TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。
TLS协议主要解决如下三个网络安全问题。
- 保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;
- 完整性(message integrity),通过MAC校验机制,一旦被篡改,通信双方会立刻发现;
- 认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;
1. 生成自签证书
生产环境可以购买证书或者使用一些平台发放的免费证书
1.1 安装openssl
网站下载:http://slproweb.com/products/Win32OpenSSL.html 下载后选择安装位置直接安装即可。安装完成后设置环境变量,例如工具安装在C:\OpenSSL-Win64,则将C:\OpenSSL-Win64\bin;配置到Path中。
1.2 生成私钥、证书等
生成TLS证书的内容:使用TLS时相关问题及解决办法
上述认证方式为单向认证:
中间人攻击
4. 双向认证
上面的server.pem和server.key 是服务端的 公钥和私钥。
如果双向认证,客户端也需要生成对应的公钥和私钥。
私钥:
openssl genpkey -algorithm RSA -out client.key
证书:
openssl req -new -nodes -key client.key -out client.csr -days 3650 -config ./openssl.cnf -extensions v3_req
SAN证书:
openssl x509 -req -days 365 -in client.csr -out client.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
服务端:
func main() {
cert, err := tls.LoadX509KeyPair("keys/mszlu.pem", "keys/mszlu.key")
if err != nil {
log.Fatal("证书读取错误",err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("keys/ca.crt")
if err != nil {
log.Fatal("ca证书读取错误",err)
}
certPool.AppendCertsFromPEM(ca)
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
})
rpcServer := grpc.NewServer(grpc.Creds(creds))
service.RegisterProdServiceServer(rpcServer,service.ProductService)
listener ,err := net.Listen("tcp",":8002")
if err != nil {
log.Fatal("启动监听出错",err)
}
err = rpcServer.Serve(listener)
if err != nil {
log.Fatal("启动服务出错",err)
}
fmt.Println("启动grpc服务端成功")
}
客户端:
func main() {
cert, _ := tls.LoadX509KeyPair("client/keys/test.pem", "client/keys/test.key")
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("client/keys/ca.crt")
certPool.AppendCertsFromPEM(ca)
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: "*.mszlu.com",
RootCAs: certPool,
})
conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatal("服务端出错,连接不上",err)
}
defer conn.Close()
prodClient := service.NewProdServiceClient(conn)
request := &service.ProductRequest{
ProdId: 123,
}
stockResponse, err := prodClient.GetProductStock(context.Background(), request)
if err != nil {
log.Fatal("查询库存出错",err)
}
fmt.Println("查询成功",stockResponse.ProdStock)
}
5. Token认证
5.1 服务端添加用户名密码的校验
func main() {
var authInterceptor grpc.UnaryServerInterceptor
authInterceptor = func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
err = Auth(ctx)
if err != nil {
return
}
return handler(ctx, req)
}
server := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor))
service.RegisterProdServiceServer(server,service.ProductService)
listener, err := net.Listen("tcp", ":8002")
if err != nil {
log.Fatal("服务监听端口失败", err)
}
err = server.Serve(listener)
if err != nil {
log.Fatal("服务、启动失败", err)
}
fmt.Println("启动成功")
}
func Auth(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return fmt.Errorf("missing credentials")
}
var user string
var password string
if val, ok := md["user"]; ok {
user = val[0]
}
if val, ok := md["password"]; ok {
password = val[0]
}
if user != "admin" || password != "admin" {
return status.Errorf(codes.Unauthenticated, "token不合法")
}
return nil
}
5.2 客户端实现
客户端需要实现 PerRPCCredentials 接口。
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
GetRequestMetadata 方法返回认证需要的必要信息,RequireTransportSecurity 方法表示是否启用安全链接,在生产环境中,一般都是启用的,但为了测试方便,暂时这里不启用了。
实现接口:
type Authentication struct {
User string
Password string
}
func (a *Authentication) GetRequestMetadata(context.Context, ...string) (
map[string]string, error,
) {
return map[string]string{"user": a.User, "password": a.Password}, nil
}
func (a *Authentication) RequireTransportSecurity() bool {
return false
}
应用:
user := &auth.Authentication{
User: "admin",
Password: "admin",
}
conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithPerRPCCredentials(user))
|