溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C#中單點(diǎn)登錄的示例分析

發(fā)布時(shí)間:2021-08-09 14:20:40 來(lái)源:億速云 閱讀:77 作者:小新 欄目:編程語(yǔ)言

這篇文章主要介紹C#中單點(diǎn)登錄的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

什么是單點(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í)際的例子把。比如億速云首頁(yè):https://www.jb51.ne ,和億速云的搜索http://so.jb51.net 。這就是兩個(gè)系統(tǒng)(不同的域名)。如果你登錄其中一個(gè),另一個(gè)也是登錄狀態(tài)。如果你退出一個(gè),另一個(gè)也是退出狀態(tài)了。

那么這是怎么實(shí)現(xiàn)的呢?這就是我們今天要分析的問(wèn)題了。

單點(diǎn)登錄(SSO)原理

首先我們需要一個(gè)認(rèn)證中心(Service),和兩個(gè)子系統(tǒng)(Client)。

當(dāng)瀏覽器第一次訪問(wèn)Client1時(shí),處于未登錄狀態(tài) -> 302到認(rèn)證中心(Service) -> 在Service的登錄頁(yè)面登錄(寫(xiě)入Cookie記錄登錄信息) -> 302到Client1(寫(xiě)入Cookie記錄登錄信息)第二次訪問(wèn)Client1 -> 讀取Client1中Cookie登錄信息 -> Client1為登錄狀態(tài)

第一次訪問(wèn)Client2 -> 讀取Client2中Cookie中的登錄信息 -> Client2為未登錄狀態(tài) -> 302到在Service(讀取Service中的Cookie為登錄狀態(tài)) -> 302到Client2(寫(xiě)入Cookie記錄登錄信息)

我們發(fā)現(xiàn)在訪問(wèn)Client2的時(shí)候,中間時(shí)間經(jīng)過(guò)了幾次302重定向,并沒(méi)有輸入用戶名密碼去登錄。用戶完全感覺(jué)不到,直接就是登錄狀態(tài)了。

圖解:


C#中單點(diǎn)登錄的示例分析

手?jǐn)]一個(gè)SSO

環(huán)境:.NET Framework 4.5.2

Service:

/// <summary>
/// 登錄
/// </summary>
/// <param name="name"></param>
/// <param name="passWord"></param>
/// <param name="backUrl"></param>
/// <returns></returns>
[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 List<string> Tokens = new List<string>();
public async Task<ActionResult> Index()
{
 var tokenId = Request.QueryString["tokenId"];
 //如果tokenId不為空,則是由Service302過(guò)來(lái)的。
 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))
    {
     //記錄登錄過(guò)的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();
}

效果圖:


C#中單點(diǎn)登錄的示例分析

當(dāng)然,這只是用較少的代碼擼了一個(gè)較簡(jiǎn)單的SSO。僅用來(lái)理解,勿用于實(shí)際應(yīng)用。

IdentityServer4實(shí)現(xiàn)SSO

環(huán)境:.NET Core 2.0

上面我們手?jǐn)]了一個(gè)SSO,接下來(lái)我們看看.NET里的IdentityServer4怎么來(lái)使用SSO。

首先建一個(gè)IdentityServer4_SSO_Service(MVC項(xiàng)目),再建兩個(gè)IdentityServer4_SSO_Client(MVC項(xiàng)目)
在Service項(xiàng)目中用nuget導(dǎo)入IdentityServer4 2.0.2IdentityServer4.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)


C#中單點(diǎn)登錄的示例分析
在Service中新建一個(gè)類Config:

public class Config
{  
 public static IEnumerable<IdentityResource> GetIdentityResources()
  {
   return new List<IdentityResource>
   {
    new IdentityResources.OpenId(),
    new IdentityResources.Profile(),
   };
  }
 public static IEnumerable<ApiResource> GetApiResources()
 {
  return new List<ApiResource>
  {
   new ApiResource("api1", "My API")
  };
 }
 // 可以訪問(wèn)的客戶端
 public static IEnumerable<Client> GetClients()
  {   
   return new List<Client>
   {    
    // 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<IdentityUser>
{
 public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
  : base(options)
 {
 } 
 protected override void OnModelCreating(ModelBuilder builder)
 {
  base.OnModelCreating(builder);
 }
}

在文件appsettings.json中配置數(shù)據(jù)庫(kù)連接字符串:

"ConnectionStrings": {
 "DefaultConnection": "Server=(local);Database=IdentityServer4_Demo;Trusted_Connection=True;MultipleActiveResultSets=true"
 }

在文件Startup.cs的ConfigureServices方法中增加:

public void ConfigureServices(IServiceCollection services)
{
 services.AddDbContext<ApplicationDbContext>(options =>
  options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); //數(shù)據(jù)庫(kù)連接字符串
 services.AddIdentity<IdentityUser, IdentityRole>()
  .AddEntityFrameworkStores<ApplicationDbContext>()
  .AddDefaultTokenProviders();
 services.AddMvc();
 string connectionString = Configuration.GetConnectionString("DefaultConnection");
 var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
 services.AddIdentityServer()
  .AddDeveloperSigningCredential()
  .AddAspNetIdentity<IdentityUser>() 
  .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ù)庫(kù)):

/// <summary>
/// 初始數(shù)據(jù)庫(kù)
/// </summary>
/// <param name="app"></param>
private void InitializeDatabase(IApplicationBuilder app)
{
 using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
 {
  serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();//執(zhí)行數(shù)據(jù)庫(kù)遷移
  serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
  var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
  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");
 });
}

C#中單點(diǎn)登錄的示例分析

對(duì)于Client的身份認(rèn)證就簡(jiǎn)單了:

[Authorize]//身份認(rèn)證
public IActionResult Index()
{
 return View();
}

/// <summary>
/// 登出
/// </summary>
/// <returns></returns>
public async Task<IActionResult> Logout()
{
 await HttpContext.SignOutAsync("Cookies");
 await HttpContext.SignOutAsync("oidc");
 return View("Index");
}

效果圖:


C#中單點(diǎn)登錄的示例分析

以上是“C#中單點(diǎn)登錄的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI