我有一个带有web服务的web应用程序,客户端将使用我的web应用软件注册他们的应用程序。
现在,客户端将拥有SPA类型的应用程序或移动应用程序,他们将从他们的应用程序中使用我的Web服务。
因此,我将实现基于令牌的机制,以确保对我的端点的访问安全。
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查询验证用户名和密码的来源。我知道我可以做这样的事情:
制作一个自定义用户类,按照此处所述实现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) 此外,如果对于任何客户端,如果客户端不希望对其各自的应用程序进行身份验证,那么我将不进行用户名密码验证,而是简单地生成访问令牌,然后发送响应,这样在每个后续请求中,访问令牌都将传递给受访问保护的资源。你认为这有道理吗?
我从未见过任何访问令牌存储在数据库中的例子,而在数据库中存储访问令牌的问题是,我每次都必须调用数据库来验证每个端点的访问令牌。
更新:
我的Web服务引擎的用例是:
1) 支持多客户端应用程序。
2) 以令牌管理的形式为每个客户端应用程序管理用户会话。因此,在本文的大部分内容中,都是将accesstoken存储在标识中,并且该标识在[Authorize]属性中进行验证,在该属性中,accesstoken也进行了验证,并基于该属性允许用户访问受保护的资源。这就是我到目前为止的理解。
所以,如果我还将用户标识和用户上下文存储在支持多客户端应用程序的标识中是个好主意吗?
来自"7.1。Access Token Representation">in"OAuth和OpenID Connect的Full Scratch Implementor Talks About Findings">:
访问令牌应该如何表示?主要有两种方式。
作为一个无意义的随机字符串。与访问关联的信息令牌存储在授权服务器后面的数据库表中。
作为对访问令牌进行编码的结果的自包含字符串通过base64url或类似的信息。
博客中描述了这两种方法的优点和缺点。
如果访问令牌是随机字符串,则与访问令牌相关联的信息(用户ID、客户端ID、作用域、寿命等)被存储在数据库中,该数据库由已经发布访问令牌的授权服务器管理。
每当公开API的资源服务器接受来自客户端应用程序的API调用时,资源服务器都必须以某种方式获取有关访问令牌的信息。
如果资源服务器可以访问授权服务器管理的数据库(换句话说,如果资源服务器和授权服务器共享数据库),则资源服务器可以直接从数据库中获取访问令牌的信息。
否则,资源服务器必须对授权服务器进行API调用以获取信息。在这种情况下,可以预期授权服务器公开了一个符合RFC 7662(OAuth 2.0 Token Introspection)的API。请注意,某些实现可能提供比RFC 7662更友好的API(例如"4。反省访问令牌">)。
无论如何,如果服务器将有关访问令牌的信息缓存在内存缓存或其他适当位置,则资源服务器不必每次都进行DB调用(或对授权服务器进行API内省调用)。
顺便说一句,当您想要保护API时,您需要访问令牌。因此,您的系统不必支持OpenID Connect,这是关于如何请求和发布ID令牌的规范。您可能会感到困惑,因为支持OpenID Connect的服务器除了ID令牌外,还可以颁发访问令牌。请参阅"所有OpenID连接流的图表">了解支持OpenID连接的服务器存在什么问题。
最后,介绍了身份管理、用户身份验证和OAuth 2.0&OpenID Connect不一定必须以单片的方式实现。有关详细信息,请参阅"OAuth 2.0和OpenID Connect实现的新体系结构">。
不,您不需要将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);
}
}
上面的示例代码没有单独的客户端分类。它将把所有用户视为单一类型的客户端。但我在评论中给出了一些示例代码,这些代码将引导您朝着正确的方向开始。
免责声明:我还不是这方面的专家,我的设置也不一样。我有一个现有的MVC应用程序和Owin,我必须在上面构建一个webapi。这是我的原型代码,它完成了任务。您必须为您的生产代码改进它。祝你玩得开心,好运。