这个问题很难回答,而且由于需要包含的内容很多,所以我尝试将一些问题分开。
Claims
有两种方法可以将声明添加到 ClaimsIdentity。
- 将声明保留在存储中(数据库中的表 AspNetUserClaims、AspNetRoleClaims)。要添加声明,请使用 UserManager.AddClaim 或 RoleManager.AddClaim。角色 (AspNetUserRoles) 很特殊,因为它们也被算作声明。
- 在代码中添加声明。您可以从 ApplicationUser 类(对于 IdentityUser 的扩展属性有用)或在流程中添加声明。
请注意区别!虽然在所有情况下都称为 AddClaim,但第一个变体将声明添加到存储中,而第二个变体将声明直接添加到 ClaimsIdentity。
那么如何将持久声明添加到 ClaimsIdentity 中呢?这是自动完成的!
顺便说一句,您可以使用属性扩展 IdentityUser,但也可以将用户声明添加到存储中。在这两种情况下,声明都会添加到 ClaimsIdentity 中。必须在 ApplicationUser.GenerateUserIdentityAsync 中添加扩展属性:
public class ApplicationUser : IdentityUser
{
public string DisplayName { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("DisplayName", DisplayName));
return userIdentity;
}
}
Flow
在发出新的 access_token 之前,服务器必须验证用户。可能有一些原因导致服务器无法颁发新的access_token。还必须考虑更改的配置。有两个提供者可以进行此设置。 access_token 提供程序和refresh_token 提供程序。
当客户端向令牌端点发出请求(grant_type = *)时,首先执行 AccessTokenProvider.ValidateClientAuthentication。如果您正在使用 client_credentials 那么您可以在这里做一些事情。但对于当前的流量,我们假设context.Validated();
提供商支持各种流程。你可以在这里读到它:https://msdn.microsoft.com/en-us/library/microsoft.owin.security.oauth.oauthauthorizationserverprovider(v=vs.113).aspx https://msdn.microsoft.com/en-us/library/microsoft.owin.security.oauth.oauthauthorizationserverprovider(v=vs.113).aspx
该提供商是作为选择加入而构建的。如果您不重写某些方法,则访问将被拒绝。
访问令牌
要获取访问令牌,必须发送凭据。对于这个例子,我将假设“grant_type = password”。在 AccessTokenProvider.GrantResourceOwnerCredentials 中,检查凭据、设置 ClaimsIdentity 并颁发令牌。
为了将refresh_token添加到票证中,我们需要重写AccessTokenProvider.GrantRefreshToken。这里你有两个选择:拒绝令牌。因为refresh_token被撤销或者由于其他原因不允许用户再使用刷新令牌。或者设置一个新的 ClaimsIdentity 来为票证生成一个新的 access_token。
class AccessTokenProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
// Reject token: context.Rejected(); Or:
// chance to change authentication ticket for refresh token requests
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var appUser = await userManager.FindByNameAsync(context.Ticket.Identity.Name);
var oAuthIdentity = await appUser.GenerateUserIdentityAsync(userManager);
var newTicket = new AuthenticationTicket(oAuthIdentity, context.Ticket.Properties);
context.Validated(newTicket);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var appUser = await userManager.FindAsync(context.UserName, context.Password);
if (appUser == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var propertyDictionary = new Dictionary<string, string> { { "userName", appUser.UserName } };
var properties = new AuthenticationProperties(propertyDictionary);
var oAuthIdentity = await appUser.GenerateUserIdentityAsync(userManager);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
// Token is validated.
context.Validated(ticket);
}
}
如果上下文有经过验证的票证,则调用 RefreshTokenProvider。在 Create 方法中,您可以设置过期时间并选择将刷新令牌添加到票证中。当前令牌尚未过期时,请勿发行新令牌。否则用户可能永远不必再次登录!
如果刷新令牌以某种方式保留下来,您始终可以添加它。或者您可以仅在登录时添加新的刷新令牌。用户已被识别,因此“旧”refresh_token 不再重要,因为它会在新的refresh_token 之前过期。如果您只想使用一个活动的 refesh_token,那么您必须保留它。
class RefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
var form = context.Request.ReadFormAsync().Result;
var grantType = form.GetValues("grant_type");
// do not issue a new refresh_token if a refresh_token was used.
if (grantType[0] != "refresh_token")
{
// 35 days.
int expire = 35 * 24 * 60 * 60;
context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));
// Add the refresh_token to the ticket.
context.SetToken(context.SerializeTicket());
}
base.Create(context);
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
base.Receive(context);
}
}
这只是刷新令牌流程的简单实现,尚未完成或经过测试。这只是为了给您一些关于实现刷新令牌流程的想法。正如您所看到的,向 ClaimsIdentity 添加声明并不难。我没有添加维护持久声明的代码。重要的是坚持不懈的主张是自动地 added!
请注意,我在使用refresh_token刷新access_token时重置了ClaimsIdentity(新票证)。这将创建一个具有当前声明状态的新 ClaimsIdentity。
我将以最后一句话结束。我说的是角色就是主张。您可能期望 User.IsInRole 检查 AspNetUserRoles 表。但事实并非如此。由于角色是声明,因此它会检查声明集合中是否有可用的角色。