前言
想象一下這樣的場景:
每天早上,你打開電腦,先登錄OA系統(tǒng)查看郵件,再登錄CRM系統(tǒng)查看客戶信息,接著登錄財務系統(tǒng)審批報銷單...每個系統(tǒng)都要輸入用戶名密碼,而且為了防止安全風險,你還得為每個系統(tǒng)設置不同的復雜密碼。
這簡直是現(xiàn)代職場人的噩夢!
作為一名程序猿,我們不僅要解決自己的痛點,更要為用戶創(chuàng)造流暢的體驗。這就是單點登錄(Single Sign-On, SSO)誕生的意義——讓用戶只需登錄一次,就能訪問所有相互信任的系統(tǒng),告別密碼記憶的煩惱。
下面我們一起探索如何在 C# 中實現(xiàn)單點登錄(SSO)。
什么是單點登錄?
**單點登錄(SSO)**,顧名思義,就是 "一次登錄,處處通行"。
它是一種身份驗證機制,允許用戶使用一組憑據(jù)(如用戶名和密碼)登錄到多個應用程序或網(wǎng)站,而無需為每個應用程序單獨登錄。
SSO的核心思想可以用一個生活中的例子來理解:
假設你是一家大型公司的員工,公司有辦公大樓、健身房、餐廳和停車場。傳統(tǒng)方式下,你進入每個區(qū)域都需要出示不同的證件或刷卡。而有了SSO,就像公司給你發(fā)了一張萬能門禁卡,刷一次卡就能暢通無阻地進入所有區(qū)域。
從技術角度看,SSO主要解決了以下問題:
- 用戶體驗:減少用戶需要記憶的密碼數(shù)量,提高操作效率
- 系統(tǒng)集成:簡化不同系統(tǒng)間的身份驗證流程
SSO的實現(xiàn)方式
SSO的實現(xiàn)方式多種多樣,但核心思想都是集中認證,分散使用。
下面是一些常見的實現(xiàn)方式:
基于Cookie的SSO(同域/子域場景)
這是最簡單的SSO實現(xiàn)方式,適用于同一主域名下的不同子域名系統(tǒng)(如a.baidu.com和b.baidu.com)。
原理是將認證信息存儲在父域名的 Cookie 中,子域名可以自動繼承父域名的 Cookie。
基于認證中心的SSO(跨域場景)
對于完全不同的域名(如a.com和b.com),Cookie無法共享,這時就需要引入獨立的認證中心。
常見的實現(xiàn)方案有CAS、OAuth2.0、OIDC和SAML等。
以 CAS(Central Authentication Service) 為例,其流程大概如下:
- 用戶訪問a.com,a.com發(fā)現(xiàn)用戶未登錄,重定向到認證中心(sso.com)
- 用戶在sso.com完成認證,認證中心生成臨時票據(jù)(ST)
基于Token的SSO(現(xiàn)代Web應用)
對于前后端分離的現(xiàn)代Web應用,常用JWT(JSON Web Token)實現(xiàn)SSO。
認證中心頒發(fā) Token 后,前端將 Token 存儲在 LocalStorage 中,每次請求攜帶 Token,各子系統(tǒng)通過驗證 Token 確認用戶身份。
單點登錄實現(xiàn)例子
1. 創(chuàng)建解決方案和項目
打開VS2022,新建空白解決方案"SSODemo",然后添加三個ASP.NET Core Web應用項目:
2. 認證中心實現(xiàn)
在SSO.Auth項目中,添加LoginController:
[Route("auth")]
public class LoginController : Controller
{
private static Dictionary<string, string> _tickets = new Dictionary<string, string>();
[HttpGet("login")]
public IActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost("login")]
public IActionResult Login(string username, string password, string returnUrl)
{
// 簡單模擬用戶驗證
if(username == "admin" && password == "123456")
{
// 生成臨時票據(jù)(ST)
var st = Guid.NewGuid().ToString();
_tickets[st] = username;
// 重定向回子系統(tǒng),攜帶ST
return Redirect($"{returnUrl}?st={st}");
}
ViewBag.Error = "用戶名或密碼錯誤";
return View();
}
[HttpGet("validate")]
public IActionResult Validate(string st, string service)
{
if(_tickets.TryGetValue(st, out var username))
{
_tickets.Remove(st); // ST一次性使用
return Ok(new { username });
}
return Unauthorized();
}
}
3. 子系統(tǒng)實現(xiàn)
在SSO.AppA和SSO.AppB中,添加HomeController:
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index()
{
// 檢查本地會話
if(HttpContext.Session.GetString("username") != null)
{
ViewBag.Username = HttpContext.Session.GetString("username");
return View();
}
// 未登錄,跳轉(zhuǎn)到認證中心
var returnUrl = $"{Request.Scheme}://{Request.Host}";
var ssoUrl = $"https://localhost:5001/auth/login?returnUrl={returnUrl}";
return Redirect(ssoUrl);
}
[HttpGet("callback")]
public async Task<IActionResult> Callback(string st)
{
// 驗證ST
using(var client = new HttpClient())
{
var validateUrl = $"https://localhost:5001/auth/validate?st={st}&service=appA";
var response = await client.GetAsync(validateUrl);
if(response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<dynamic>(content);
string username = result.GetProperty("username").GetString();
// 建立本地會話
HttpContext.Session.SetString("username", username);
return RedirectToAction("Index");
}
}
return Unauthorized();
}
[HttpGet("logout")]
public IActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Index");
}
}
4. 配置和運行
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseSession();
// ...
}
- 同時啟動三個項目,訪問AppA或AppB,都會被重定向到認證中心登錄。登錄成功后,再訪問另一個App,無需再次登錄。
總結(jié)
單點登錄在各種場景下都能大顯身手,我們經(jīng)常看到的一些接入微信、支付寶等第三方登錄的應用,本質(zhì)上是利用這些平臺作為認證中心的SSO流程。
作為 C# 程序員,我們可以利用 ASP.NET Core 強大的功能,相對輕松地實現(xiàn)各種 SSO 方案,無論是簡單的同域Cookie共享,還是復雜的跨域認證中心,.NET 都提供了完善的支持。
不過,在實現(xiàn) SSO 的時候,安全問題也是需要密切注意的,這里有一些建議:
- 票據(jù)尤其是臨時的票據(jù)要設置較短的有效期,一般不要超過5分鐘
- 使用 state 參數(shù)防止跨站請求偽造(CSRF攻擊)
- 實現(xiàn)全局注銷,確保所有系統(tǒng)會話都被清除
該文章在 2025/7/28 12:39:17 編輯過