ASP.NET Core 和 ASP.NET Framework 共享 Identity 身份驗證


  .NET Core 已經熱了好一陣子,1.1版本發布后其可用性也越來越高,開源、組件化、跨平台、性能優秀、社區活躍等等標簽再加上“微軟爸爸”主推和大力支持,盡管現階段對比.net framework還是比較“稚嫩”,但可以想象到它光明的前景。作為 .net 開發者你是否已經開始嘗試將項目遷移到 .net core 上?這其中要解決的一個較大的問題就是如何讓你的 .net core 和老 .net framework 站點實現身份驗證兼容!

1、第一篇章

我們先來看看 .net core 中對 identity 的實現,在 Startup.cs 的 Configure 中配置 Cookie 認證的相關屬性

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationScheme = "test",
        CookieName = "MyCookie"
    });
}

Controller

public IActionResult Index()
{
    return View();
}

public IActionResult Login()
{
    return View();
}

[HttpPost]
public async Task<IActionResult> Login(string name)
{
    var identity = new ClaimsIdentity(
                    new List<Claim>
                    {
                    new Claim(ClaimTypes.Name,name, ClaimValueTypes.String)
                    },
                    ClaimTypes.Authentication,
                    ClaimTypes.Name,
                    ClaimTypes.Role);
    var principal = new ClaimsPrincipal(identity);
    var properties = new AuthenticationProperties { IsPersistent = true };

    await HttpContext.Authentication.SignInAsync("test", principal, properties);

    return RedirectToAction("Index");
}

login 視圖

<!DOCTYPE html>
<html>
<head>
    <title>登錄</title>
</head>
<body>
    <form asp-controller="Account" asp-action="Login" method="post">
       <input type="text" name="name" /><input type="submit" value="提交" />
    </form>
</body>
</html>

index 視圖

<!DOCTYPE html>
<html>
<head>
  <title>歡迎您-@User.Identity.Name</title>
</head>
<body>
    @if (User.Identity.IsAuthenticated)
    {
        <p>登錄成功!</p>
    }
</body>
</html>

下面是實現效果的截圖:

ok,到此我們用 .net core 比較簡單地實現了用戶身份驗證信息的保存和讀取。

接着思考,如果我的 .net framework 項目想讀取 .net core 項目保存的身份驗證信息應該怎么做?

要讓兩個項目都接受同一個 Identity 至少需要三個條件:

  • CookieName 必須相同。
  • Cookie 的作用域名必須相同。
  • 兩個項目的 Cookie 認證必須使用同一個 Ticket。

首先我們對 .net core 的 Cookie 認證添加 domain 屬性和 ticket 屬性

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    var protectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\keyPath\"));
    var dataProtector = protectionProvider.CreateProtector("MyCookieAuthentication");
    var ticketFormat = new TicketDataFormat(dataProtector);

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationScheme = "test",
        CookieName = "MyCookie",
        CookieDomain = "localhost",
        TicketDataFormat = ticketFormat
    });
}

此時我們在 .net core 項目中執行用戶登錄,程序會在我們指定的目錄下生成 key.xml

我們打開文件看看程序幫我們記錄了那些信息

<?xml version="1.0" encoding="utf-8"?>
<key id="eb8b1b59-dbc5-4a28-97ad-2117a2e8f106" version="1">
  <creationDate>2016-12-04T08:27:27.8435415Z</creationDate>
  <activationDate>2016-12-04T08:27:27.8214603Z</activationDate>
  <expirationDate>2017-03-04T08:27:27.8214603Z</expirationDate>
  <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
    <descriptor>
      <encryption algorithm="AES_256_CBC" />
      <validation algorithm="HMACSHA256" />
      <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
        <value>yHdMEYlEBzcwpx0bRZVIbcGJ45/GqRwFjMfq8PJ+k7ZWsNMic0EMBgP33FOq9MFKX0XE/a1plhDizbb92ErQYw==</value>
      </masterKey>
    </descriptor>
  </descriptor>
</key>

ok,接下來我們開始配置 .net framework 項目,同樣,在 Startup.cs 中配置 Cookie 認證的相關屬性。

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var protectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\keyPath\"));
        var dataProtector = protectionProvider.CreateProtector("MyCookieAuthentication");
        var ticketFormat = new AspNetTicketDataFormat(new DataProtectorShim(dataProtector));
            
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "test",
            CookieName = "MyCookie",
            CookieDomain = "localhost",
            TicketDataFormat = ticketFormat
        });
    }
}

view

<!DOCTYPE html>
<html>
<head>
    <title>.net framewor歡迎您-@User.Identity.Name</title>
</head>
<body>
    @if (User.Identity.IsAuthenticated)
    {
        <p>.net framework登錄成功!</p>
    }
</body>
</html>

寫法和 .net core 基本上是一致的,我們來看下能否成功獲取用戶名:

反之在 .net framework 中登錄在 .net core 中獲取身份驗證信息的方法是一樣的,這里就不重復寫了。

然而,到此為止事情就圓滿解決了嗎?很遺憾,麻煩才剛剛開始!


2、第二篇章

如果你的子項目不多,也不復雜的情況下,新增一個 .net core 站點,然后適當修改以前的 .net framework 站點,上述實例確實能夠滿足需求。可是如果你的子站點足夠多,或者項目太過復雜,牽扯到的業務過於龐大或重要,這種情況下我們通常是不願意動老項目的。或者說我們沒有辦法將所有的項目都進行更改,然后和新增的 .net core 站點同時上線,如果這么做了,那么更新周期會拉的很長不說,測試和更新之后的維護階段壓力都會很大。所以我們必須要尋找到一種方案,讓 .net core 的身份驗證機制完全迎合 .net framwork。

因為 .net framework 的 cookie 是對稱加密,而 .net core 是非對稱加密,所以要在 .net core 中動手的話必須要對 .net core 默認的加密和解密操作進行攔截,如果可行的話最好的方案應該是將 .net framework 的 FormsAuthentication 類移植到 .net core 中。但是用 reflector 看了下,牽扯到的代碼太多,剪不斷理還亂,github 找到其開源地址:https://github.com/Microsoft/referencesource/blob/master/System.Web/Security/FormsAuthentication.cs ,瞎忙活了一陣之后終於感慨:臣妾做不到(>﹏< )。

不過幸好有領路人,參考這篇博文:http://www.cnblogs.com/cmt/p/5940796.html

Cookie 認證的相關屬性

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationScheme = "test",
    CookieName = "MyCookie",
    CookieDomain = "localhost",
    TicketDataFormat = new FormsAuthTicketDataFormat("")
});

FormsAuthTicketDataFormat

public class FormsAuthTicketDataFormat : ISecureDataFormat<AuthenticationTicket>
{
    private string _authenticationScheme;

    public FormsAuthTicketDataFormat(string authenticationScheme)
    {
        _authenticationScheme = authenticationScheme;
    }

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        var formsAuthTicket = GetFormsAuthTicket(protectedText);
        var name = formsAuthTicket.Name;
        DateTime issueDate = formsAuthTicket.IssueDate;
        DateTime expiration = formsAuthTicket.Expiration;

        var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, name) }, "Basic");
        var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

        var authProperties = new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties
        {
            IssuedUtc = issueDate,
            ExpiresUtc = expiration
        };
        var ticket = new AuthenticationTicket(claimsPrincipal, authProperties, _authenticationScheme);
        return ticket;
    }

    FormsAuthTicket GetFormsAuthTicket(string cookie)
    {
        return DecryptCookie(cookie).Result;
    }

    async Task<FormsAuthTicket> DecryptCookie(string cookie)
    {
        HttpClient _httpClient = new HttpClient();
        var response = await _httpClient.GetAsync("http://192.168.190.134/user/getMyTicket?cookie={cookie}");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsAsync<FormsAuthTicket>();
    }
}

FormsAuthTicket

public class FormsAuthTicket
{
    public DateTime Expiration { get; set; }
    public DateTime IssueDate { get; set; }
    public string Name { get; set; }
}

以上實現了對 cookie 的解密攔截,然后通過 webapi 從 .net framework 獲取 ticket

[Route("getMyTicket")]
public IHttpActionResult GetMyTicket(string cookie)
{
    var formsAuthTicket = FormsAuthentication.Decrypt(cookie);
    return Ok(new { formsAuthTicket.Name, formsAuthTicket.IssueDate, formsAuthTicket.Expiration });
}

有了 webapi 這條線,解密解決了,加密就更簡單了,通過 webapi 獲取加密后的 cookie,.net core 要做的只有一步,保存 cookie 就行了

[HttpPost]
public async Task<IActionResult> Login(string name)
{
    HttpClient _httpClient = new HttpClient();
    var response = await _httpClient.GetAsync($"http://192.168.190.134/user/getMyCookie?name={name}");
    response.EnsureSuccessStatusCode();

    string cookieValue = (await response.Content.ReadAsStringAsync()).Trim('\"');
    CookieOptions options = new CookieOptions();
    options.Expires = DateTime.MaxValue;
    HttpContext.Response.Cookies.Append("MyCookie", cookieValue, options);

    return RedirectToAction("Index");
}

webapi 獲取 cookie

[Route("getMyCookie")]
public string GetMyCookie(string name)
{
    FormsAuthentication.SetAuthCookie(name, false);
    return FormsAuthentication.GetAuthCookie(name, false).Value;
}

其余代碼不用做任何更改,ok,我們來測試一下

ok,登錄成功,至此完成.net framework和.net core身份驗證的兼容,哎,如果 .net core 的團隊能多考慮一些這方面的兼容問題,哪怕是一個折中方案也能讓開發者更有動力去做遷移。

雁過留聲,閱過點贊哈~~~


我的公眾號《捷義》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM