接着上篇的《.net core实践系列之SSO-同域实现》,此次来聊聊SSO跨域的实现方式。此次虽然说是.net core实践,可是核心点使用jquery居多。html
建议看这篇文章的朋友能够先看上篇《.net core实践系列之SSO-同域实现》作一个SSO大概了解。jquery
源码地址:https://github.com/SkyChenSky/Core.SSO.gitgit
只要统一Token的产生和校验方式,不管受权与认证的在哪(认证系统或业务系统),也不管用户信息存储在哪(浏览器、服务器),其实均可以实现单点登陆的效果github
全部支持JavaScript 的浏览器,都必须遵照的安全策略,也是浏览器最基本的安全功能。web
若是没有处理过发起跨域请求,就算服务器接收到了,响应成功了浏览器也是会拦截的。ajax
指域名,协议,端口相同api
浏览器为了阻止恶意脚本获取不一样源上的的敏感信息。跨域
然而在实际状况下跨域请求的场景也是存在的,解决方案有两种:浏览器
Cookie的读取和发送也是必须遵循同源策略的。安全
虽然说请求共享能够设置响应头Access-Control-Allow-Credentials、Access-Control-Allow-Origin与Ajax请求属性xhrFields: {withCredentials: true}进行解决,可是!
就算响应头有set-cookie浏览器也是没法正常保存的。
针对cookie认证,我惟一能找到的解决方案就是跳转页面。
具体步骤:
PS:若是哪位朋友有更加好的方案,能够及时与我沟通,很是感谢
<script> $(function () { $("#submit").click(function () { $("#postForm").ajaxSubmit(function (result) { if (result.success) { var token = getToken(); if (token) { var authorizeHostArray = new Array( "http://www.web1.com/Token/Authorization", "http://www.web2.com/Token/Authorization" ); var authorizeHostParams = ""; authorizeHostArray.forEach(function (item) { authorizeHostParams += "&hostAuthorization=" + item; }); window.location.href = authorizeHostArray[0] + "?token=" + token + authorizeHostParams; } } else { alert(result.msg); } }); }); function getToken() { var token = null; $.ajax({ url: "/api/Token", type: "GET", async: false, success: function (d) { token = d.token; } }); return token; } }); </script>
public class TokenController : Controller { public static TokenCookieOptions CookieOptions { get; set; } public IActionResult Authorization(string token, List<string> hostAuthorization = null) { if (CookieOptions == null || string.IsNullOrEmpty(token)) return BadRequest(); HttpContext.Response.Cookies.Append(CookieOptions.Name, token, new CookieOptions { Domain = CookieOptions.Domain, Expires = DateTimeOffset.UtcNow.Add(CookieOptions.Expires), HttpOnly = CookieOptions.HttpOnly, IsEssential = CookieOptions.IsEssential, MaxAge = CookieOptions.MaxAge, Path = CookieOptions.Path, SameSite = CookieOptions.SameSite }); if (hostAuthorization.Any()) hostAuthorization = hostAuthorization.Where(a => !a.Contains(HttpContext.Request.Host.Host)).ToList(); if (!hostAuthorization.Any()) hostAuthorization = new List<string> { "http://www.sso.com" }; return View(new TokenViewData { Token = token, HostAuthorization = hostAuthorization }); } public IActionResult Logout(List<string> hostAuthorization = null) { HttpContext.Response.Cookies.Delete(CookieOptions.Name); if (hostAuthorization.Any()) hostAuthorization = hostAuthorization.Where(a => !a.Contains(HttpContext.Request.Host.Host)).ToList(); if (!hostAuthorization.Any()) hostAuthorization = new List<string> { "http://www.sso.com" }; return View(new TokenViewData { HostAuthorization = hostAuthorization }); } }
与同域的实现的方式一致。
生成与认证是一对的,与之对应的就是AES的加密与解密。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.Name = "Token"; 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()); TokenController.CookieName = options.Cookie.Name; }); }
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); } }
业务系统自主认证的方式,对于系统的代码复用率与维护性都很低。若是想进行转发到认证系统进行认证,能够对[Authorize]进行重写。
大体思路是:
访问业务系统时,由自定义的[Authorize]进行拦截
获取到Token设置到请求头进行HttpPost到认证系统提供的/api/token/Authentication接口
响应给业务系统若是是成功则继续访问,若是是失败则401或者跳转到登陆页。
最近事情比较多,demo与文章写的比较仓促,若是朋友们有更好的实现方式与建议,麻烦在下面评论反馈给我,先在此感谢。