這篇文章主要介紹C#中單點(diǎn)登錄的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
玉州ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來市場(chǎng)廣闊!成為創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!
什么是單點(diǎn)登錄?
我想肯定有一部分人“望文生義”的認(rèn)為單點(diǎn)登錄就是一個(gè)用戶只能在一處登錄,其實(shí)這是錯(cuò)誤的理解(我記得我第一次也是這么理解的)。
單點(diǎn)登錄指的是多個(gè)子系統(tǒng)只需要登錄一個(gè),其他系統(tǒng)不需要登錄了(一個(gè)瀏覽器內(nèi))。一個(gè)子系統(tǒng)退出,其他子系統(tǒng)也全部是退出狀態(tài)。
如果你還是不明白,我們舉個(gè)實(shí)際的例子把。比如創(chuàng)新互聯(lián)首頁:https://www.jb51.ne ,和創(chuàng)新互聯(lián)的搜索http://so.jb51.net 。這就是兩個(gè)系統(tǒng)(不同的域名)。如果你登錄其中一個(gè),另一個(gè)也是登錄狀態(tài)。如果你退出一個(gè),另一個(gè)也是退出狀態(tài)了。
那么這是怎么實(shí)現(xiàn)的呢?這就是我們今天要分析的問題了。
單點(diǎn)登錄(SSO)原理
首先我們需要一個(gè)認(rèn)證中心(Service),和兩個(gè)子系統(tǒng)(Client)。
當(dāng)瀏覽器第一次訪問Client1時(shí),處于未登錄狀態(tài) -> 302到認(rèn)證中心(Service) -> 在Service的登錄頁面登錄(寫入Cookie記錄登錄信息) -> 302到Client1(寫入Cookie記錄登錄信息)第二次訪問Client1 -> 讀取Client1中Cookie登錄信息 -> Client1為登錄狀態(tài)
第一次訪問Client2 -> 讀取Client2中Cookie中的登錄信息 -> Client2為未登錄狀態(tài) -> 302到在Service(讀取Service中的Cookie為登錄狀態(tài)) -> 302到Client2(寫入Cookie記錄登錄信息)
我們發(fā)現(xiàn)在訪問Client2的時(shí)候,中間時(shí)間經(jīng)過了幾次302重定向,并沒有輸入用戶名密碼去登錄。用戶完全感覺不到,直接就是登錄狀態(tài)了。
圖解:
手?jǐn)]一個(gè)SSO
環(huán)境:.NET Framework 4.5.2
Service:
////// 登錄 /// /// /// /// ///[HttpPost] public string Login(string name, string passWord, string backUrl) { if (true)//TODO:驗(yàn)證用戶名密碼登錄 { //用Session標(biāo)識(shí)會(huì)話是登錄狀態(tài) Session["user"] = "XX已經(jīng)登錄"; //在認(rèn)證中心 保存客戶端Client的登錄認(rèn)證碼 TokenIds.Add(Session.SessionID, Guid.NewGuid()); } else//驗(yàn)證失敗重新登錄 { return "/Home/Login"; } return backUrl + "?tokenId=" + TokenIds[Session.SessionID];//生成一個(gè)tokenId 發(fā)放到客戶端 }
Client:
public static ListTokens = new List (); public async Task Index() { var tokenId = Request.QueryString["tokenId"]; //如果tokenId不為空,則是由Service302過來的。 if (tokenId != null) { using (HttpClient http = new HttpClient()) { //驗(yàn)證Tokend是否有效 var isValid = await http.GetStringAsync("http://localhost:8018/Home/TokenIdIsValid?tokenId=" + tokenId); if (bool.Parse(isValid.ToString())) { if (!Tokens.Contains(tokenId)) { //記錄登錄過的Client (主要是為了可以統(tǒng)一登出) Tokens.Add(tokenId); } Session["token"] = tokenId; } } } //判斷是否是登錄狀態(tài) if (Session["token"] == null || !Tokens.Contains(Session["token"].ToString())) { return Redirect("http://localhost:8018/Home/Verification?backUrl=http://localhost:26756/Home"); } else { if (Session["token"] != null) Session["token"] = null; } return View(); }
效果圖:
當(dāng)然,這只是用較少的代碼擼了一個(gè)較簡單的SSO。僅用來理解,勿用于實(shí)際應(yīng)用。
IdentityServer4實(shí)現(xiàn)SSO
環(huán)境:.NET Core 2.0
上面我們手?jǐn)]了一個(gè)SSO,接下來我們看看.NET里的IdentityServer4怎么來使用SSO。
首先建一個(gè)IdentityServer4_SSO_Service(MVC項(xiàng)目),再建兩個(gè)IdentityServer4_SSO_Client(MVC項(xiàng)目)
在Service項(xiàng)目中用nuget導(dǎo)入IdentityServer4 2.0.2
、IdentityServer4.AspNetIdentity 2.0.0
、IdentityServer4.EntityFramework 2.0.0
在Client項(xiàng)目中用nuget導(dǎo)入IdentityModel 2.14.0
然后分別設(shè)置Service和Client項(xiàng)目啟動(dòng)端口為 5001(Service)、5002(Client1)、5003(Client2)
在Service中新建一個(gè)類Config:
public class Config { public static IEnumerableGetIdentityResources() { return new List { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; } public static IEnumerable GetApiResources() { return new List { new ApiResource("api1", "My API") }; } // 可以訪問的客戶端 public static IEnumerable GetClients() { return new List { // OpenID Connect hybrid flow and client credentials client (MVC) //Client1 new Client { ClientId = "mvc1", ClientName = "MVC Client1", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, RequireConsent = true, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, //注意端口5002 是我們修改的Client的端口 PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" }, AllowOfflineAccess = true }, //Client2 new Client { ClientId = "mvc2", ClientName = "MVC Client2", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, RequireConsent = true, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5003/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5003/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" }, AllowOfflineAccess = true } }; } }
新增一個(gè)ApplicationDbContext類繼承于IdentityDbContext:
public class ApplicationDbContext : IdentityDbContext{ public ApplicationDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } }
在文件appsettings.json中配置數(shù)據(jù)庫連接字符串:
"ConnectionStrings": { "DefaultConnection": "Server=(local);Database=IdentityServer4_Demo;Trusted_Connection=True;MultipleActiveResultSets=true" }
在文件Startup.cs的ConfigureServices方法中增加:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); //數(shù)據(jù)庫連接字符串 services.AddIdentity () .AddEntityFrameworkStores () .AddDefaultTokenProviders(); services.AddMvc(); string connectionString = Configuration.GetConnectionString("DefaultConnection"); var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; services.AddIdentityServer() .AddDeveloperSigningCredential() .AddAspNetIdentity () .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); options.EnableTokenCleanup = true; options.TokenCleanupInterval = 30; }); }
并在Startup.cs文件里新增一個(gè)方法InitializeDatabase(初始化數(shù)據(jù)庫):
////// 初始數(shù)據(jù)庫 /// /// private void InitializeDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) { serviceScope.ServiceProvider.GetRequiredService ().Database.Migrate();//執(zhí)行數(shù)據(jù)庫遷移 serviceScope.ServiceProvider.GetRequiredService ().Database.Migrate(); var context = serviceScope.ServiceProvider.GetRequiredService (); context.Database.Migrate(); if (!context.Clients.Any()) { foreach (var client in Config.GetClients())//循環(huán)添加 我們直接添加的 5002、5003 客戶端 { context.Clients.Add(client.ToEntity()); } context.SaveChanges(); } if (!context.IdentityResources.Any()) { foreach (var resource in Config.GetIdentityResources()) { context.IdentityResources.Add(resource.ToEntity()); } context.SaveChanges(); } if (!context.ApiResources.Any()) { foreach (var resource in Config.GetApiResources()) { context.ApiResources.Add(resource.ToEntity()); } context.SaveChanges(); } } }
修改Configure方法:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //初始化數(shù)據(jù) InitializeDatabase(app); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); app.UseDatabaseErrorPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseIdentityServer(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
然后新建一個(gè)AccountController控制器,分別實(shí)現(xiàn)注冊(cè)、登錄、登出等。
新建一個(gè)ConsentController控制器用于Client回調(diào)。
然后在Client的Startup.cs類里修改ConfigureServices方法:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }).AddCookie("Cookies").AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:5001"; options.RequireHttpsMetadata = false; options.ClientId = "mvc2"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("api1"); options.Scope.Add("offline_access"); }); }
對(duì)于Client的身份認(rèn)證就簡單了:
[Authorize]//身份認(rèn)證 public IActionResult Index() { return View(); } ////// 登出 /// ///public async Task Logout() { await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); return View("Index"); }
效果圖:
以上是“C#中單點(diǎn)登錄的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!