You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Core 2.0中复用ASP.NET Framework的machineKey解析认证令牌

解决ASP.NET Core 2.0+复用.NET Framework machineKey解密OAuth Bearer令牌的问题

Great question! When migrating from .NET Framework's OAuth Bearer authentication (which relies on machineKey) to ASP.NET Core 2.0+, you need to replicate the exact token decryption logic since Core doesn't have a built-in machineKey equivalent. Here's a step-by-step solution that maintains compatibility with your existing tokens:

1. Grab Required NuGet Packages

First, you'll need a couple of packages to bridge the gap between Core and the old machineKey encryption logic:

Install-Package Microsoft.AspNetCore.Authentication.OAuth
Install-Package System.Web

The System.Web package lets us reuse the existing MachineKey class for decryption, so we don't have to rewrite AES/SHA1 encryption from scratch.

2. Build a MachineKey-Compatible Ticket Format

Create a custom MachineKeyTicketDataFormat that implements ISecureDataFormat<AuthenticationTicket>. This class will handle decrypting tokens using your legacy machineKey parameters:

using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using System.Web.Security;

public class MachineKeyTicketDataFormat : ISecureDataFormat<AuthenticationTicket>
{
    private readonly string _decryptionKey;
    private readonly string _validationKey;
    private readonly string _decryptionAlgorithm;
    private readonly string _validationAlgorithm;

    public MachineKeyTicketDataFormat(string decryptionKey, string validationKey, 
        string decryptionAlgorithm = "AES", string validationAlgorithm = "SHA1")
    {
        _decryptionKey = decryptionKey;
        _validationKey = validationKey;
        _decryptionAlgorithm = decryptionAlgorithm;
        _validationAlgorithm = validationAlgorithm;
    }

    // We don't need to generate tokens in Core (old apps handle that), so leave these empty
    public string Protect(AuthenticationTicket data) => throw new NotImplementedException("Only token decryption is supported");
    public string Protect(AuthenticationTicket data, string purpose) => Protect(data);

    public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null);

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        // Decrypt the token using the exact machineKey logic from .NET Framework
        var ticketBytes = MachineKey.Unprotect(
            Convert.FromBase64String(protectedText),
            "Microsoft.Owin.Security.OAuth", // This matches the purpose used by OWIN's OAuth middleware
            purpose ?? "Access_Token");

        // Deserialize the decrypted bytes back into an AuthenticationTicket
        var serializer = new TicketSerializer();
        return serializer.Deserialize(ticketBytes);
    }
}

// Replicate the TicketSerializer used by .NET Framework's OWIN OAuth middleware
// This is critical for correctly reading the token's claim data
public class TicketSerializer : IDataSerializer<AuthenticationTicket>
{
    public byte[] Serialize(AuthenticationTicket model)
    {
        using var memoryStream = new MemoryStream();
        using var binaryWriter = new BinaryWriter(memoryStream);
        
        WriteWithLength(binaryWriter, model.Identity.AuthenticationType);
        WriteClaims(binaryWriter, model.Identity.Claims);
        WriteProperties(binaryWriter, model.Properties);
        
        binaryWriter.Flush();
        return memoryStream.ToArray();
    }

    public AuthenticationTicket Deserialize(byte[] data)
    {
        using var memoryStream = new MemoryStream(data);
        using var binaryReader = new BinaryReader(memoryStream);
        
        var authType = ReadWithLength(binaryReader);
        var claims = ReadClaims(binaryReader);
        var properties = ReadProperties(binaryReader);

        var identity = new ClaimsIdentity(claims, authType);
        return new AuthenticationTicket(identity, properties);
    }

    private void WriteWithLength(BinaryWriter writer, string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            writer.Write(-1);
            return;
        }
        var bytes = Encoding.UTF8.GetBytes(value);
        writer.Write(bytes.Length);
        writer.Write(bytes);
    }

    private string ReadWithLength(BinaryReader reader)
    {
        var length = reader.ReadInt32();
        return length == -1 ? null : Encoding.UTF8.GetString(reader.ReadBytes(length));
    }

    private void WriteClaims(BinaryWriter writer, IEnumerable<Claim> claims)
    {
        var claimList = claims.ToList();
        writer.Write(claimList.Count);
        foreach (var claim in claimList)
        {
            WriteWithLength(writer, claim.Type);
            WriteWithLength(writer, claim.Value);
            WriteWithLength(writer, claim.ValueType);
            WriteWithLength(writer, claim.Issuer);
            WriteWithLength(writer, claim.OriginalIssuer);
        }
    }

    private List<Claim> ReadClaims(BinaryReader reader)
    {
        var count = reader.ReadInt32();
        var claims = new List<Claim>(count);
        for (var i = 0; i < count; i++)
        {
            claims.Add(new Claim(
                ReadWithLength(reader),
                ReadWithLength(reader),
                ReadWithLength(reader),
                ReadWithLength(reader),
                ReadWithLength(reader)));
        }
        return claims;
    }

    private void WriteProperties(BinaryWriter writer, AuthenticationProperties properties)
    {
        var dict = properties?.Items ?? new Dictionary<string, string>();
        writer.Write(dict.Count);
        foreach (var kvp in dict)
        {
            WriteWithLength(writer, kvp.Key);
            WriteWithLength(writer, kvp.Value);
        }
    }

    private AuthenticationProperties ReadProperties(BinaryReader reader)
    {
        var count = reader.ReadInt32();
        var dict = new Dictionary<string, string>(count);
        for (var i = 0; i < count; i++)
        {
            dict.Add(ReadWithLength(reader), ReadWithLength(reader));
        }
        return new AuthenticationProperties(dict);
    }
}

3. Configure Authentication in Startup

In Startup.cs, register your custom authentication scheme and the machineKey-compatible ticket format:

using Microsoft.AspNetCore.Authentication;

public void ConfigureServices(IServiceCollection services)
{
    // Your other service registrations...

    // Replace these with your actual machineKey values from Web.config
    var legacyDecryptionKey = "blablabla";
    var legacyValidationKey = "blablabla";

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "LegacyBearer";
        options.DefaultChallengeScheme = "LegacyBearer";
    })
    .AddScheme<AuthenticationSchemeOptions, LegacyBearerAuthenticationHandler>("LegacyBearer", options =>
    {
        // Add any scheme-specific options here if needed
    });

    // Register our custom ticket format with the service container
    services.AddSingleton<ISecureDataFormat<AuthenticationTicket>>(sp =>
        new MachineKeyTicketDataFormat(legacyDecryptionKey, legacyValidationKey));
}

4. Build the Custom Bearer Handler

Create a LegacyBearerAuthenticationHandler to extract the token from the request header and use our custom format to decrypt it:

using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Text.Encodings.Web;

public class LegacyBearerAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private readonly ISecureDataFormat<AuthenticationTicket> _ticketDataFormat;

    public LegacyBearerAuthenticationHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock,
        ISecureDataFormat<AuthenticationTicket> ticketDataFormat)
        : base(options, logger, encoder, clock)
    {
        _ticketDataFormat = ticketDataFormat;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // Check for the Authorization header
        if (!Request.Headers.ContainsKey("Authorization"))
            return AuthenticateResult.NoResult();

        if (!AuthenticationHeaderValue.TryParse(Request.Headers["Authorization"], out var authHeader))
            return AuthenticateResult.Fail("Invalid Authorization header format");

        // Only process Bearer tokens
        if (!string.Equals(authHeader.Scheme, "Bearer", StringComparison.OrdinalIgnoreCase))
            return AuthenticateResult.NoResult();

        var token = authHeader.Parameter;
        if (string.IsNullOrEmpty(token))
            return AuthenticateResult.Fail("Bearer token is missing");

        try
        {
            // Decrypt the token into an AuthenticationTicket
            var ticket = _ticketDataFormat.Unprotect(token);
            if (ticket == null)
                return AuthenticateResult.Fail("Failed to decrypt token");

            // Check if the token has expired
            if (ticket.Properties.ExpiresUtc.HasValue && ticket.Properties.ExpiresUtc.Value < Clock.UtcNow)
                return AuthenticateResult.Fail("Token has expired");

            // Create a successful authentication result
            var authResult = new AuthenticationTicket(ticket.Principal, ticket.Properties, Scheme.Name);
            return AuthenticateResult.Success(authResult);
        }
        catch (Exception ex)
        {
            return AuthenticateResult.Fail($"Token processing failed: {ex.Message}");
        }
    }
}

5. Enable Authentication Middleware

Finally, add the authentication middleware to your pipeline in Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // Your other middleware (static files, routing, etc.)...

    app.UseAuthentication();
    app.UseAuthorization();

    // Your endpoint mappings...
}

Test It Out

Now you can mark your ASP.NET Core controllers/actions with [Authorize], and when requests come in with Bearer tokens generated by your old .NET Framework apps, the Core app will decrypt them using your existing machineKey and recognize the authenticated user—just like the legacy apps do.


内容的提问来源于stack exchange,提问作者Nick Kovalsky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:54:10