首页 文章

基于令牌的webapi实现以保护 endpoints

提问于
浏览
2

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

现在客户端将有 SPA or mobile apps 类型的应用程序,他们将从他们的应用程序中使用我的web服务 .

所以我将实现基于令牌的机制来保护对我的 endpoints 的访问 .

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查询 . 我知道我可以这样做:

创建一个实现IUser的自定义用户类,如描述here定义实现的自定义用户存储

public class UserStoreService 
     : IUserStore<CustomUser>, IUserPasswordStore<CustomUser>

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

4)我们已经为用户提供了一个固定的 endpoints ,客户端可以通过该 endpoints 创建管理员,因此我将使用我的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,然后发送响应,然后在每个后续请求访问令牌将被传递以访问受保护的资源 . 您认为这有意义吗?

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

Update :

我的webservice引擎的用例是:

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

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

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

2 回答

  • 1

    不,您不需要将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 . 这是我的原型代码,它完成了这项工作 . 您必须为您的 生产环境 代码改进它 . 玩得开心,祝你好运 .

  • 2

    从“7.1 . 访问令牌表示“in”Full-Scratch Implementor of OAuth and OpenID Connect Talks About Findings“:

    如何表示访问令牌?有两种主要方式 . 作为无意义的随机字符串 . 与访问令牌相关联的信息存储在授权服务器后面的数据库表中 . 作为一个自包含的字符串,它是通过base64url或类似的东西对访问令牌信息进行编码的结果 .

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

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

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

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

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

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

    顺便说一下,当你想要保护API时你需要的是 access tokens . 因此,您的系统不必支持OpenID Connect,这是关于如何请求和发布 ID tokens 的规范 . 您可能会感到困惑,因为除了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” .

相关问题