【发布时间】:2016-07-06 17:51:59
【问题描述】:
我按照文章实现了一个授权服务器
我已经实现了它几乎与文章一样,但我看不到身份验证服务器如何知道刷新令牌已过期。事实上,我已经测试过,当呈现过期的刷新令牌时,服务器不会授予访问令牌,但我在我的身份验证服务器中看不到这个逻辑。此外,当我使用过期的刷新令牌请求访问令牌时,我的 OAuthAuthorizationServerProvider 子类根本不会被调用,实际上,当我使用过期的访问令牌请求新的访问令牌时,我的 OAuthAuthorizationServerProvider 派生类或 IAuthenticationTokenProvider 实现中的任何方法都不会被调用刷新令牌。任何帮助表示赞赏。这是我所拥有的
public class SmartCardOAuthAuthenticationTokenProvider : IAuthenticationTokenProvider
{
private IDataAccessFactoryFactory _producesFactoryThatProducesIAuthenticateDataAccess;
public SmartCardOAuthAuthenticationTokenProvider(IDataAccessFactoryFactory producesFactoryThatProducesIAuthenticateDataAccess)
{
_producesFactoryThatProducesIAuthenticateDataAccess = producesFactoryThatProducesIAuthenticateDataAccess;
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
var refreshTokenId = Guid.NewGuid().ToString("n");
using(IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory())//using (IAuthorizationDataAccess _repo = new AuthRepository())
{
IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var token = new RefreshToken()
{
RefreshTokenId = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshTokenAsync(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
string hashedTokenId = Helper.GetHash(context.Token);
//using (IAuthorizationDataAccess _repo = new AuthRepository())
//{
using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository())
{
IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();
var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId);
if (refreshToken != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId);
}
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
public class SmartCardOAuthAuthorizationProvider : OAuthAuthorizationServerProvider
{
private IDataAccessFactoryFactory _producesFactoryThatProducesIAuthenticateDataAccess;
public SmartCardOAuthAuthorizationProvider(IDataAccessFactoryFactory producesFactoryThatProducesIAuthenticateDataAccess)
{
_producesFactoryThatProducesIAuthenticateDataAccess = producesFactoryThatProducesIAuthenticateDataAccess;
}
public override System.Threading.Tasks.Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
if (context.UserName != "onlyOneHardCodedUserForSakeOfExploration" && context.Password!="thePassword")
{
context.SetError("invalid_grant", "the user name or password is incorrect");
return Task.FromResult<object>(null); ;
}
ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "PostVSDebugBreakModeEnterEventArgs"));
identity.AddClaim(new Claim(DatawareClaimTypes.SmartCardUserId.ToString(), 1.ToString()));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
//ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddDays(2));
context.Validated(ticket);
return Task.FromResult<object>(null);
}
public override System.Threading.Tasks.Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
Client client = null;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (context.ClientId == null)
{
//Remove the comments from the below line context.SetError, and invalidate context
//if you want to force sending clientId/secrects once obtain access tokens.
//context.Validated();
context.SetError("invalid_clientId", "ClientId should be sent.");
return Task.FromResult<object>(null);
}
string[] clientIdClientUnique = context.ClientId.Split(':');
if (clientIdClientUnique == null || clientIdClientUnique.Length <= 1)
{
context.SetError("invalid_client_unique");
return Task.FromResult<object>(null);
}
clientId = clientIdClientUnique[0];
string clientUnique = clientIdClientUnique[1];
using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository())
{
IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();
client = _repo.FindClient(clientId);//new Client { Active = true, AllowedOrigin = "*", ApplicationType = ApplicationTypes.DesktopClient, ClientId = context.ClientId, Name = "Visual Studio Event Source", RefreshTokenLifeTimeInMinutes = 14400, Secret = Helper.GetHash(clientSecret) };//_repo.FindClient(context.ClientId);
}
if (client == null)
{
//context.SetError("invalid_client_unique");
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
if (string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("invalid_clientId", "Client secret should be sent.");
return Task.FromResult<object>(null);
}
else
{
if (client.Secret != Helper.GetHash(clientSecret))
{
context.SetError("invalid_clientId", "Client secret is invalid.");
return Task.FromResult<object>(null);
}
}
if (!client.Active)
{
context.SetError("invalid_clientId", "Client is inactive.");
return Task.FromResult<object>(null);
}
context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTimeInMinutes.ToString());
context.Validated();
return Task.FromResult<object>(null);
}
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
if (originalClient != currentClient)
{
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return Task.FromResult<object>(null);
}
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
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);
}
}
再次,当我提出请求时
并且刷新令牌已过期,上述类\方法均未命中。此外,传递给 API 的刷新令牌只不过是我在SmartCardOAuthAuthenticationTokenProvider.CreateAsync 中生成的 GUID,它不包含有关到期的信息。如果通过刷新请求访问时上述方法均未命中,并且在通过刷新请求新访问令牌时请求没有传递(看起来什么都没有),那么服务器如何知道刷新令牌已过期?
在我看来就像魔法一样。
更新 1 - 添加启动代码
public static class OwinStartUpConfig
{
public static void Configure(HttpConfiguration configFromOwinStartup)
{
configFromOwinStartup.MapHttpAttributeRoutes();
configFromOwinStartup.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });
var jsonFormatter = configFromOwinStartup.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
RegiserDependencies(configFromOwinStartup);
}
public static void RegiserDependencies(HttpConfiguration configFromOwinStartup)
{
string connectionStringForSmartCardDbCntx = System.Configuration.ConfigurationManager.ConnectionStrings["SmartCardDataContext"].ConnectionString;
string projectNameWhenNewProjectCreatedDueToNoMatch = System.Configuration.ConfigurationManager.AppSettings["ProjectNameWhenNewProjectCreatedDueToNoMatch"];
Autofac.ContainerBuilder builderUsedToRegisterDependencies = new Autofac.ContainerBuilder();
builderUsedToRegisterDependencies.RegisterType<DataAccessFactoryFactoryEf>()
.As<IDataAccessFactoryFactory>()
.WithParameter(new TypedParameter(typeof(string), connectionStringForSmartCardDbCntx));
builderUsedToRegisterDependencies.Register(
c =>
new List<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>
{
new MatchVSProjectWithMostRecentActivity<VSDebugBreakModeEnterActivity>(),
new MatchVSSolutionWithMostRecentActivityActivityMatch<VSDebugBreakModeEnterActivity>(),
new MatchMostRecentActivityMatch<VSDebugBreakModeEnterActivity>(),
new MatchToNewProjectActivityMatch<VSDebugBreakModeEnterActivity>(projectNameWhenNewProjectCreatedDueToNoMatch)
}
).As<IEnumerable<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>>();
builderUsedToRegisterDependencies
.RegisterType<MatchDontGiveUpActivityMatch<VSDebugBreakModeEnterActivity>>()
//.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IEnumerable<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>>("VSDebugBreakModeEnterActivityMatchers"))
.As<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>();
builderUsedToRegisterDependencies
.RegisterType<VSDebugBreakModeEnterEventArgsEventSaver>()
.Named<ISaveVisualStudioEvents>("VSDebugBreakModeEnterSaver");
builderUsedToRegisterDependencies
.RegisterType<VSDebugBreakModeEnterEventArgsController>()
.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<ISaveVisualStudioEvents>("VSDebugBreakModeEnterSaver"));
var container = builderUsedToRegisterDependencies.Build();
configFromOwinStartup.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
}
public static class OAuthStartupConfig
{
internal static void Configure(IAppBuilder app)
{
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), //TimeSpan.FromDays(1),
Provider = new SmartCardOAuthAuthorizationProvider(new AuthorizationDataAccessFactoryFactory()),
RefreshTokenProvider = new SmartCardOAuthAuthenticationTokenProvider(new AuthorizationDataAccessFactoryFactory())
};
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
更新 - 响应 cmets
上述响应中的访问令牌包含声明信息和访问令牌的到期信息。所有这些信息都由服务器序列化到上面的访问令牌中。因此,我可以看到如何检查访问令牌的到期时间。但是,刷新令牌呢?当使用刷新令牌请求访问令牌时:
在上面的请求中,刷新令牌是好的,因此通过刷新令牌对访问令牌的请求被授予,但如果刷新令牌已过期,OAuthAuthorizationServerProvider 将如何检查刷新令牌是否过期?
再次,我检查了上面提供的代码确实检查了过期的刷新令牌,并且如果刷新令牌已过期,则不会授予访问令牌,但它是如何知道的?我没有写任何东西来检查我的 OAuthAuthorizationServerProvider 派生类中的刷新令牌到期。那怎么办????
更新
奇迹发生在 IAuthenticationTokenProvider 实现的 ReceiveAsync 方法中。
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
string hashedTokenId = Helper.GetHash(context.Token);
//using (IAuthorizationDataAccess _repo = new AuthRepository())
//{
using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository())
{
IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();
var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId);
if (refreshToken != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId);
}
}
}
尤其是context.DeserializeTicket(refreshToken.ProtectedTicket); 这行代码很神奇。这将设置 context.Ticket 属性。在 ReceiveAsync 方法完成后。无需手动检查任何东西 OWIN,在幕后某处,知道票已过期。
【问题讨论】:
-
您能否显示将您的提供程序连接到 OAuthServerOptions 的代码?我怀疑您在创建服务器选项时没有指向刷新令牌提供程序。
标签: asp.net-mvc asp.net-web-api owin