1. 程式人生 > >淺析C#中單點登入的原理和使用

淺析C#中單點登入的原理和使用

是單點登入?
我想肯定有一部分人“望文生義”的認為單點登入就是一個使用者只能在一處登入,其實這是錯誤的理解(我記得我第一次也是這麼理解的)。
單點登入指的是多個子系統只需要登入一個,其他系統不需要登入了(一個瀏覽器內)。一個子系統退出,其他子系統也全部是退出狀態。
如果你還是不明白,我們舉個實際的例子把。比如部落格園首頁:https://www.cnblogs.com,和部落格園的找找看http://zzk.cnblogs.com。這就是兩個系統(不同的域名)。如果你登入其中一個,另一個也是登入狀態。如果你退出一個,另一個也是退出狀態了。
那麼這是怎麼實現的呢?這就是我們今天要分析的問題了。

單點登入(SSO)原理

  • 首先我們需要一個認證中心(Service),和兩個子系統(Client)。

  • 當瀏覽器第一次訪問Client1時,處於未登入狀態 -> 302到認證中心(Service) -> 在Service的登入頁面登入(寫入Cookie記錄登入資訊) -> 302到Client1(寫入Cookie記錄登入資訊)

  • 第二次訪問Client1 -> 讀取Client1中Cookie登入資訊 -> Client1為登入狀態

  • 第一次訪問Client2 -> 讀取Client2中Cookie中的登入資訊 -> Client2為未登入狀態 -> 302到在Service(讀取Service中的Cookie為登入狀態) -> 302到Client2(寫入Cookie記錄登入資訊)

我們發現在訪問Client2的時候,中間時間經過了幾次302重定向,並沒有輸入使用者名稱密碼去登入。使用者完全感覺不到,直接就是登入狀態了。

圖解:
640?wx_fmt=png&wxfrom=5&wx_lazy=1

手擼一個SSO

環境:.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:驗證使用者名稱密碼登入    {        //用Session標識會話是登入狀態        Session["user"] = "XX已經登入";        //在認證中心 儲存客戶端Client的登入認證碼        TokenIds.Add(Session.SessionID, Guid.NewGuid());    }    else//驗證失敗重新登入    {        return "/Home/Login";    }    return backUrl + "?tokenId=" + TokenIds[Session.SessionID];//生成一個tokenId 發放到客戶端}

Client:

public static List<string> Tokens = new List<string>();

public async Task<ActionResult> Index(){  
 var tokenId = Request.QueryString["tokenId"];    //如果tokenId不為空,則是由Service302過來的。    if (tokenId != null)    {        
        using (HttpClient http = new HttpClient())        {            //驗證Tokend是否有效            var isValid = await http.GetStringAsync("http://localhost:8018/Home/TokenIdIsValid?tokenId=" + tokenId);          
          if (bool.Parse(isValid.ToString()))            {        
                 if (!Tokens.Contains(tokenId))                {                    //記錄登入過的Client (主要是為了可以統一登出)                    Tokens.Add(tokenId);
                 }                Session["token"] = tokenId;            }        }    }    //判斷是否是登入狀態    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(); }

效果圖:
0?wx_fmt=gif

當然,這只是用較少的程式碼擼了一個較簡單的SSO。僅用來理解,勿用於實際應用。

IdentityServer4實現SSO

環境:.NET Core 2.0
上面我們手擼了一個SSO,接下來我們看看.NET裡的IdentityServer4怎麼來使用SSO。
首先建一個IdentityServer4_SSO_Service(MVC專案),再建兩個IdentityServer4_SSO_Client(MVC專案)
在Service專案中用nuget匯入IdentityServer4 2.0.2IdentityServer4.AspNetIdentity 2.0.0IdentityServer4.EntityFramework 2.0.0
在Client專案中用nuget匯入IdentityModel 2.14.0
然後分別設定Service和Client專案啟動埠為 5001(Service)、5002(Client1)、5003(Client2)
0?wx_fmt=png
在Service中新建一個類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")
        };
    }    // 可以訪問的客戶端
    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
                }
            };
        }
}

新增一個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中配置資料庫連線字串:

"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"))); //資料庫連線字串
    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檔案裡新增一個方法InitializeDatabase(初始化資料庫):

/// <summary>/// 初始資料庫/// </summary>/// <param name="app"></param>private void InitializeDatabase(IApplicationBuilder app){    using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
    {
        serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();//執行資料庫遷移
        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())//迴圈新增 我們直接新增的 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)
 {     //初始化資料
     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?}");     }); }

然後新建一個AccountController控制器,分別實現註冊、登入、登出等。
新建一個ConsentController控制器用於Client回撥。
然後在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");
    });
}

0?wx_fmt=png
對於Client的身份認證就簡單了:

[Authorize]//身份認證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");
}

效果圖:
0?wx_fmt=gif

原始碼地址(demo可配置資料庫連線後直接執行)

  • https://github.com/zhaopeiym/BlogDemoCode/tree/master/sso(%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95)

推薦閱讀

  • http://www.cnblogs.com/ywlaker/p/6113927.html

  • https://identityserver4.readthedocs.io/en/release

相關文章:

原文地址:http://www.cnblogs.com/zhaopei/p/SSO.html

.NET社群新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注

640?wx_fmt=jpeg

相關推薦

淺析C#登入原理使用

是單點登入? 我想肯定有一部分人“望文生義”的認為單點登入就是一個使用者只能在一處登入,其實這是錯誤的理解(我記得我第一次也是這麼理解的)。 單點登入指的是多個子系統只需要登入一個,其他系統不需要登入了(一個瀏覽器內)。一個子系統退出,其他子系統也全部是退出狀態。 如果你還是不明白,我們舉個實際的例子把。

SSO登入原理簡單實現

1、http無狀態協議   web應用採用browser/server架構,http作為通訊協議。http是無狀態協議,瀏覽器的每一次請求,伺服器會獨立處理,不與之前或之後的請求產生關聯,這個過程用下圖說明,三次請求/響應對之間沒有任何聯絡   但這也同時意味著,任何使用者都能通過瀏覽器訪問伺服器資源,如

登入原理與簡單實現學習

一、單系統登入機制 1、http無狀態協議   web應用採用browser/server架構,http作為通訊協議。http是無狀態協議,瀏覽器的每一次請求,伺服器會獨立處理,不與之前或之後的請求產生關聯,這個過程用下圖說明,三次請求/響應對之間沒有任何聯絡   但這也同時意味著

登入原理與簡單實現

一、單系統登入機制 1、http無狀態協議 web應用採用browser/server架構,http作為通訊協議。http是無狀態協議,瀏覽器的每一次請求,伺服器會獨立處理,不與之前或之後的請求產生關聯,這個過程用下圖說明,三次請求/響應對之間沒有任何聯絡 但這也同

登入原理及實現(共享)

單點登入原理及實現 隨著業務發展,公司業務會不斷壯大,每個業務都會存在使用者登入和許可權驗證,不可能要求使用者每個業務網站都登入一次,這個時候,就需要單點登入功能。下面將先介紹基本概念,然後以百度(baidu.com)為例進行講解,最後用一個小例子講解如何實現(

CAS登入原理簡單介紹

1. SSO簡介 1.1 單點登入定義 單點登入(Single sign on),英文名稱縮寫SSO,SSO的意思就是在多系統的環境中,登入單方系統,就可以在不用再次登入的情況下訪問相關受信任的系統。也就是說只要登入一次單體系統就可以。計劃在專案中加入單點登入,

cas登入原理簡單介紹(1)

SSO簡介 1.1 單點登入定義 單點登入(Single sign on),英文名稱縮寫SSO,SSO的意思就是在多系統的環境中,登入單方系統,就可以在不用再次登入的情況下訪問相關受信任的系統。也就是說只要登入一次單體系統就可以。計劃在專案中加入單點登入,開發

C++虛擬函式工作原理 虛 繼承類的記憶體佔用大小計算

                      虛擬函式的實現要求物件攜帶額外的資訊,這些資訊用於在執行時確定該物件應該呼叫哪一個虛擬函式。典型情況下,這一資訊具有一種被稱為 vptr(virtual table pointer,虛擬函式表指標)的指標的形式。vptr 指向一個被稱為 vtbl(virtual t

CAS登入原理分析

一,業務分析 在分散式系統架構中,假設把上述的三個子系統部署在三個不同的伺服器上。前提是使用者登入之後才能訪問這些子系統。那麼使用傳統方式,可能會存在這樣的問題: 1.當訪問使用者中心,需要使用者登入帳號 2.當訪問購物車,還需要使用者登入帳號 3.當訪問商品

登入原理與簡單實現(轉) 登入原理與簡單實現

單點登入原理與簡單實現   (2017-09-22更新)GitHub:https://github.com/sheefee/simple-sso 一、單系統登入機制 1、http無狀態協議   web應用採用browser/server架構,http作為通訊

sso登入原理詳解(最後解說的比較好)

yale cas可以百度一下,這是學習cas後的一點總結,以備日後使用!安全性:使用者只須在cas錄入使用者名稱和密碼,之後通過ticket繫結使用者,在cas客戶端與cas校驗是通過ticket,並不會在網上傳輸密碼,所以可以保證安全性,密碼不被竊取原理:1個cookie+

CAS登入原理解析

1、基於Cookie的單點登入的回顧        基於Cookie的單點登入核心原理:       將使用者名稱密碼加密之後存於Cookie中,之後訪問網站時在過濾器(filter)中校驗使用者許可權,如果沒有許可權

CAS實現SSO登入原理

1.      CAS簡介 CAS(Central Authentication Service) 是 Yale大學發起的一個企業級的、開源的專案,旨在為 Web 應用系統提供一種可靠的單點登入解決方法(屬於Web SSO)。 CAS開始於2001年, 並在 2004年

SSO登入原理解析

一、單系統登入原理解析 一、無狀態協議http會話         客戶端每發出一次請求,伺服器就會獨立的進行處理,而當客戶端過了一定的時間之後再次向伺服器發出請求,伺服器依然進行單獨的處理,而不與之前和之後的請求產生聯絡

cas登入原理

  一、所需軟體 Jdk:jdk1.6.0_13 Apache:httpd-2.2.15-win32-x86-openssl-0.9.8m-r2 Tomcat:apache-tomcat-6.0.10 memcache:memcached-1.2.1-win32(需要memcache叢集環境) 二

[C/C++]C++虛擬函式的原理虛擬函式表

#include using namespace std; class A{     public:     A();     virtual void fun1();     void fun2(); }; A::A() { } void A::fun1() {     cout<<"I am

shiro 登入原理 例項

Shiro 1.2開始提供了Jasig CAS單點登入的支援,單點登入主要用於多系統整合,即在多個系統中,使用者只需要到一箇中央伺服器登入一次即可訪問這些系統中的任何一個,無須多次登入。 Jasig CAS單點登入系統分為伺服器端和客戶端,伺服器端提供單點登入,多個客戶端

一張圖看明白CAS登入原理

最近工作上用到了CAS,研究了下,折騰的心累.關於CAS單點登入的原理或者說認證流程,網上查到的資料上大部分都扯得不是很清晰,看的雲裡霧裡.所以就花了些時間專門去整理了下畫了這張時序圖,用的是一個線上的工具,對UML的支援不怎麼完善,因此畫出來的就顯得不是很專業

Java登入的實現——類似QQ“頂號”操作

簡介   對於目前的網路環境而言,在開發的系統中建立一個完善的賬號系統尤為重要。而其中的一個手段就是進行多點登入的限制。類似於騰訊qq應用軟體的機制,在其他裝置上登入自己賬號的時候,當前登入會被踢出。這樣就避免了一些自己本身的失誤或者一些惡意的賬號攻擊。

CAS框架登入原理解析

單點登入:Single Sign On,簡稱SSO,SSO使得在多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統。 CAS框架:CAS(Central Authentication Service)是實現SSO單點登入的框架。 CSA