微服务安全

Micro service security

在过去的几天里,我一直在玩微服务模式,一切都很顺利,但安全问题似乎让我感到困惑。

所以如果我可以问一个问题: 如何处理单个服务的用户身份验证?目前,我将请求传递给 Gateway API,后者又连接到该服务。

问题已编辑请见下方

请记住,各个服务不应相互了解。 Gateway 本身就是聚合器。

当前架构。

模拟请求的小代码:

前端 - 客户端应用程序

public class EntityRepository<T>
{
    private IGateway _gateway = null;
    public EntityRepository(IGateway gateway)
    {
        this._gateway = gateway;
    }
    public IEnumerable<T> FindAll()
    {
        return this._gateway.Get(typeof(T)).Content.ReadAsAsync<IEnumerable<T>>().Result;
    }
    public T FindById(int id)
    {
        return this._gateway.Get(typeof(T)).Content.ReadAsAsync<T>().Result;
    }
    public void Add(T obj)
    {
        this._gateway.Post(typeof(T), obj);
    }
    public void Update(T obj)
    {
        this._gateway.Post(typeof(T), obj);
    }
    public void Save(T obj)
    {
        this._gateway.Post(typeof(T), obj);
    }
}


   //Logic lives elsewhere
   public HttpResponseMessage Get(Type type)
   {
      return Connect().GetAsync(Path(type)).Result;
   }
   public HttpResponseMessage Post(Type type, dynamic obj)
   {
      return Connect().PostAsync(Path(type), obj);
   }
    private string Path(Type type)
    {
        var className = type.Name;
        return "api/service/" + Application.Key + "/" + className;
    }
    private HttpClient Connect()
    {
        var client = new HttpClient();
        client.BaseAddress = new Uri("X");

        // Add an Accept header for JSON format.
         client.DefaultRequestHeaders.Accept.Add(
         new MediaTypeWithQualityHeaderValue("application/json"));

        return client;
    }

我使用泛型来确定它到达网关后需要在何处触发。 因此,如果 TypeCategory,它将触发 Category 服务,从而调用:

public IEnumerable<dynamic> FindAll(string appKey, string cls)
{
    var response = ConnectTo.Service(appKey, cls);
    return (appKey == Application.Key) ? (response.IsSuccessStatusCode) ? response.Content.ReadAsAsync<IEnumerable<dynamic>>().Result : null : null;
}

网关不包含类型的物理 files/Class。

写了一些代码后,我希望有人能给我一些演示或用当前架构处理 security/user 身份验证的最佳方法。

案例场景 1 用户点击网络应用程序并登录,此时用户加密的电子邮件和密码被发送到 Gateway API 然后传递到 User Service 并决定用户是否通过身份验证 - 一切都很好但现在我想从用户收到的 Message Service 中获取所有消息。如果用户通过身份验证,我真的不能在网关中说获取消息,因为这不能解决在 Gateway API

之外调用 Message Service 的问题

我也无法为每个单独的服务添加身份验证,因为这将需要所有相应的服务与 User Service 对话,这违背了模式的目的。

修复: 只允许网关调用服务。应阻止对网关外部服务的请求。

我知道安全是一个广泛的话题,但在当前情况下,我希望有人能指导我采取最佳行动方案来解决问题。

目前,我在所有关闭的应用程序中硬编码了一个 Guid,如果应用程序相等,它又会获取数据。

选项 1(首选)

简单的方法是微服务应该在网关后面,因此您可以将服务列入白名单以连接到它们,这意味着只有授权和受信任的方才能访问(即只有网关)。客户不应该直接访问它们。 Gateway 是您的夜总会保镖。

选项 2

您可以使用 JWT 或某种形式的令牌并在服务之间共享密钥。我使用 JWT 授权不记名令牌。

其他服务不需要查询用户服务,只需要知道token有效,就可以授权使用API。我获取从客户端传递到网关的 JWT,并将其注入到发送到后面其他服务的请求中,直接通过。

后面的微服务需要与授权网关具有相同的 JWT 消耗,但正如我提到的那样,这只是确定一个有效的令牌,而不是查询一个有效的用户。

但这有一个问题,即一旦某人获得授权,他们就可以跳转调用其他用户数据,除非您在令牌中包含声明之类的内容。

我的想法

我发现从单体服务到微服务的挑战部分是您需要切换您信任的地方。在 Monolithic 中,您可以控制您负责的一切。微服务的要点是其他服务可以完全控制它们的域。您必须信任其他服务来履行其义务,并且不想在必要的范围之外重新检查和重新授权每个级别的所有内容。

编辑

这个回答是关于网关<->微服务通信的。当应用程序与网关对话时,用户当然应该得到正确的身份验证

结束编辑

首先,微服务应该不能从互联网访问。它们应该只能从网关访问(可以集群)。

其次,您确实需要能够识别当前用户。您可以将 UserId 作为 HTTP header 传递。创建一个 WebApi 过滤器,它采用 header 并从中创建一个自定义 IPrincipal

最后你需要一些方法来确保请求来自网关或其他微服务。一种简单的方法是对令牌使用 HMAC 身份验证。

将每个服务和网关的密钥存储在 web.config 中。然后只需在每个请求中发送一个令牌(您可以使用 WebApi 身份验证过滤器对其进行身份验证)

要生成散列,请使用 .NET 中的 HMACSHA256 class:

private static string CreateToken(string message, string secret)
{
    secret = secret ?? "";
    var keyByte = Encoding.ASCII.GetBytes(secret);
    var messageBytes = Encoding.ASCII.GetBytes(message);
    using (var hasher = new HMACSHA256(keyByte))
    {
        var hashmessage = hasher.ComputeHash(messageBytes);
        return Convert.ToBase64String(hashmessage);
    }
}

所以在你的 MicroServiceClient 你会做这样的事情:

var hash = CreateToken(userId.ToString(), mySharedSecret);
var myHttpRequest = HttpRequest.Create("yourUrl");
myHttpRequest.AddHeader("UserId", userId);
myHttpRequest.AddHeader("UserIdToken", hash);
//send request..

然后在微服务中创建一个过滤器,如:

public class TokenAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
    protected string SharedSecret
    {
        get { return ConfigurationManager.AppSettings["SharedSecret"]; }
    }

    public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        await Task.Run(() =>
        {
            var userId = context.Request.Headers.GetValues("UserId").FirstOrDefault();
            if (userId == null)
            {
                context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
                return;
            }

            var userIdToken = context.Request.Headers.GetValues("UserIdToken").FirstOrDefault();
            if (userIdToken == null)
            {
                context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
                return;
            }

            var token = CreateToken(userId, SharedSecret);
            if (token != userIdToken)
            {
                context.ErrorResult = new StatusCodeResult(HttpStatusCode.Forbidden, context.Request);
                return;
            }


            var principal = new GenericPrincipal(new GenericIdentity(userId, "CustomIdentification"),
                new[] {"ServiceRole"});
            context.Principal = principal;
        });
    }

    public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
    }

    public bool AllowMultiple
    {
        get { return false; }
    }

    private static string CreateToken(string message, string secret)
    {
        secret = secret ?? "";
        var keyByte = Encoding.ASCII.GetBytes(secret);
        var messageBytes = Encoding.ASCII.GetBytes(message);
        using (var hasher = new HMACSHA256(keyByte))
        {
            var hashmessage = hasher.ComputeHash(messageBytes);
            return Convert.ToBase64String(hashmessage);
        }
    }
}