MVC6 从另一个网站解密表单身份验证 cookie

MVC6 Decrypting a forms authentication cookie from another website

我有一个 webforms 网站正在调用我们正在开发的新 MVC6 网站。用户将像往常一样使用表单身份验证在 webforms 网站上登录,然后被重定向到新的 MVC6 网站。我知道在 MVC6 中我应该使用 Cookie 身份验证但无法使用它来解密 cookie。我怀疑是因为 web.config 和 machinekey 的变化,但我真的卡住了。

这是我所做的。

我已经设置cookie认证如下

        app.UseCookieAuthentication(options =>
        {
            options.CookieName = "MyWebformsCookie";
            options.AutomaticAuthenticate = true;
            options.AuthenticationScheme = "Cookies";
            options.TicketDataFormat = new MySecureDataFormat();
            options.DataProtectionProvider = new MyDataProtectionProvider();
            //options.CookieDomain = "localhost";
        });

class如下

public class MySecureDataFormat : ISecureDataFormat<AuthenticationTicket>
{
    public string Protect(AuthenticationTicket data)
    {
        return string.Empty;
    }

    public string Protect(AuthenticationTicket data, string purpose)
    {
        return string.Empty;
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        return null;
    }

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        var ticket = FormsAuthentication.Decrypt(protectedText);
        return null;
    }
}

正在读取 cookie,并调用了 Unprotect 方法,但随后它在 FormsAuthentication.Decrypt 方法上出错,错误

类型 'System.Web.HttpException' 的异常发生在 System.Web.dll 但未在用户代码中处理

附加信息:无法验证数据。

Stack = at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length, Boolean useValidationSymAlgo, Boolean useLegacyMode, IVType ivType, Boolean signData) at System.Web.Security.FormsAuthentication.Decrypt(String encryptedTicket) at WebApplication.Mvc.MySecureDataFormat.Unprotect(String protectedText, String purpose) in C:\SVNCode\GlobalConnectV2\WebApplication.Mvc\Startup.cs:line 153
at Microsoft.AspNet.Authentication.Cookies.CookieAuthenticationHandler.d__9.MoveNext()

所以这让我相信它没有读取机器密钥。我在 wwwroot 文件夹

的 web.config 中有这个
<configuration>
  <system.webServer>
    ...
  </system.webServer>
  <system.web>
    <machineKey compatibilityMode="Framework20SP2" validation="SHA1" decryption="AES" validationKey="mykey" decryptionKey="dec" />
  </system.web>
</configuration>

这适用于早期的 MVC 应用程序,但猜测在 MVC6 中发生了一些变化。我也试过以下但没有运气

    services.ConfigureDataProtection(configure =>
    {
        configure.UseCryptographicAlgorithms(new Microsoft.AspNet.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptionOptions()
        {
            EncryptionAlgorithm = Microsoft.AspNet.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_CBC,
            ValidationAlgorithm = Microsoft.AspNet.DataProtection.AuthenticatedEncryption.ValidationAlgorithm.HMACSHA256
        });

    });

有什么建议吗?

当您对用户进行身份验证时,将身份验证 cookie 的域设置为二级域,即 parent.com(您的 asp.net 方)。每个子域(MVC 站点)都将根据请求接收父域的 cookie,因此可以对每个子域进行身份验证,因为您将有一个共享的身份验证 cookie 可以使用。

验证码:

System.Web.HttpCookie authcookie = System.Web.Security.FormsAuthentication.GetAuthCookie(UserName, False);
authcookie.Domain = "parent.com";
HttpResponse.AppendCookie(authcookie);
HttpResponse.Redirect(System.Web.Security.FormsAuthentication.GetRedirectUrl(UserName, 
                                                                   False));

参考问题Forms Authentication across Sub-Domains

ASP.NET webforms and MVC authentication sharing via cookie

有一个名为 Replacing machineKeywhole section of documentation on docs.asp.net

主要与软件包有关Microsoft.AspNet.DataProtection.SystemWeb

然后您将能够在代码或配置中设置您的密钥,并能够直接从 读取这些 cookie。

这是一个关于如何执行此操作的代码示例(来自文档):

public override void ConfigureServices(IServiceCollection services)
{
    services.ConfigureDataProtection(configure =>
    {
        configure.SetApplicationName("my-app");
        configure.PersistKeysToFileSystem(new DirectoryInfo(@"\server\share\myapp-keys\"));
        configure.ProtectKeysWithDpapi();
    });
}

当然,您必须在依赖项列表中添加前面提到的包,这可能会迫使您使用 Windows(不确定,但它会在 CoreClr 中编译)。

这不适用于现有的机器密钥,但它会生成一个新的集合,让您可以从那里开始。

我对在 ASP.NET 5 应用程序中使用 FormsAuthentication.Decrypt() 感到不高兴。

最后我根据可用的文档编写了一个解密例程,还查看了 Microsoft 为系统 Web 提供的参考源代码。

解密使用 SHA1 验证和 AES 加密的表单身份验证 cookie 所需的 类 可以在我的 GIST 上找到:https://gist.github.com/dazinator/0cdb8e1fbf81d3ed5d44

有了这些之后,您就可以像以前一样创建自定义 TicketFormat:

public class FormsAuthCookieTicketFormat : ISecureDataFormat<AuthenticationTicket>
{

    private LegacyFormsAuthenticationTicketEncryptor _Encryptor;
    private Sha1HashProvider _HashProvider;

    public FormsAuthCookieTicketFormat(string decryptionKey, string validationKey)
    {
        _Encryptor = new LegacyFormsAuthenticationTicketEncryptor(decryptionKey);
        _HashProvider = new Sha1HashProvider(validationKey);
    }

    public string Protect(AuthenticationTicket data)
    {
        throw new NotImplementedException();            
    }

    public string Protect(AuthenticationTicket data, string purpose)
    {
        throw new NotImplementedException();
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        throw new NotImplementedException();
    }

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        var ticket = _Encryptor.DecryptCookie(protectedText, _HashProvider);

        var identity = new ClaimsIdentity("MyCookie");
        identity.AddClaim(new Claim(ClaimTypes.Name, ticket.Name));
        identity.AddClaim(new Claim(ClaimTypes.IsPersistent, ticket.IsPersistent.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.Expired, ticket.Expired.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.Expiration, ticket.Expiration.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.CookiePath, ticket.CookiePath));
        identity.AddClaim(new Claim(ClaimTypes.Version, ticket.Version.ToString()));           

        // Add some additional properties to the authentication ticket.
        var props = new AuthenticationProperties();
        props.ExpiresUtc = ticket.Expiration.ToUniversalTime();
        props.IsPersistent = ticket.IsPersistent;

        var principal = new ClaimsPrincipal(identity);

        var authTicket = new AuthenticationTicket(principal, props, CookieDetails.AuthenticationScheme);
        return authTicket;
    }

然后像这样连接起来:

var formsCookieFormat = new FormsAuthCookieTicketFormat(_DecryptionKeyText, _ValidationKeyText);

        app.UseCookieAuthentication(options =>
        {
            // shortened for brevity... 
            options.TicketDataFormat = formsCookieFormat ;
            options.CookieName = "MyCookie";                
        });