zoukankan      html  css  js  c++  java
  • 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 的团队能多考虑一些这方面的兼容问题,哪怕是一个折中方案也能让开发者更有动力去做迁移。

    雁过留声,阅过点赞哈~~~


    我的公众号《捷义》

    作者:捷义
    出处:http://www.cnblogs.com/youclk/
    说明:转载请标明来源和作者
  • 相关阅读:
    Study Plan The TwentySecond Day
    Study Plan The Nineteenth Day
    Study Plan The TwentySeventh Day
    Study Plan The Twentieth Day
    Study Plan The TwentyFirst Day
    python实现进程的三种方式及其区别
    yum makecache
    JSONPath 表达式的使用
    oracle执行cmd的实现方法
    php daodb插入、更新与删除数据
  • 原文地址:https://www.cnblogs.com/youclk/p/6130324.html
Copyright © 2011-2022 走看看