webapi中基于令牌的实现,以保护端点

时间:2017-11-03 12:44:01

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

我正在使用带有Web服务的Web应用程序,客户端将使用我的Web应用程序注册其应用程序。

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

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

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

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

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

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

Id(autogenerated)   accesstoken    userid   clientid    createdat    expiresat

因此,在用户进行身份验证后,现在如果用户想要访问任何受保护的资源,则用户需要在后续的标头调用中传递此访问令牌。

所以我会做什么,我将从头部获取访问令牌,然后验证对该数据库的accessstoken,然后允许访问我的受保护资源,其他明智的用户将获得授权。

我见过很多与此相关的事情,所以基本上这是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>

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

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,然后发送响应,然后在每个后续请求中,访问令牌将被传递以访问受保护的资源。您认为这有意义吗?

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

更新:

我的webservice引擎的用例是:

1)支持多个客户端应用程序。

2)以每个客户端应用程序的令牌管理的形式管理用户会话。所以这里的大多数文章都是在身份中存储accessstoken,并且在[Authorize]属性中验证了身份,其中也验证了accesstoken,并且基于该用户被允许访问受保护的资源。这是我的理解,直到现在。

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

2 个答案:

答案 0 :(得分:2)

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

身份对Oauth的自我支持。你必须正确配置它。您可以在Startup.Auth.cs中设置 OAuthAuthorizationServerOptions 的配置。示例代码如下。我试图在代码中的评论中回答你的大部分问题。

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);
    }
}

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

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

答案 1 :(得分:1)

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

  

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

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

  2.   
  3. 作为自包含字符串,它是对访问令牌进行编码的结果   base64url或类似的信息。

  4.   

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

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

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

如果资源服务器可以访问授权服务器管理的数据库(换句话说,如果资源服务器和授权服务器共享数据库),资源服务器可以直接从数据库获取有关访问令牌的信息。

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

无论如何,如果服务器在内存缓存或其他适当的位置缓存有关访问令牌的信息,那么每次资源服务器都不一定要进行数据库调用(或对授权服务器进行内省API调用)。 p>

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

最后,身份管理,用户身份验证和OAuth 2.0&amp; OpenID Connect不一定必须以单片方式实现。有关详细信息,请参阅New Architecture of OAuth 2.0 and OpenID Connect Implementation