如何使用Windows Identity Foundation(WTF)實現單點登錄

来源:https://www.cnblogs.com/JETSh/archive/2018/03/16/8583866.html
-Advertisement-
Play Games

資料參考來源 : 我姓區不姓區 有關於WIF的介紹以及環境配置在此不多說,可以去網上搜索,或者點擊上方鏈接前往查看,以下所述都基於WIF配置完成的條件上; 以下很多東西都是從 我姓區不姓區 的博客直接copy過來的,我另外加的就是我跟著他的博客一路中所踩的坑以及我自己的理解; 開始單點登錄踩坑之旅: ...


資料參考來源 : 我姓區不姓區

有關於WIF的介紹以及環境配置在此不多說,可以去網上搜索,或者點擊上方鏈接前往查看,以下所述都基於WIF配置完成的條件上;

以下很多東西都是從 我姓區不姓區 的博客直接copy過來的,我另外加的就是我跟著他的博客一路中所踩的坑以及我自己的理解;

開始單點登錄踩坑之旅:

我們接下來的demo將包括以下的工程:

  1. SiteA —— 基於.net framework 4.5的MVC 4程式,使用WIF 4.5的SDK,第一個RP
  2. SiteB —— 基於.net framework 4.5的MVC 4程式,使用WIF 3.5的SDK,第二個RP
  3. SiteC —— 基於.net framework 4.0的MVC 4程式,使用WIF 3.5的SDK,第三個RP
  4. SiteD —— 基於.net framework 4.0 的WebApplication程式,使用WIF 3.5的SDK,第四個RP
  5. STS —— 基於.net framework 4.5 的MVC 4程式,作為IP

一、創建第一個RP

以管理員身份打開vs2012,在起始頁上點擊“新建項目”,在左邊的“模板”樹下,展開“其它項目類型”,然後選擇“Visual Studio解決方案”,“名稱”輸入框里輸入WIFSSO,然後選擇解決方案的路徑後點擊”確定“,如圖:

在”解決方案資源管理器“中,在新建好的解決方案上點右鍵,選擇”添加“->”新建項目“。在彈出的對話框中選擇”ASP.NET MVC 4 Web應用程式“,記得.Net Framework版本選4.5,名稱起名為”SiteA“,然後點確定,如圖:

在彈出的“新ASP.NET MVC 4項目”對話框中直接點“確定”,第一個RP項目新建完成後,添加以下兩個引用:System.IdentityModel和System.IdentityModel.Services。這次的教程不使用Identity and Access Tool,而是直接修改web.config文件,這樣能使大家對WIF的配置有更深入的瞭解。

打開web.config文件,將configSections節里的entityFramework配置節點刪掉,因為我們不需要用到Entity Framework。最好把web.config中關於Entity Framework相關的配置全都刪掉,因為我們都用不上。然後加上以下這兩個節點:

<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />  
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />  

將authentication節的mode屬性設為None,並把裡面的form節點刪掉,因為我們採用的是WIF的身份驗證方式,而不是傳統的Forms身份驗證。然後增加authorization節點,不允許匿名用戶訪問站點:

<authorization>  
  <deny users="?"/>  
</authorization>  

在system.webServer節點下增加2個HttpModule的配置節點:

<modules>  
  <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />  
  <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />  
</modules>  

最後,增加WIF的配置節點:

<system.identityModel>  
  <identityConfiguration>  
    <audienceUris mode="Always">  
      <add value="http://www.sitea.com" />  
    </audienceUris>  
    <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">  
      <trustedIssuers>  
        <add name="http://www.sts.com" thumbprint="FD1425A2F30937786F46E52E43B01AFD54E5D64D"/>  
      </trustedIssuers>  
    </issuerNameRegistry>  
  </identityConfiguration>  
</system.identityModel>  
<system.identityModel.services>  
  <federationConfiguration>  
    <cookieHandler requireSsl="false" />  
    <wsFederation passiveRedirectEnabled="true" issuer="http://www.sts.com" realm="http://www.sitea.com" reply="http://www.sitea.com" requireHttps="false"/>  
  </federationConfiguration>  
</system.identityModel.services>  

我來詳細解釋一下這些節點的意義。audienceUris指定了一組可以被RP接受的身份標識URI,只有這些配置中的URI範圍內的令牌才可以被接受。這裡,我把siteA配置在這裡。trustedIssuers就是受信任的發行者,由於我們這個demo沒有用到SSL,所以這裡我指定的thumbprint是IIS Express的指紋,這個指紋在哪裡可以獲得呢?打開IIS管理器,在左側樹點擊根節點,然後在“功能視圖”里雙擊“伺服器證書",如下圖:

在打開的證書列表裡,找到IIS Express Development Certificate,雙擊,在彈出的”證書“對話框中點擊“詳細信息”頁簽,找到“指紋”然後點擊,把框里的指紋拷下來,全都改成大寫後粘貼到thumbnail的值里去:

接下來配置federationConfiguration節點,它表示配置WSFederationAuthenticationModule (WSFAM) 和SessionAuthenticationModule (SAM) 時使用聯合身份驗證通過的 WS 聯合身份驗證協議。這裡我們使用WS 聯合身份驗證的身份驗證模塊 (WSFAM),關於該節點的詳細配置信息,請參考:http://msdn.microsoft.com/zh-cn/library/office/apps/hh568665.aspx

好,這樣一來,SiteA的配置就已經完成了,然後我們來加點代碼。

打開/Views/Home/Index.cshtml,將原有的代碼刪掉,改為如下代碼:

 

@using System.Security.Claims  
@{  
    ViewBag.Title = "SiteA主頁";  
    ClaimsIdentity ci = User.Identity as ClaimsIdentity;  
    if(ci!=null)  
    {  
        <h2>@ci.FindFirst(ClaimTypes.Name).Value</h2>  
        <h2>@ci.FindFirst(ClaimTypes.Email).Value</h2>  
    }  
}  
  
<a href="http://www.sts.com/Account/LogOff">退出</a>  

代碼很簡單,只要當前用戶處於已登錄狀態,就把用戶的名稱和Email顯示在頁面上。

 

至此,SiteA就已經完成了。你是不是迫不及待的想要運行了呢?別急,雖然有SiteA了,但還沒有STS呢,現在啟動SiteA,由於沒登錄,所以它會跳轉到STS,但STS還不存在,所以會出錯的。

 

二、創建STS

接下來我們來創建STS,在解決方案上新建項目,新建一個名為STS的MVC 4應用程式,.Net Framework選擇4.5,項目模板選擇“Internet應用程式",確定。

添加System.IdentityModel和System.IdentityModel.Services這兩個引用,打開web.config,為forms節點添加兩個屬性:

<forms loginUrl="~/Account/Login" timeout="2880" slidingExpiration="true" name=".STSASPAUTH" />

在AppSettings里增加如下三個節點:

<add key="IssuerName" value="PassiveSigninSTS" />  
<add key="SigningCertificateName" value="CN=localhost" />  
<add key="EncryptingCertificateName" value="" />  

同樣禁止匿名用戶訪問:

<authorization>  
  <deny users="?"/>  
</authorization>  

在應用程式下新建一個名為Services的文件夾,在裡面新建一個類文件,名為:CertificateUtil,用於獲取證書,具體代碼如下:

public class CertificateUtil  
{  
    public static X509Certificate2 GetCertificate(StoreName name, StoreLocation location, string subjectName)  
    {  
        X509Store store = new X509Store(name, location);  
        X509Certificate2Collection certificates = null;  
        store.Open(OpenFlags.ReadOnly);  
  
        try  
        {  
            X509Certificate2 result = null;  
            certificates = store.Certificates;  
  
            for (int i = 0; i < certificates.Count; i++)  
            {  
                X509Certificate2 cert = certificates[i];  
                if (cert.SubjectName.Name.ToLower() == subjectName.ToLower())  
                {  
                    if (result != null)  
                        throw new ApplicationException(string.Format("subject Name {0}存在多個證書", subjectName));  
                    result = new X509Certificate2(cert);  
                }  
            }  
  
            if (result == null)  
            {  
                throw new ApplicationException(string.Format("沒有找到用於 subject Name {0} 的證書", subjectName));  
            }  
  
            return result;  
        }  
        finally  
        {  
            if (certificates != null)  
            {  
                for (int i = 0; i < certificates.Count; i++)  
                {  
                    certificates[i].Reset();  
                }  
            }  
            store.Close();  
        }  
    }  
  
}  

創建新類,名為Common,存放幾個常量:

public class Common  
{  
    public const string IssuerName = "IssuerName";  
    public const string SigningCertificateName = "SigningCertificateName";  
    public const string EncryptingCertificateName = "EncryptingCertificateName";  
  
}  

創建新類,名為SingleSignOnManager,用於註冊RP以及獲取RP列表:

public class SingleSignOnManager  
{  
    const string SITECOOKIENAME = "StsSiteCookie";  
    const string SITENAME = "StsSite";  
  
    /// <summary>  
    /// Returns a list of sites the user is logged in via the STS  
    /// </summary>  
    /// <returns></returns>  
    public static string[] SignOut()  
    {  
        if (HttpContext.Current != null &&  
             HttpContext.Current.Request != null &&  
             HttpContext.Current.Request.Cookies != null  
            )  
        {  
            HttpCookie siteCookie =  
                HttpContext.Current.Request.Cookies[SITECOOKIENAME];  
  
            if (siteCookie != null)  
                return siteCookie.Values.GetValues(SITENAME);  
        }  
  
        return new string[0];  
    }  
  
    public static void RegisterRP(string SiteUrl)  
    {  
        if (HttpContext.Current != null &&  
             HttpContext.Current.Request != null &&  
             HttpContext.Current.Request.Cookies != null  
            )  
        {  
            // get an existing cookie or create a new one  
            HttpCookie siteCookie =  
                HttpContext.Current.Request.Cookies[SITECOOKIENAME];  
            if (siteCookie == null)  
                siteCookie = new HttpCookie(SITECOOKIENAME);  
  
            siteCookie.Values.Add(SITENAME, SiteUrl);  
  
            HttpContext.Current.Response.AppendCookie(siteCookie);  
        }  
    }  
  
}  

創建新類,CustomSecurityTokenService,自定義令牌服務,繼承SecurityTokenService,用於返回需要的聲明令牌:

public class CustomSecurityTokenService : SecurityTokenService  
{  
    private readonly SigningCredentials signingCreds;  
    private readonly EncryptingCredentials encryptingCreds;  
  
    public CustomSecurityTokenService(SecurityTokenServiceConfiguration config)  
        : base(config)  
    {  
        this.signingCreds = new X509SigningCredentials(  
            CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, WebConfigurationManager.AppSettings[Common.SigningCertificateName]));  
  
        if (!string.IsNullOrWhiteSpace(WebConfigurationManager.AppSettings[Common.EncryptingCertificateName]))  
        {  
            this.encryptingCreds = new X509EncryptingCredentials(  
                CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, WebConfigurationManager.AppSettings[Common.EncryptingCertificateName]));  
        }  
    }  
  
    /// <summary>  
    /// 此方法返回要發佈的令牌內容。內容由一組ClaimsIdentity實例來表示,每一個實例對應了一個要發佈的令牌。當前Windows Identity Foundation只支持單個令牌發佈,因此返回的集合必須總是只包含單個實例。  
    /// </summary>  
    /// <param name="principal">調用方的principal</param>  
    /// <param name="request">進入的 RST,我們這裡不用它</param>  
    /// <param name="scope">由之前通過GetScope方法返回的範圍</param>  
    /// <returns></returns>  
    protected override ClaimsIdentity GetOutputClaimsIdentity(ClaimsPrincipal principal, RequestSecurityToken request, Scope scope)  
    {  
        //返回一個預設聲明集,裡面了包含自己想要的聲明  
        //這裡你可以通過ClaimsPrincipal來驗證用戶,並通過它來返回正確的聲明。  
        string identityName = principal.Identity.Name;  
        string[] temp = identityName.Split('|');  
        ClaimsIdentity outgoingIdentity = new ClaimsIdentity();  
        outgoingIdentity.AddClaim(new Claim(ClaimTypes.Email, temp[0]));  
        outgoingIdentity.AddClaim(new Claim(ClaimTypes.DateOfBirth, temp[1]));  
        outgoingIdentity.AddClaim(new Claim(ClaimTypes.Name, temp[2]));  
        SingleSignOnManager.RegisterRP(scope.AppliesToAddress);  
        return outgoingIdentity;  
    }  
  
    /// <summary>  
    /// 此方法返回用於令牌發佈請求的配置。配置由Scope類表示。在這裡,我們只發佈令牌到一個由encryptingCreds欄位表示的RP標識        /// </summary>  
    /// <param name="principal"></param>  
    /// <param name="request"></param>  
    /// <returns></returns>  
    protected override Scope GetScope(ClaimsPrincipal principal, RequestSecurityToken request)  
    {  
        // 使用request的AppliesTo屬性和RP標識來創建Scope  
        Scope scope = new Scope(request.AppliesTo.Uri.AbsoluteUri, this.signingCreds);  
  
        if (Uri.IsWellFormedUriString(request.ReplyTo, UriKind.Absolute))  
        {  
            if (request.AppliesTo.Uri.Host != new Uri(request.ReplyTo).Host)  
                scope.ReplyToAddress = request.AppliesTo.Uri.AbsoluteUri;  
            else  
                scope.ReplyToAddress = request.ReplyTo;  
        }  
        else  
        {  
            Uri resultUri = null;  
            if (Uri.TryCreate(request.AppliesTo.Uri, request.ReplyTo, out resultUri))  
                scope.ReplyToAddress = resultUri.AbsoluteUri;  
            else  
                scope.ReplyToAddress = request.AppliesTo.Uri.ToString();  
        }  
        if (this.encryptingCreds != null)  
        {  
            // 如果STS對應多個RP,要選擇證書指定到請求令牌的RP,然後再用 encryptingCreds   
            scope.EncryptingCredentials = this.encryptingCreds;  
        }  
        else  
            scope.TokenEncryptionRequired = false;  
        return scope;  
    }  
}  

最後添加新類CustomSecurityTokenServiceConfiguration,繼承SecurityTokenServiceConfiguration:

public class CustomSecurityTokenServiceConfiguration : SecurityTokenServiceConfiguration  
{  
    private static readonly object syncRoot = new object();  
    private const string CustomSecurityTokenServiceConfigurationKey = "CustomSecurityTokenServiceConfigurationKey";  
  
    public CustomSecurityTokenServiceConfiguration()  
        : base(WebConfigurationManager.AppSettings[Common.IssuerName])  
    {  
        this.SecurityTokenService = typeof(CustomSecurityTokenService);  
    }  
  
    public static CustomSecurityTokenServiceConfiguration Current  
    {  
        get  
        {  
            HttpApplicationState app = HttpContext.Current.Application;  
            CustomSecurityTokenServiceConfiguration config = app.Get(CustomSecurityTokenServiceConfigurationKey) as CustomSecurityTokenServiceConfiguration;  
            if (config != null)  
                return config;  
            lock (syncRoot)  
            {  
                config = app.Get(CustomSecurityTokenServiceConfigurationKey) as CustomSecurityTokenServiceConfiguration;  
                if (config == null)  
                {  
                    config = new CustomSecurityTokenServiceConfiguration();  
                    app.Add(CustomSecurityTokenServiceConfigurationKey, config);  
                }  
  
                return config;  
            }  
        }  
    }  
}  

打開/Controllers/HomeController.cs,將Index()方法修改如下:

public ActionResult Index()  
{  
    FederatedPassiveSecurityTokenServiceOperations.ProcessRequest(  
        System.Web.HttpContext.Current.Request,  
        User as ClaimsPrincipal,  
        CustomSecurityTokenServiceConfiguration.Current.CreateSecurityTokenService(),  
        System.Web.HttpContext.Current.Response);  
    return View();  
}  

打開/Controllers/AccountController.cs,將Login(LoginModel model, string returnUrl)方法修改如下:

        [HttpPost]  
        [AllowAnonymous]  
        [ValidateAntiForgeryToken]  
        public ActionResult Login(LoginModel model, string returnUrl)  
        {  
            var query = HttpUtility.ParseQueryString(Request.UrlReferrer.Query);  
            if (model.UserName == "[email protected]" && model.Password == "123456")  
            {  
                FormsAuthentication.SetAuthCookie("[email protected]|1983-10-22|oujian", false);  
                if (!string.IsNullOrEmpty(returnUrl))  
                    return Redirect(returnUrl);  
                return RedirectToAction("Index", "Home");  
            }  
            return View(model);  
        }  

LogOff方法修改如下:

 public ActionResult LogOff()  
        {  
            FormsAuthentication.SignOut();  
            ViewData["AddressesExpected"] = SingleSignOnManager.SignOut().Distinct().ToArray();  
            return View("Login");  
        }  

打開/Views/Account/Login.cshtml,添加以下代碼:

@{  
    ViewBag.Title = "登錄";  
    var addressesExpected = ViewData["AddressesExpected"] as string[];  
    if (addressesExpected != null)  
    {  
        foreach (var address in addressesExpected)  
        {  
    <img src="@(address)?wa=wsignoutcleanup1.0" style="display:none;" />  
        }  
    }  
  
}  

OK,至此STS也已經完成了。把SiteA和STS都部署到IIS上,然後打開C:\Windows\System32\Drivers\etc\hosts文件,添加幾個站點:

註意:更改host文件需要管理員許可權,否則是改動不了的;這個更改的作用是:將功能變數名稱指向的網址變成本地,有喜歡惡作劇的朋友可以把別人最喜歡的功能變數名稱網站指向到本地或者其他網站等等,哈哈,不知道這個東西的人會懵比的,哈哈;

127.0.0.1   www.sitea.com  
127.0.0.1   www.siteb.com  
127.0.0.1   www.sitec.com  
127.0.0.1   www.sited.com  
127.0.0.1   www.sts.com  

好了,在瀏覽器輸入www.sitea.com,看看如何,它馬上跳轉到了www.sts.com的登錄頁面,輸入[email protected],密碼123456,確定,登錄成功,跳回到了www.sitea.com,並顯示出了用戶名和Email:

點擊退出,將註銷當前用戶,並跳轉到登錄頁。

 

註意:以上是原博客中的原文,我在實踐的過程中曾報出一個問題:

錯誤:X.509 證書 CN=localhost 不在被信任的人的存儲中。 X.509 certificate CN=localhost 鏈生成失敗。所使用的證書具有無法驗證的信任鏈。請替換該證書或更改 certificateValidationMode。已處理證書鏈,但是在不受信任提供程式信任的根證書中終止;

開始看到這個問題是懵逼的,在網上搜索了好久都沒找到答案,多般曲折,最終還是找到了,http://www.cnblogs.com/pangguoming/p/5833009.html。我將localhost證書導出,然後在導入到受信任的根證書頒發機構,文件名寫CN=後面的東西,然後運行成功;

 

三、創建其它RP

OK,站點A搞定了,那其它站點如何呢?現在只是最簡單的登錄退出功能而已,說好的單點登錄呢?
別急,接下來就一一實現。
新建基於.NET Framework4.5的MVC4程式,添加Microsoft.IdentityModel引用。修改web.config,configSections里添加如下節點:

<section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />  

Compilation里增加Microsoft.IdentityModel的程式集:

<compilation debug="true" targetFramework="4.5" >  
  <assemblies>  
    <add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>  
  </assemblies>  
</compilation> 

身份驗證改為None,添加authorization節點,禁止匿名用戶訪問:

<authentication mode="None">  
</authentication>  
<authorization>  
  <deny users="?" />  
</authorization>  

添加三個httpModules:

<httpModules>  
      <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />  
      <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />  
      <add name="ClaimsAuthorizationModule" type="Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />  
    </httpModules>  
system.webServer里添加以下三個modules:  
    <modules >  
      <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />  
      <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />  
      <add name="ClaimsAuthorizationModule" type="Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />  
    </modules>  

最後增加microsoft.identityModel節點:

<microsoft.identityModel>  
   <service>  
     <audienceUris mode="Always">  
       <add value="http://www.siteb.com" />  
     </audienceUris>  
     <federatedAuthentication>  
       <wsFederation passiveRedirectEnabled="true" issuer="http://www.sts.com" realm="http://www.siteb.com" reply="http://www.siteb.com"  requireHttps="false" />  
       <cookieHandler requireSsl="false" />  
     </federatedAuthentication>  
     <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">  
       <trustedIssuers>  
         <add thumbprint="FD1425A2F30937786F46E52E43B01AFD54E5D64D" name="http://www.sts.com" />  
       </trustedIssuers>  
     </issuerNameRegistry>  
   </service>  
 </microsoft.identityModel>  

以上配置跟SIteA差不多,只是WIF3.5和4.5的區別而已,在這裡就不贅述了,要獲取詳細信息,請參考微軟官方網站。
打開/Views/Home/Index.cshtml,將代碼修改如下,在SiteB里我們顯示Email和生日:

@using Microsoft.IdentityModel.Claims  
@{  
    ViewBag.Title = "SiteB主頁";  
    ClaimsIdentity ci = User.Identity as ClaimsIdentity;  
    if(ci!=null)  
    {  
        <h2>@ci.Claims.SingleOrDefault(c=>c.ClaimType == ClaimTypes.Email).Value</h2>  
        <h2>@ci.Claims.SingleOrDefault(c=>c.ClaimType == ClaimTypes.DateOfBirth).Value</h2>  
    }  
}  
  
  
<a href="http://www.sts.com/Account/LogOff">退出</a>  

OK,部署到IIS上,然後運行,頁面跳轉到了sts的登錄頁面,輸入用戶名和密碼,跳轉,哎喲我去,怎麼報錯了:

原因是從sts返回來的數據里有<>這種標簽,於是asp.net認為那是有危險的,於是拋出了異常,這個異常大家估計以前也碰到過,最簡單粗暴的方法就是把驗證請求的配置改為false,但這裡我不建議這麼乾, 為此,我們專門用一個類來處理這種情況。
在SiteB目錄下新建一個文件夾名為Services,然後添加一個類,名為SampleRequestValidator:

/// <summary>  
  /// This SampleRequestValidator validates the wresult parameter of the  
  /// WS-Federation passive protocol by checking for a SignInResponse message  
  /// in the form post. The SignInResponse message contents are verified later by  
  /// the WSFederationPassiveAuthenticationModule or the WIF signin controls.  
  /// </summary>  
  
  
  public class SampleRequestValidator : RequestValidator  
  {  
      protected override bool IsValidRequestString(HttpContext context, string value, RequestValidationSource requestValidationSource, string collectionKey, out int validationFailureIndex)  
      {  
          validationFailureIndex = 0;  
  
  
          if (requestValidationSource == RequestValidationSource.Form && collectionKey.Equals(WSFederationConstants.Parameters.Result, StringComparison.Ordinal))  
          {  
                  return true;  
          }  
  
  
          return base.IsValidRequestString(context, value, requestValidationSource, collectionKey, out validationFailureIndex);  
      }  
  
  
  }  

然後在web.config裡加入這個類的配置:

<httpRuntime targetFramework="4.5" requestValidationType="SiteC.Services.SampleRequestValidator" />  

重新運行程式,非常完美:

這時候再打開SIteA,發現也已經處於了登錄狀態,這時候在SiteA點擊退出,跳轉到了登錄頁,再看看這時候的SiteB呢,刷新SiteB首頁,發現也跳轉到了登錄頁,證明在SiteA的退出操作對SiteB也起了作用,確實是單點登錄了!
SiteC和SiteD的配置與SiteB類似,這裡我就不重覆了,留給大家自己練習一下,等所有的項目都配置好以後,在任意站點登錄,發現其它站點也是登錄狀態;在任意站點退出,發現其它站點也已經退出。利用WIF,單點登錄變的如此簡單~~

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 眾所周知python中單引號和雙引號常常被我們所使用,例如print、input等等。 但是對於列印輸出所引導的字元串大多都是用雙引號的形式來做,"Hello,python!",而單引號多(三個單引號)是用來註釋代碼用。 我們一旦遇到了 包含多個單引號和雙引號的字元串的話,系統就會自動判定引號節點, ...
  • 在學習Spring Aop時,遇到一個問題,當 @Around(環繞通知)與 @AfterReturning(後置通知)共存 時,@AfterReturning 通過屬性 returning = "var" 獲取目標方法的返回值時結果總為null,如下: 介面代碼: 目標類代碼: 切麵代碼: 運行結 ...
  • “Hello World”,這大概是每個程式員最熟悉的一句話了吧。每每我們進入一種語言的世界,第一句話就是向它問好。仔細想來,也許這就是我們的一種態度,一種出於習慣的禮貌,或者說是一種出於禮貌的問候吧。“世界,你好!”。短短的一句話,意味著從那一刻起,我們便正式進入了某一種語言的世界,所以也可以說是 ...
  • 實驗結論 題目1:輸入 1~7 的整數,如果輸入的是 1~5,則輸出“workday. Let’s work hard”;如果輸入的是 6~7,則輸出“weekend. Let’s have a rest.” 源代碼(1)及運行結果 源代碼(2)及運行結果 題目2:輸入一個整數,將各位數字反轉後輸出 ...
  • 對磁碟文件進行排序,文件包含最多一千萬條記錄,每條記錄都是7位的整數,無其他相關數據,每個整數只出現一次,由於某種系統需要,只能提供1MB左右記憶體。由於是實時系統,最多運行幾分鐘就能給出回應,十秒鐘是比較理想的運行時間。 準確的問題描述: 輸入:一個包含n個正整數的文件,每個數都小於n,其中n=10 ...
  • 簡單使用 Mvc 內置的 Ioc(續) 本文基於 .NET Core 2.0。 上一章《[.Net Core] 簡單使用 Mvc 內置的 Ioc》已經對日常 Mvc 中的 Ioc 的簡單用法進行了說明,此外還有一些需要補充的內容。 接下來會圍繞著這些疑問進行回答:AOP 中 Filter 和 Ioc ...
  • 1.1 需求問題的提出 五個與需求有關的敗因描述:(1)不完整的需求(2)缺乏用戶參與(3)不切實際的用戶期望(4)需求變更頻繁(5)提供了不再需要的需求 1.2 不同項目的需求視圖 不同的軟體項目具有不同的特點,這對需求也帶來了影響,在此主要從信息系統、嵌入式系統、軟體產品等不同角度說明如何進行相 ...
  • 無法在web伺服器上啟動調試,您沒有調試web伺服器進程的許可權,您需要以web伺服器的用戶賬戶身份運行,或者具有管理員許可權。 原因:從Windows NT 6.1開始,既Windows Vista版本開始,增強了用戶控制,尤其是Windows 8。如果沒有用系統優化軟體做過用戶控制,系統許可權調整,即 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...