You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

auth.go 6.3 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package rpc
  2. import (
  3. "crypto/tls"
  4. "fmt"
  5. "strconv"
  6. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  7. "golang.org/x/net/context"
  8. "google.golang.org/grpc"
  9. "google.golang.org/grpc/codes"
  10. "google.golang.org/grpc/credentials"
  11. "google.golang.org/grpc/metadata"
  12. "google.golang.org/grpc/peer"
  13. "google.golang.org/grpc/status"
  14. )
  15. const (
  16. ClientAPISNIV1 = "rpc.client.jcs-pub.v1"
  17. InternalAPISNIV1 = "rpc.internal.jcs-pub.v1"
  18. MetaUserID = "x-jcs-user-id"
  19. MetaAccessTokenID = "x-jcs-access-token-id"
  20. MetaNonce = "x-jcs-nonce"
  21. MetaSignature = "x-jcs-signature"
  22. MetaTokenAuthInfo = "x-jcs-token-auth-info"
  23. )
  24. type AccessTokenAuthInfo struct {
  25. UserID jcstypes.UserID
  26. AccessTokenID jcstypes.AccessTokenID
  27. Nonce string
  28. Signature string
  29. }
  30. type AccessTokenVerifier interface {
  31. Verify(authInfo AccessTokenAuthInfo) bool
  32. }
  33. type AccessTokenProvider interface {
  34. MakeAuthInfo() (AccessTokenAuthInfo, error)
  35. }
  36. func (s *ServerBase) tlsConfigSelector(hello *tls.ClientHelloInfo) (*tls.Config, error) {
  37. switch hello.ServerName {
  38. case ClientAPISNIV1:
  39. return &tls.Config{
  40. Certificates: []tls.Certificate{s.serverCert},
  41. ClientAuth: tls.NoClientCert,
  42. NextProtos: []string{"h2"},
  43. }, nil
  44. case InternalAPISNIV1:
  45. return &tls.Config{
  46. Certificates: []tls.Certificate{s.serverCert},
  47. ClientAuth: tls.RequireAndVerifyClientCert,
  48. ClientCAs: s.rootCA,
  49. NextProtos: []string{"h2"},
  50. }, nil
  51. default:
  52. return nil, fmt.Errorf("unknown server name: %s", hello.ServerName)
  53. }
  54. }
  55. func (s *ServerBase) authUnary(
  56. ctx context.Context,
  57. req interface{},
  58. info *grpc.UnaryServerInfo,
  59. handler grpc.UnaryHandler,
  60. ) (resp any, err error) {
  61. pr, ok := peer.FromContext(ctx)
  62. if !ok {
  63. return nil, status.Error(codes.Unauthenticated, "no peer found in context")
  64. }
  65. tlsInfo, ok := pr.AuthInfo.(credentials.TLSInfo)
  66. if !ok {
  67. return nil, status.Error(codes.Unauthenticated, "no tls info found in peer")
  68. }
  69. // 如果是使用interanl ServerName通过的TLS认证,则直接放行
  70. if tlsInfo.State.ServerName == InternalAPISNIV1 {
  71. return handler(ctx, req)
  72. }
  73. // 如果是无需认证的API,则直接放行
  74. if s.noAuthAPIs[info.FullMethod] {
  75. return handler(ctx, req)
  76. }
  77. // 否则要进行额外的Token认证
  78. if !s.accessTokenAuthAPIs[info.FullMethod] {
  79. return nil, status.Error(codes.Unauthenticated, "unauthorized access")
  80. }
  81. meta, ok := metadata.FromIncomingContext(ctx)
  82. if !ok {
  83. return nil, status.Error(codes.Unauthenticated, "no metadata found in context")
  84. }
  85. userIDs := meta.Get(MetaUserID)
  86. if len(userIDs) != 1 {
  87. return nil, status.Error(codes.Unauthenticated, "missing or multiple user ids in metadata")
  88. }
  89. userID, err := strconv.ParseInt(userIDs[0], 10, 64)
  90. if err != nil {
  91. return nil, status.Error(codes.Unauthenticated, "invalid user id in metadata")
  92. }
  93. accessTokenIDs := meta.Get(MetaAccessTokenID)
  94. if len(accessTokenIDs) != 1 {
  95. return nil, status.Error(codes.Unauthenticated, "missing or multiple access token ids in metadata")
  96. }
  97. nonce := meta.Get(MetaNonce)
  98. if len(nonce) != 1 {
  99. return nil, status.Error(codes.Unauthenticated, "missing or multiple nonces in metadata")
  100. }
  101. signature := meta.Get(MetaSignature)
  102. if len(signature) != 1 {
  103. return nil, status.Error(codes.Unauthenticated, "missing or multiple signatures in metadata")
  104. }
  105. authInfo := AccessTokenAuthInfo{
  106. UserID: jcstypes.UserID(userID),
  107. AccessTokenID: jcstypes.AccessTokenID(accessTokenIDs[0]),
  108. Nonce: nonce[0],
  109. Signature: signature[0],
  110. }
  111. if !s.tokenVerifier.Verify(authInfo) {
  112. return nil, status.Error(codes.Unauthenticated, "invalid access token")
  113. }
  114. ctx = context.WithValue(ctx, MetaTokenAuthInfo, authInfo)
  115. return handler(ctx, req)
  116. }
  117. func (s *ServerBase) authStream(
  118. srv any,
  119. stream grpc.ServerStream,
  120. info *grpc.StreamServerInfo,
  121. handler grpc.StreamHandler,
  122. ) error {
  123. pr, ok := peer.FromContext(stream.Context())
  124. if !ok {
  125. return status.Error(codes.Unauthenticated, "no peer found in context")
  126. }
  127. tlsInfo, ok := pr.AuthInfo.(credentials.TLSInfo)
  128. if !ok {
  129. return status.Error(codes.Unauthenticated, "no tls info found in peer")
  130. }
  131. // 如果是使用interanl ServerName通过的TLS认证,则直接放行
  132. if tlsInfo.State.ServerName == InternalAPISNIV1 {
  133. return handler(srv, stream)
  134. }
  135. // 如果是无需认证的API,则直接放行
  136. if s.noAuthAPIs[info.FullMethod] {
  137. return handler(srv, stream)
  138. }
  139. // 否则要进行额外的Token认证
  140. if !s.accessTokenAuthAPIs[info.FullMethod] {
  141. return status.Error(codes.Unauthenticated, "unauthorized access")
  142. }
  143. meta, ok := metadata.FromIncomingContext(stream.Context())
  144. if !ok {
  145. return status.Error(codes.Unauthenticated, "no metadata found in context")
  146. }
  147. userIDs := meta.Get(MetaUserID)
  148. if len(userIDs) != 1 {
  149. return status.Error(codes.Unauthenticated, "missing or multiple user ids in metadata")
  150. }
  151. userID, err := strconv.ParseInt(userIDs[0], 10, 64)
  152. if err != nil {
  153. return status.Error(codes.Unauthenticated, "invalid user id in metadata")
  154. }
  155. accessTokenIDs := meta.Get(MetaAccessTokenID)
  156. if len(accessTokenIDs) != 1 {
  157. return status.Error(codes.Unauthenticated, "missing or multiple access token ids in metadata")
  158. }
  159. nonce := meta.Get(MetaNonce)
  160. if len(nonce) != 1 {
  161. return status.Error(codes.Unauthenticated, "missing or multiple nonces in metadata")
  162. }
  163. signature := meta.Get(MetaSignature)
  164. if len(signature) != 1 {
  165. return status.Error(codes.Unauthenticated, "missing or multiple signatures in metadata")
  166. }
  167. authInfo := AccessTokenAuthInfo{
  168. UserID: jcstypes.UserID(userID),
  169. AccessTokenID: jcstypes.AccessTokenID(accessTokenIDs[0]),
  170. Nonce: nonce[0],
  171. Signature: signature[0],
  172. }
  173. if !s.tokenVerifier.Verify(authInfo) {
  174. return status.Error(codes.Unauthenticated, "invalid access token")
  175. }
  176. return handler(srv, &serverStream{stream, context.WithValue(stream.Context(), MetaTokenAuthInfo, authInfo)})
  177. }
  178. type serverStream struct {
  179. grpc.ServerStream
  180. ctx context.Context
  181. }
  182. func (s *serverStream) Context() context.Context {
  183. return s.ctx
  184. }
  185. func GetAuthInfo(ctx context.Context) (AccessTokenAuthInfo, bool) {
  186. val := ctx.Value(MetaTokenAuthInfo)
  187. if val == nil {
  188. return AccessTokenAuthInfo{}, false
  189. }
  190. authInfo, ok := val.(AccessTokenAuthInfo)
  191. return authInfo, ok
  192. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。