zoukankan      html  css  js  c++  java
  • .net core实践系列之SSO-跨域实现

    前言

    接着上篇的《.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认证,我唯一能找到的解决方案就是跳转页面。

    具体步骤:

    1. 认证中心登录成功后,请求登录中心接口获得token
    2. 携带token逐个跳转到业务系统的中转页面。
    3. 跳转完成后,返回到认证中心登录页面进行引导。

     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>

    业务系统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:ssokey"));
                       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与文章写的比较仓促,如果朋友们有更好的实现方式与建议,麻烦在下面评论反馈给我,先在此感谢。

  • 相关阅读:
    shell脚本修改Linux系统中所有IP样例
    关闭并卸载数据库脚本
    查询编译不通过的存储过程并重新编译
    SQL函数造数据样例(一)
    类型转换和多态
    Java学习笔记(三)
    Java学习笔记二()
    Java学习笔记(一)
    1.2.零宽断言
    1.3.匹配小括号的字符(可能有小括号在一行的,也有多行的)
  • 原文地址:https://www.cnblogs.com/skychen1218/p/9805995.html
Copyright © 2011-2022 走看看