前言
接着上篇的《.net core 实践系列之 SSO - 同域实现》, 这次来聊聊 SSO 跨域的实现方式. 这次虽说是. net core 实践, 但是核心点使用 jQuery 居多.
建议看这篇文章的朋友可以先看上篇《.net core 实践系列之 SSO - 同域实现》做一个 SSO 大概了解.
源码地址: https://github.com/SkyChenSky/Core.SSO.git
效果图
知识点回顾
实现原则
只要统一 Token 的产生和校验方式, 无论授权与认证的在哪(认证系统或业务系统), 也无论用户信息存储在哪(浏览器, 服务器), 其实都可以实现单点登录的效果
实现关键点
Token 的生成
Token 的共享
Token 校验
Token 共享复杂度
同域
跨域
Token 认证方式
业务系统自认证
转发给认证中心认证
同源策略
所有支持 JavaScript 的浏览器, 都必须遵守的安全策略, 也是浏览器最基本的安全功能.
如果没有处理过发起跨域请求, 就算服务器接收到了, 响应成功了浏览器也是会拦截的.
同源
指域名, 协议, 端口相同
目的
浏览器为了阻止恶意脚本获取不同源上的的敏感信息.
跨域请求
然而在实际情况下跨域请求的场景也是存在的, 解决方案有两种:
JSONP
响应头设置 "Access-Control-Allow-Origin"
Cookie
Cookie 的读取和发送也是必须遵循同源策略的.
虽说请求共享可以设置响应头 Access-Control-Allow-Credentials,Access-Control-Allow-Origin 与 Ajax 请求属性 xhrFields: {withCredentials: true}进行解决, 但是!
就算响应头有 set-cookie 浏览器也是无法正常保存的.
SSO 跨域解决方式
针对 cookie 认证, 我唯一能找到的解决方案就是跳转页面.
具体步骤:
认证中心登录成功后, 请求登录中心接口获得 token
携带 token 逐个跳转到业务系统的中转页面.
跳转完成后, 返回到认证中心登录页面进行引导.
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;
- });
- Windows.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>
业务系统 Token 保存与注销
- 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
- });
- }
- }
Token 生成与认证
与同域的实现的方式一致.
生成与认证是一对的, 与之对应的就是 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 与文章写的比较仓促, 如果朋友们有更好的实现方式与建议, 麻烦在下面评论反馈给我, 先在此感谢.
来源: https://www.cnblogs.com/skychen1218/p/9805995.html