.net core实践系列之SSO-同域实现

前言 SSO的系列还是以.Net Core作为实践例子与大家分享,SSO在Web方面复杂度分同域与跨域。本篇先分享同域的设计与实现,跨域将在下篇与大家分享。 如有需要调试demo的,可把SSO项目部署为域名http://sso.cg.com/,Web1项目部署为http://web1.cg.com,http://web2.cg.com,可以减少配置修改量 源码地址:https://github.com/SkyChenSky/Core.SSO 效果图 SSO简介 单点登录,全称为Single Sign On,在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 它是一个解决方案,目的是为了整合企业内多个应用系统,仅由一组账号只需进行一次登录,就可被授权访问多个应用系统。 流程描述 未登录状态访问业务Web应用会引导到认证中心。 用户在认证中心输入账号信息通过登录后,认证中心会根据用户信息生成一个具有安全性的token,将以任何方式持久化在浏览器。 此后访问其他Web应用的时候,必须携带此token进行访问,业务Web应用会通过本地认证或者转发认证而对token进行校验。 从上图可以简单的分析出三个关键点: Token的生成 Token的共享 Token校验 Token的生成 方式有多种: 可以通过Web框架对用户信息加密成Token。 Token编码方式也可以为JSON WEB TOKEN(JWT) 也可以是一段MD5,通过字典匹配保存在服务器用户信息与MD5值 Token的共享 浏览器存储有三种方式: Cookie 容量4KB限制 过期时间 localStorage 容量5MB限制 生命周期永久 sessionStorage 容量5MB限制 生命周期当前会话,关闭浏览器则失效 无法与服务端交互 作为拥有会失效的会话状态,更因选择Cookie存储。那么Cookie的使用是可以在同域共享的,因此在实现SSO的时候复杂度又分为同域与跨域。 同域的共享比较简单,在应用设置Cookie的Domain属性进行设置,就可以完美的解决。 Token校验 校验分两种情况: 转发给认证中心认证  由谁授权,就由谁进行身份认证。授权与认证是成对的。如果是以Cookie认证,那就是服务端对token进行解密。如果是服务端保存用户信息,则匹配token值。 业务应用自身认证  不需要转发,那就意味着业务应用认证规则与认证中心的认证规则必须是一致的。 设计要点 原则上来讲,只要统一Token的产生和校验方式,无论授权与认证的在哪(认证系统或业务系统),也无论用户信息存储在哪(浏览器、服务器),其实都可以实现单点登录的效果。 此次使用.NET Core MVC框架,以Cookie认证通过业务应用自身认证的方式进行同父域的SSO实现。 为什么要使用Cookie认证方式? 1.会话状态分布在客户浏览器,避免大量用户同时在线对服务端内存容量的压力。 2.横向扩展良好性,可按需增减节点。 统一应用授权认证 将以Core的Cookie认证进行实现,那么意味着每个应用对用户信息的加解密方式需要一致。 因此对AddCookie的设置属性DataProtectionProvider或者TicketDataFormat的加密方式进行重写实现。 .NET Core的SSO实现 Cookie认证 认证中心AddCookie的设置 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.Name = "Token"; options.Cookie.Domain = ".cg.com"; options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; options.SlidingExpiration = true; //options.DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"D:\sso\key")); options.TicketDataFormat = new TicketDataFormat(new AesDataProtector()); }); } 业务应用AddCookie的设置 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.Name = "Token"; options.Cookie.Domain = ".cg.com"; options.Events.OnRedirectToLogin = BuildRedirectToLogin; options.Events.OnSigningOut = BuildSigningOut; options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; options.SlidingExpiration = true; options.TicketDataFormat = new TicketDataFormat(new AesDataProtector()); }); } 基于设计要点的“统一应用授权认证”这一点,两者的区别不大,ticket的加密方式统一使用了AES,都指定Cookie.Domain = ".cg.com",保证了Cookie同域共享,设置了HttpOnly避免XSS攻击。 两者区别在于: options.Events.OnRedirectToLogin = BuildRedirectToLogin; options.Events.OnSigningOut = BuildSigningOut; 这是为了让业务应用引导跳转到认证中心登录页面。OnRedirectToLogin是认证失败跳转。OnSigningOut是注销跳转。 /// /// 未登录下,引导跳转认证中心登录页面 /// /// /// private static Task BuildRedirectToLogin(RedirectContext context) { var currentUrl = new UriBuilder(context.RedirectUri); var returnUrl = new UriBuilder { Host = currentUrl.Host, Port = currentUrl.Port, Path = context.Request.Path }; var redirectUrl = new UriBuilder { Host = "sso.cg.com", Path = currentUrl.Path, Query = QueryString.Create(context.Options.ReturnUrlParameter, returnUrl.Uri.ToString()).Value }; context.Response.Redirect(redirectUrl.Uri.ToString()); return Task.CompletedTask; } /// /// 注销,引导跳转认证中心登录页面 /// /// /// private static Task BuildSigningOut(CookieSigningOutContext context) { var returnUrl = new UriBuilder { Host = context.Request.Host.Host, Port = context.Request.Host.Port ?? 80, }; var redirectUrl = new UriBuilder { Host = "sso.cg.com", Path = context.Options.LoginPath, Query = QueryString.Create(context.Options.ReturnUrlParameter, returnUrl.Uri.ToString()).Value }; context.Response.Redirect(redirectUrl.Uri.ToString()); return Task.CompletedTask; } } 登录注销 认证中心与业务应用两者的登录注册基本一致。 private async Task SignIn(User user) { var claims = new List { new Claim(JwtClaimTypes.Id,user.UserId), new Claim(JwtClaimTypes.Name,user.UserName), new Claim(JwtClaimTypes.NickName,user.RealName), }; var userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Basic")); var returnUrl = HttpContext.Request.Cookies[ReturnUrlKey]; await HttpContext.SignInAsync(userPrincipal, new AuthenticationProperties { IsPersistent = true, RedirectUri = returnUrl }); HttpContext.Response.Cookies.Delete(ReturnUrlKey); return Redirect(returnUrl ?? "/"); } private async Task SignOut() { await HttpContext.SignOutAsync(); } HttpContext.SignInAsync的原理 使用的是Cookie认证那么就是通过Microsoft.AspNetCore.Authentication.Cookies库的CookieAuthenticationHandler类的HandleSignInAsync方法进行处理的。 源码地址:https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs View Code 从源码我们可以分析出流程: 根据ClaimsPrincipal的用户信息序列化后通过加密方式进行加密获得ticket。(默认加密方式是的KeyRingBasedDataProtecto。源码地址:https://github.com/aspnet/DataProtection) 再通过之前的初始化好的CookieOption再AppendResponseCookie方法进行设置Cookie 最后通过Events.RedirectToReturnUrl进行重定向到ReturnUrl。 Ticket加密 两种设置方式 CookieAuthenticationOptions.DataProtectionProvider CookieAuthenticationOptions.TicketDataFormat DataProtectionProvider 如果做了集群可以设置到共享文件夹,在第一个启动的应用则会创建如下图的文件 options.DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"D:\sso\key")); TicketDataFormat 重写数据加密方式,本次demo使用了是AES. options.TicketDataFormat = new TicketDataFormat(new AesDataProtector()); internal class AesDataProtector : IDataProtector { private const string Key = "!@#13487"; public IDataProtector CreateProtector(string purpose) { return this; } public byte[] Protect(byte[] plaintext) { return AESHelper.Encrypt(plaintext, Key); } public byte[] Unprotect(byte[] protectedData) { return AESHelper.Decrypt(protectedData, Key); } } 结尾 以上为.NET Core MVC的同域SSO实现思路与细节 。因编写demo的原因代码复用率并不好,冗余代码比较多,大家可以根据情况进行抽离封装。下篇会继续分享跨域SSO的实现。如果对本篇有任何建议与疑问,可以在下方评论反馈给我。 作  者: 陈珙 出  处:http://www.cnblogs.com/skychen1218/ 关于作者:专注于微软平台的项目开发。如有问题或建议,请多多赐教! 版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。 声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是作者坚持原创和持续写作的最大动力! https://www.cnblogs.com/skychen1218/p/9773466.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信