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

  • 相关阅读:
    Photoshop 基础七 位图 矢量图 栅格化
    Photoshop 基础六 图层
    Warfare And Logistics UVALive
    Walk Through the Forest UVA
    Airport Express UVA
    Guess UVALive
    Play on Words UVA
    The Necklace UVA
    Food Delivery ZOJ
    Brackets Sequence POJ
  • 原文地址:https://www.cnblogs.com/skychen1218/p/9805995.html
Copyright © 2011-2022 走看看