当前位置: 首页 > 知识库问答 >
问题:

在两个 Web API 项目之间共享 OAuth 令牌

黄修永
2023-03-14

我已经创建了一个带有OAuth令牌身份验证的Web API应用程序。当令牌服务器与服务在同一个应用程序上运行时,这没有问题。但是,我想将授权服务移动到它自己的应用程序(VS项目)中,并在我正在处理的几个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);
    }

共有2个答案

凌运恒
2023-03-14

如果您不想使用MahineKey,这就有点棘手了,我希望它可以跨不同的服务器和用户使用每个服务器唯一的MahineKey

跨Asp.NET核心和框架的数据保护提供商(生成密码重置链接)

我开始在DataProtectionTokenProvider的帮助下实现我自己的ValidateAsync.cs用于 ASP.NET 核心身份。这门课真的帮助我找到了解决方案

https://github . com/aspnet/Identity/blob/master/src/Identity/dataprotectiontokenprovider . cs

使用< code > data protectortokenprovider时,令牌从< code>SecurityStamp生成

System.Security.Cryptography.ProtectedData.Protect(userData, entropy, DataProtectionScope.CurrentUser);

假设如果所有站点都使用相同的应用程序池标识,则它也可以正常工作。它也可以是带有保护描述符“LOCAL=user”数据保护提供程序

new DataProtectionProvider("LOCAL=user")

https://learn.microsoft.com/en-us/previous-versions/aspnet/dn613280(v=vs.108)

https://learn . Microsoft . com/en-us/dot net/API/system . security . cryptography . data protector?view=netframework-4.7.2

https://learn.microsoft.com/en-us/uwp/api/windows.security.cryptography.dataprotection.dataprotectionprovider

当阅读<code>DpapiDataProtectionProvider</code>(DPAPI代表数据保护应用程序编程接口)时,描述如下:

用于提供从data protection API派生的数据保护服务。当应用程序不由ASP托管时,它是数据保护的最佳选择。NET和所有进程都以相同的域标识运行。

创建方法用途描述为:

用于确保受保护数据的额外熵只能出于正确的目的不受保护。

https://learn . Microsoft . com/en-us/previous-versions/aspnet/dn 253784(v = vs . 113)

考虑到这些信息,我认为在尝试使用Microsoft提供的普通类时没有任何进展。

我最终实现了自己的<code>IUserTokenProvider

我选择用证书实现< code>IDataProtector,因为我可以相对容易地在服务器之间传输这些证书。我也可以使用运行网站的< code >应用程序池标识从< code>X509Store中获取它,这样应用程序本身就不会存储任何密钥。

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);

关于普通Data保护令牌提供者如何使用的更多信息

https://stackoverflow.com/a/53390287/3850405

亢嘉茂
2023-03-14

只是在自己研究这个时偶然发现了这个 Q。TL;DR 的答案是令牌是使用 machine.config 文件中的 machineKey 属性生成的:如果要在多个服务器上托管,则需要覆盖它。

MachineKey可以在web.config中重写:

<system.web>
<machineKey validationKey="VALUE GOES HERE" 
            decryptionKey="VALUE GOES HERE" 
            validation="SHA1" 
            decryption="AES"/>
</system.web>

计算机密钥应在本地生成 - 使用在线服务不安全。有关生成密钥的知识库文章

这一切的原始裁判http://bitoftech.net/2014/09/24/decouple-owin-authorization-server-resource-server-oauth-2-0-web-api

 类似资料:
  • 问题内容: 我们正在考虑将ci从jenkins迁移到gitlab。我们有几个项目具有相同的构建工作流程。现在,我们使用一个定义了管道的共享库,而项目内部的jenkinsfile仅调用在共享库中定义的定义实际管道的方法。因此,仅需在单个点上进行更改即可影响多个项目。 我想知道gitlab ci是否也可以做到?据我发现,无法在存储库外部定义gitlab- ci.yml。还有另一种定义管道并与几个项目共

  • 问题内容: 有没有办法在Netbeans和Eclipse之间共享相同的* .java文件? 问题答案: 您可以将eclipse项目导入netbeans, 或者,您可以从现有资源创建Eclipse项目。

  • 在Java中,对于两个JVM(运行在同一台物理机器上),是否有办法使用/共享相同的内存地址空间?假设JVM-1中的生产者将消息放在特定的预定义内存位置,如果JVM-2上的消费者知道要查看哪个内存位置,那么它是否可以检索消息?

  • 问题内容: 我有一些称为的数据,该数据位于三个孩子的父对象的范围内: 在这三个指令之间共享的最佳方法是什么?选项包括: 使用隔离的范围传递三遍,从而跨四个范围复制它 让子指示继承父范围,并找到,或在 把上并注入到这一点的子指示 还是有另一种更好的方法? 问题答案: 您可以创建一个工厂,该工厂可以传递给每个指令或控制器。这样可以确保在任何给定时间只有一个数组实例。编辑:这里唯一的陷阱是确保您在指令作

  • 问题内容: 我想在以下两个指令之间共享: 在HTML中,我有: 我创建了具有隔离范围的名为“ directive1”的指令,并将名称“ table”分配给该属性。我无法在其他指令中访问此作用域属性。 那么,如何访问另一个指令的作用域呢? 问题答案: 您可以对需要跨指令同步的项目执行操作。 或者,您可以将对象传递给指令1隔离范围,该范围将充当通信机制。在此对象上,如果更改子属性(如),则会影响父范围

  • 问题内容: 我有两个线程。可以调用修改变量的类的update方法。另一个调用读取该变量的类的update方法。只有一个线程写入,一个(或多个)线程读取该变量。由于我是多线程技术的新手,我需要在并发方面做什么? 谢谢, 问题答案: 如果有且仅有一个写线程,你可以逃脱使得它。否则,请查看答案。 仅在只有一个写线程的情况下才起作用,因为只有一个写线程,因此它始终具有的正确值。