【问题标题】:Token based implementation in webapi to secure endpointswebapi中基于令牌的实现以保护端点
【发布时间】:2018-04-16 04:56:24
【问题描述】:

我有一个带有网络服务的网络应用程序,客户将使用我的网络应用程序注册他们的应用程序。

现在客户将拥有 SPA 或移动应用程序类型的应用程序,他们将从他们的应用程序中使用我的网络服务。

所以我将实施基于令牌的机制来保护对我的端点的访问。

1) 但是在这里我很困惑,我应该使用任何框架来生成访问令牌,或者我可以使用任何库来生成任何随机字符串,我将在响应中发送这些字符串。例如:

TokenId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("+", "_")

因此,在注册应用程序时,如果客户端已为其应用程序启用身份验证,则将验证用户,然后我将返回访问令牌并使用该用户 ID 将 accesstoken 保存在我的数据库中。

所以我的数据库表如下所示,用于存储经过验证的访问令牌:

Id(autogenerated)   accesstoken    userid   clientid    createdat    expiresat

因此,在用户通过身份验证之后,如果用户现在想要访问任何受保护的资源,那么用户需要在随后的标头调用中传递此访问令牌。

因此,我将从标头获取访问令牌,然后针对该数据库验证该访问令牌,然后允许访问我的受保护资源,其他明智的用户将获得授权。

我见过很多与此相关的东西,所以基本上这是 oauth2,我想实现它。

我见过Openid connect(这个项目甚至不编译),它位于oauth2之上,用于身份验证,oauth2将用于授权。

但是在这里,因为我将访问令牌存储在我的数据库中,所以我对此表示怀疑:

2) 现在我是否需要 openconnectid(但这个项目甚至不编译)来验证访问令牌,或者因为我将访问令牌存储在我的数据库中,所以我不需要 openconnectid?

3) 我想实现 asp.net 身份,但我会收到动态数据库连接字符串,因为我已经看到 asp.net 身份主要适用于实体框架,我找不到任何可以使用 ado.net 的来源使用 SQL 查询验证用户名和密码。我知道我可以这样做:

按照here 的描述创建一个实现 IUser 的自定义用户类 定义实现

的自定义用户存储
public class UserStoreService 
     : IUserStore<CustomUser>, IUserPasswordStore<CustomUser>

但我不会有这些信息,因为我没有固定的连接字符串。连接字符串再次存储在客户端注册的数据库中。

4) 我们为用户提供了一个固定端点,客户端可以通过该端点创建一个管理员,因此我将使用我的 RSA 算法进行密码散列,然后将其存储在数据库中。那么现在我需要使用 asp.net 身份吗?

5) 我已经看到很多基于令牌的实现的以下链接,但我没有得到他们在哪个部分验证 accesstoken 但现在因为我将 accesstoken 存储在我的数据库中,我需要使用以下任何实现?

http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/

http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/

6) 此外,如果对于任何客户端,如果客户端不希望对其各自的应用程序进行身份验证,那么我要做的是我将没有该用户名密码验证,但我将简单地生成 accesstoken,然后发送响应然后在随后的每个请求中,都会传递访问令牌以访问受保护的资源。您认为这有意义吗?

我从未见过任何将访问令牌存储在数据库中的示例,而将访问令牌存储在数据库中的问题是我每次都必须调用数据库以验证每个端点的访问令牌。

更新:

我的网络服务引擎的用例是:

1) 支持多客户端应用。

2) 以令牌管理的形式为每个客户端应用程序管理用户会话。因此,由于本文的大部分内容都将 accesstoken 存储在身份中,并且该身份在 [Authorize] 属性中进行了验证,其中还验证了 accesstoken 并基于该用户被允许访问受保护的资源。这是我到目前为止的理解。

那么,如果我还支持多个客户端应用程序的用户身份和存储用户上下文是一个好主意吗?

【问题讨论】:

  • 嗨学习。我想知道herehere 你是否会努力遵守你写作的大小写和撇号规则,在这里我在你的帖子中修复了另外 30 个左右的错误。您的英语显然很好,所以我想知道您是在手机上撰写帖子,还是这些错误是风格上的,因此是故意的?请记住,Stack Overflow 不是一个论坛 - 它是一个问答网站,我们试图在其中至少保持一些标准以保持清晰。
  • 有许多志愿编辑愿意帮助调整和改进帖子,但我们不是您的自动更正。你能帮忙吗?
  • 我已经从计算机上发布了这个问题,所以有时我确实会错过撇号和其他一些错误(我猜),但我认为我的问题目的很容易理解和明确,但如果我做了,我仍然很抱歉哪里出错了。
  • 如果您愿意,您可以查看已完成的修复in the question's revision history - 我数了 37 次修复 - 不仅仅是一两个。您的回复是否意味着您希望志愿者在您将来发布的每个帖子中修复您的错误?
  • 好吧,您现在已经了解到社区确实关心这些事情,您可以询问Meta question about it if you wish to。如果你发帖,请告诉我,这样我也可以回复。请注意,当您在帖子中出错时,编辑通常会修复它们,因此如果您故意这样做,您就是在故意浪费某人的宝贵时间。不用说,这不是很好。

标签: c# asp.net-web-api oauth-2.0 asp.net-identity owin


【解决方案1】:

来自 “7.1. Full-Scratch Implementor of OAuth and OpenID Connect Talks About Findings中的访问令牌表示”

应该如何表示访问令牌?主要有两种方式。

  1. 作为无意义的随机字符串。与访问相关的信息 令牌存储在授权服务器后面的数据库表中。

  2. 作为一个独立的字符串,它是编码访问令牌的结果 base64url 或类似的信息。

博客中描述了这两种方式的优缺点。

如果访问令牌是随机字符串,则与访问令牌相关的信息片段(用户 ID、客户端 ID、范围、生命周期等)存储在由已颁发访问令牌的授权服务器管理的数据库中.

每当暴露 API 的资源服务器接受来自客户端应用程序的 API 调用时,资源服务器必须以某种方式获取有关访问令牌的信息。

如果资源服务器可以访问授权服务器管理的数据库(即资源服务器和授权服务器共享数据库),则资源服务器可以直接从数据库中获取访问令牌的信息。

否则,资源服务器必须对授权服务器进行 API 调用以获取信息。在这种情况下,可以预期授权服务器公开一个符合RFC 7662(OAuth 2.0 Token Introspection)的 API。请注意,某些实现可能会提供比 RFC 7662 对开发人员更友好的 API(例如 4. Introspection Access Token)。

无论如何,如果服务器在内存缓存或其他适当的地方缓存有关访问令牌的信息,则您的资源服务器不必每次都进行 DB 调用(或对授权服务器的自省 API 调用)。

顺便说一句,当您想要保护 API 时,您需要的是访问令牌。因此,您的系统不必支持 OpenID Connect,它是关于如何请求和发布 ID 令牌 的规范。您可能会感到困惑,因为除了 ID 令牌之外,支持 OpenID Connect 的服务器也可以发布访问令牌。请参阅Diagrams of All The OpenID Connect Flows,了解支持 OpenID Connect 的服务器存在哪些问题。

最后,身份管理、用户身份验证以及 OAuth 2.0 和 OpenID Connect 不一定必须以单一方式实现。详情请参阅New Architecture of OAuth 2.0 and OpenID Connect Implementation

【讨论】:

  • 赞成您为帮助我所做的努力,非常感谢您。我正在尝试关注此链接(bitoftech.net/2014/06/01/…),基本上我所理解的是,到目前为止我所看到的就像当用户被验证时,用户身份会像 context.user 一样创建,如果我没有错,它包含有关用户的信息,然后生成 accesstoken。但唯一令人困惑的是如何将 accesstoken 信息放在上下文中并由资源服务器验证以允许访问到受保护的资源?
  • 有多种方式来实现身份管理、用户认证和 OAuth 2.0 & OpenID Connect。您所指的文章只是各种实现中的一种。恕我直言,访问令牌信息不应该放在用户上下文中,但是您所指的文章中的解决方案可能会将用户信息和访问令牌信息放在一个地方(用户上下文),但我不确定。跨度>
  • 我在我的问题中添加了用例。你能告诉我你的看法吗
【解决方案2】:

不,您不需要将 access_token 存储在数据库中。您可以解密 JWT 并读取信息,因为您是使用密钥对其进行加密的人。 (默认为机器密钥。)

Identity 对 Oauth 有一个关闭的自我支持。您必须正确配置它。您可以在 Startup.Auth.cs 中设置 OAuthAuthorizationServerOptions 的配置。示例代码如下。我已经尝试在代码中的 cmets 中回答您的大部分问题。

public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static string PublicClientId { get; private set; }

    public void ConfigureOAuth(IAppBuilder app)
    {
        // Configure the application for OAuth based flow
        PublicClientId = "theDragonIsAlive";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new YourOwnApplicationOAuthProvider(PublicClientId),
            //AuthorizeEndpointPath = new PathString("/Access/Account"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(7)
            //AllowInsecureHttp = true
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);

    }

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
    private readonly string _publicClientId;

    public ApplicationOAuthProvider(string publicClientId)
    {
        if (publicClientId == null)
        {
            throw new ArgumentNullException("publicClientId");
        }

        _publicClientId = publicClientId;
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
        // This where you are validating the username and password credentials.
        ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);

        if (user == null)
        {
            context.SetError("Dragon Fire:", "The user name or password is incorrect. You shall be burnt.");
            return;
        }

        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
           OAuthDefaults.AuthenticationType);

        AuthenticationProperties properties = CreateProperties(user.UserName);
        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(oAuthIdentity);
    }

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }

        return Task.FromResult<object>(null);
    }

    // This method is where you will create the client access token.
    // First you get the client, you can place values from the client record into the tokens claim collection.
    // You then create a new ClaimsIdentity.
    // You add some claims, in the example client name is added.
    // Create an AuthenticationTicket using your claims identity.
    // Validate the ticket (you do need to do this or the client will be considered unauthenticated)
    //public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
    //{
    //    var client = clientService.GetClient(context.ClientId);
    //    var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
    //    oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, client.ClientName));
    //    var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
    //    context.Validated(ticket);
    //    return base.GrantClientCredentials(context);
    //}

    // This method has to be implmented when you are maintaining a list of clients which you will allow.
    // This method is for validating the input, you can used this method to verify the client id and secret are valid.
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        //string clientId;
        //string clientSecret;
        //context.TryGetFormCredentials(out clientId, out clientSecret);

        //if (clientId == "1234" && clientSecret == "12345")
        //{
        //    context.Validated(clientId);
        //}

        //return base.ValidateClientAuthentication(context);

        // Resource owner password credentials does not provide a client ID.
        if (context.ClientId == null)
        {
            context.Validated();
        }

        return Task.FromResult<object>(null);
    }

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    {
        if (context.ClientId == _publicClientId)
        {
            Uri expectedRootUri = new Uri(context.Request.Uri, "/");

            if (expectedRootUri.AbsoluteUri == context.RedirectUri)
            {
                context.Validated();
            }
        }

        return Task.FromResult<object>(null);
    }

    public static AuthenticationProperties CreateProperties(string userName)
    {
        IDictionary<string, string> data = new Dictionary<string, string>
        {
            { "userName", userName }
        };
        return new AuthenticationProperties(data);
    }
}

上面的示例代码没有单独的客户端分类。它将所有用户视为单一类型的客户端。但是我在 cmets 中给出了一些示例代码,它们将指导您朝着正确的方向开始。

免责声明:我还不是这方面的专家,而且我的设置不同。我有一个带有 Owin 的现有 MVC 应用程序,我必须在它之上构建一个 webapi。这是我的原型代码,它完成了工作。您将不得不为您的生产代码改进它。玩得开心,祝你好运。

【讨论】:

  • 赞成您为帮助我所做的努力。如果我想跳过 ValidateClientRedirectUri 那我不想提供重定向 uri 并因此想跳过此验证。这可能吗?
  • 当我们处理授权端点时,必须在调用 context.validated 之前验证注册的“client_id”和“redirect_uri”。如果您不验证 redirect_uri,您的应用程序很容易受到网络钓鱼攻击。根据 MSDN 文档,它说,“应用程序必须实现这个调用。”有什么不想做的理由吗?
  • 那么你是说在隐式授权类型的情况下,用户只是通过 clientid 应该注册 redirecturi ?
  • 是的。重定向 URI 是应用的回调入口点。验证客户后,客户应该去哪里?这是由重定向 uri 定义的。
  • 但是我不能只返回 accesstoken 作为响应,然后在得到响应后客户端可以在他的 javascript 代码中配置重定向的位置。这有意义吗?
猜你喜欢
  • 2014-06-30
  • 2020-11-09
  • 1970-01-01
  • 1970-01-01
  • 2014-01-23
  • 2019-09-03
  • 1970-01-01
  • 2017-05-13
  • 2018-09-21
相关资源
最近更新 更多