作者:陳珙
連結:https://www.cnblogs.com/skychen1218/p/9773466.html
前言
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儲存。那麼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:ssokey”));
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
{
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
protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
properties = properties ?? new AuthenticationProperties();
_signInCalled = true;
// Process the request cookie to initialize members like _sessionKey.
await EnsureCookieTicket();
var cookieOptions = BuildCookieOptions();
var signInContext = new CookieSigningInContext(
Context,
Scheme,
Options,
user,
properties,
cookieOptions);
DateTimeOffset issuedUtc;
if (signInContext.Properties.IssuedUtc.HasValue)
{
issuedUtc = signInContext.Properties.IssuedUtc.Value;
}
else
{
issuedUtc = Clock.UtcNow;
signInContext.Properties.IssuedUtc = issuedUtc;
}
if (!signInContext.Properties.ExpiresUtc.HasValue)
{
signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
}
await Events.SigningIn(signInContext);
if (signInContext.Properties.IsPersistent)
{
var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime();
}
var ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.Scheme.Name);
if (Options.SessionStore != null)
{
if (_sessionKey != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
_sessionKey = await Options.SessionStore.StoreAsync(ticket);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
Options.ClaimsIssuer));
ticket = new AuthenticationTicket(principal, null, Scheme.Name);
}
var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
Options.CookieManager.AppendResponseCookie(
Context,
Options.Cookie.Name,
cookieValue,
signInContext.CookieOptions);
var signedInContext = new CookieSignedInContext(
Context,
Scheme,
signInContext.Principal,
signInContext.Properties,
Options);
await Events.SignedIn(signedInContext);
// Only redirect on the login path
var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
await ApplyHeaders(shouldRedirect, signedInContext.Properties);
Logger.SignedIn(Scheme.Name);
}
從原始碼我們可以分析出流程:
根據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:ssokey”));
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的原因程式碼復用率並不好,冗餘程式碼比較多,大家可以根據情況進行抽離封裝。
●編號169,輸入編號直達本文
●輸入m獲取文章目錄
Web開發
更多推薦《18個技術類公眾微信》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等