我创建了一个带有OAuth令牌身份验证的Web API应用程序。当令牌服务器在与服务相同的应用程序上运行时,这没有问题。但是,我想将授权服务移动到自己的应用程序(VS项目)中,并将其用于我正在处理的几个Web API项目中。但是,当我将授权逻辑隔离到它自己的项目中时,原始服务不再将生成的令牌视为有效。我的问题是,一个Web API项目是否有可能为另一个Web API项目生成令牌以进行验证?这是我的授权服务和原始服务的OWIN启动代码
验证服务:
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseWebApi(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
}
private void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
原始服务:
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
HttpConfiguration config = new HttpConfiguration();
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
var oauthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(oauthBearerOptions);
}
答案 0 :(得分:6)
在我自己研究这个问题时偶然发现了这个问题。 TL; DR答案是使用machine.config文件中的machineKey属性生成令牌:如果要在多个服务器上托管,则需要覆盖它。
可以在web.config中重写MachineKey:
<system.web>
<machineKey validationKey="VALUE GOES HERE"
decryptionKey="VALUE GOES HERE"
validation="SHA1"
decryption="AES"/>
</system.web>
应在本地生成计算机密钥 - 使用在线服务不安全。 KB Article for generating keys
这里所有这些的原始参考http://bitoftech.net/2014/09/24/decouple-owin-authorization-server-resource-server-oauth-2-0-web-api
答案 1 :(得分:0)
如果您不想使用MachineKey
,这真是有点棘手,我希望它可以跨不同的服务器和用户使用,每台服务器具有唯一的MachineKey
。
Data Protection provider across Asp.NET Core and Framework (generate password reset link)
我首先在ValidateAsync
的帮助下实现自己的DataProtectionTokenProvider.cs
,以获得ASP.NET Core Identity。该课程确实帮助我找到了解决方案。
https://github.com/aspnet/Identity/blob/master/src/Identity/DataProtectionTokenProvider.cs
使用SecurityStamp
时,会从DataProtectorTokenProvider<TUser, TKey>
生成令牌,但是很难深入挖掘。如果在单个服务器上更改Application Pool Identity
时验证将失败,则表明实际的保护机制将如下所示:
System.Security.Cryptography.ProtectedData.Protect(userData, entropy, DataProtectionScope.CurrentUser);
考虑到所有站点都使用相同的Application Pool Identity
也是可行的。也可以是DataProtectionProvider
和protectionDescriptor
"LOCAL=user"
。
new DataProtectionProvider("LOCAL=user")
https://docs.microsoft.com/en-us/previous-versions/aspnet/dn613280(v%3dvs.108)
在阅读有关DpapiDataProtectionProvider
(DPAPI代表数据保护应用程序编程接口)时,描述如下:
用于提供源自以下内容的数据保护服务: 数据保护API。当您进行数据保护时,它是最佳的选择 应用程序不是由ASP.NET托管,并且所有进程都以 相同的域标识。
Create方法的用途描述为:
用于确保受保护数据的附加熵只能是 出于正确目的未受保护。
https://docs.microsoft.com/en-us/previous-versions/aspnet/dn253784(v%3dvs.113)
鉴于此信息,我在尝试使用Microsoft
提供的普通类方面没有前进的方向。
我最终实现了自己的IUserTokenProvider<TUser, TKey>
,IDataProtectionProvider
和IDataProtector
来代替它。
我选择用证书实现IDataProtector
,因为我可以相对容易地在服务器之间传输证书。我也可以通过运行网站的X509Store
从Application Pool Identity
进行选择,因此应用程序本身不会存储任何密钥。
public class CertificateProtectorTokenProvider<TUser, TKey> : IUserTokenProvider<TUser, TKey>
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
private IDataProtector protector;
public CertificateProtectorTokenProvider(IDataProtector protector)
{
this.protector = protector;
}
public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser, TKey> manager, TUser user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var ms = new MemoryStream();
using (var writer = new BinaryWriter(ms, new UTF8Encoding(false, true), true))
{
writer.Write(DateTimeOffset.UtcNow.UtcTicks);
writer.Write(Convert.ToInt32(user.Id));
writer.Write(purpose ?? "");
string stamp = null;
if (manager.SupportsUserSecurityStamp)
{
stamp = await manager.GetSecurityStampAsync(user.Id);
}
writer.Write(stamp ?? "");
}
var protectedBytes = protector.Protect(ms.ToArray());
return Convert.ToBase64String(protectedBytes);
}
public virtual async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser, TKey> manager, TUser user)
{
try
{
var unprotectedData = protector.Unprotect(Convert.FromBase64String(token));
var ms = new MemoryStream(unprotectedData);
using (var reader = new BinaryReader(ms, new UTF8Encoding(false, true), true))
{
var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero);
var expirationTime = creationTime + TimeSpan.FromDays(1);
if (expirationTime < DateTimeOffset.UtcNow)
{
return false;
}
var userId = reader.ReadInt32();
var actualUser = await manager.FindByIdAsync(user.Id);
var actualUserId = Convert.ToInt32(actualUser.Id);
if (userId != actualUserId)
{
return false;
}
var purp = reader.ReadString();
if (!string.Equals(purp, purpose))
{
return false;
}
var stamp = reader.ReadString();
if (reader.PeekChar() != -1)
{
return false;
}
if (manager.SupportsUserSecurityStamp)
{
return stamp == await manager.GetSecurityStampAsync(user.Id);
}
return stamp == "";
}
}
catch (Exception e)
{
// Do not leak exception
}
return false;
}
public Task NotifyAsync(string token, UserManager<TUser, TKey> manager, TUser user)
{
throw new NotImplementedException();
}
public Task<bool> IsValidProviderForUserAsync(UserManager<TUser, TKey> manager, TUser user)
{
throw new NotImplementedException();
}
}
public class CertificateProtectionProvider : IDataProtectionProvider
{
public IDataProtector Create(params string[] purposes)
{
return new CertificateDataProtector(purposes);
}
}
public class CertificateDataProtector : IDataProtector
{
private readonly string[] _purposes;
private X509Certificate2 cert;
public CertificateDataProtector(string[] purposes)
{
_purposes = purposes;
X509Store store = null;
store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certificateThumbprint = ConfigurationManager.AppSettings["CertificateThumbprint"].ToUpper();
cert = store.Certificates.Cast<X509Certificate2>()
.FirstOrDefault(x => x.GetCertHashString()
.Equals(certificateThumbprint, StringComparison.InvariantCultureIgnoreCase));
}
public byte[] Protect(byte[] userData)
{
using (RSA rsa = cert.GetRSAPrivateKey())
{
// OAEP allows for multiple hashing algorithms, what was formermly just "OAEP" is
// now OAEP-SHA1.
return rsa.Encrypt(userData, RSAEncryptionPadding.OaepSHA1);
}
}
public byte[] Unprotect(byte[] protectedData)
{
// GetRSAPrivateKey returns an object with an independent lifetime, so it should be
// handled via a using statement.
using (RSA rsa = cert.GetRSAPrivateKey())
{
return rsa.Decrypt(protectedData, RSAEncryptionPadding.OaepSHA1);
}
}
}
客户网站重置:
var provider = new CertificateProtectionProvider();
var protector = provider.Create("ResetPassword");
userManager.UserTokenProvider = new CertificateProtectorTokenProvider<ApplicationUser, int>(protector);
if (!await userManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
{
return GetErrorResult(IdentityResult.Failed());
}
var result = await userManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);
后台:
var createdUser = userManager.FindByEmail(newUser.Email);
var provider = new CertificateProtectionProvider();
var protector = provider.Create("ResetPassword");
userManager.UserTokenProvider = new CertificateProtectorTokenProvider<ApplicationUser, int>(protector);
var token = userManager.GeneratePasswordResetToken(createdUser.Id);
有关常规DataProtectorTokenProvider<TUser, TKey>
的工作原理的更多信息: