簡體   English   中英

具有 Asp.Net 會員資格的 ADFS 單點登錄

[英]ADFS Single Sign-On With Asp.Net Membership

這就是挑戰——我維護了一個混合的 asp.net mvc/web 表單應用程序,它使用表單身份驗證和舊的 asp.net 成員身份提供程序(aspnet_Users、aspnet_Membership 等)。 我們公司正在轉向使用 ADFS 的單點登錄。 我們必須更改混合 asp.net 應用程序以使用 ADFS 進行身份驗證。

我的問題是,我是否可以更改混合 asp.net 應用程序以使用 ADFS 進行身份驗證,但繼續使用現有的成員身份提供程序來處理授權?

這個計划會奏效嗎? 我的假設正確嗎?

  1. 使用此鏈接中所述的 Windows Identity Foundation 4.5 被動重定向: https : //docs.microsoft.com/en-us/dotnet/framework/security/how-to-build-claims-aware-aspnet-mvc-web-app - 使用-wif 未經身份驗證的用戶將自動重定向到我們的 ADFS 安全令牌服務器。

  2. 在 asp.net 網站中,從 ADFS 令牌中讀取經過身份驗證的用戶的用戶名並調用 FormsAuthentication.SetAuthCookie 以使成員資格提供程序可用。 這將在基頁類(對於 Web 表單)或自定義授權屬性(對於 mvc 控制器,覆蓋 AuthorizeCore)中完成。 對於特定用戶只會進行一次調用,我將使用 Session 變量來跟蹤是否已進行調用。

部分歸結為這個問題:由於我們將使用 ADFS 進行身份驗證,因此 asp.net 網站的 web.config 將具有“無”身份驗證模式並拒絕所有匿名用戶。 使用此 web.config 設置,單獨調用 FormsAuthentication.SetAuthCookie 是否會啟用成員身份提供程序? 或者會員提供商是否要求將身份驗證模式設置為“表單”?

如果您想知道“為什么不嘗試一下?”,那是因為 ADFS 服務器將在幾個月內無法使用,但我現在負責制定開發計划。 我確實知道,如果我只是采用常規的 asp.net mvc 應用程序,請將身份驗證模式設置為“無”並使用正確的用戶名和密碼調用 Membership.ValidateUser,然后調用 FormsAuthentication.SetAuthCookie 成員資格提供商似乎確實如此正常工作,雖然 Request.IsAuthenticated 當然是假的,所以我沒有方便的方法來進行完整的測試,因為每次授權檢查首先查看用戶是否在查看角色之前通過身份驗證。

事實證明,這出奇地簡單。 我使用的是 Framework 4.7 和 Windows Server 2016。注意——其他版本的 Framework 和 Windows Server 有完全不同的說明。

按照以下步驟操作后,我成功地將 ADFS 集成到使用 Membership 的 asp.net Web 應用程序中。 對 Membership 數據庫的所有現有調用都有效(例如,Membership.Getuser()、Roles.GetRolesForUser())。 此外,System.Threading.Thread.CurrentPrincipal.Identity 是全功能的。 IsInRole() 和 [Authorize] 屬性等調用無需更改即可工作。

這不是一個詳細的演練,只是對我必須在 Web 應用程序中更改的內容的粗略描述(設置 ADFS 是完全獨立的事情)。

設置 ADFS 后,在指向 ADFS 的 Web 應用程序中創建一個 FederationMetadata.xml 文件。 Google 以獲取有關創建 FederationMetadata.xml 文件的說明。 注意:不要使用 Framework 3.5 Windows Identity Federation Utility 來創建您的 FederationData.xml; 該實用程序將更改您的 web.config 以使用已棄用的 Micorsoft.Identity 庫。 相反,您需要使用 System.Identity 庫。 我的 FederationMetadata.xml 文件如下所示:

<?xml version="1.0" encoding="utf-8"?>
<EntityDescriptor ID="_ff25f54f-e839-4005-9dc5-bb598b34a50d" entityID="https://MyServer.MyCompany.com/ADFSAuthentication/" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
  <RoleDescriptor xsi:type="fed:ApplicationServiceType" xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706" protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <fed:TargetScopes>
      <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
        <wsa:Address>https://MyServer.MyCompany.com/ADFSAuthentication/</wsa:Address>
      </wsa:EndpointReference>
      <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
        <wsa:Address>https://MyServer.MyCompany.com/ADFSAuthentication/</wsa:Address>
      </wsa:EndpointReference>
    </fed:TargetScopes>
    <fed:PassiveRequestorEndpoint>
      <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
        <wsa:Address>https://MyServer.MyCompany.com/ADFSAuthentication/</wsa:Address>
      </wsa:EndpointReference>
    </fed:PassiveRequestorEndpoint>
  </RoleDescriptor>
</EntityDescriptor>

在您的 web.config 中: 1. 使您的 FederationMetadata 文件可見

<location path="FederationMetadata">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
  1. 關閉身份驗證,並拒絕所有未經授權的用戶。

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

    ...

  2. 為方便起見,保留您現有的成員資格和角色提供者。 在角色提供程序中設置 cacheRolesInCookie="false"。 角色現在由 SessionAuthenticationModule 維護。 如果您在 cookie 中緩存角色,您將破壞 SessionAuthenticationModule。

  3. 添加 system.Identity 所需的 appsettings,指向 ADFS。

     <appSettings> <add key="ida:FederationMetadataLocation" value="https://adfs.MyCompany.com/federationmetadata/2007-06/FederationMetadata.xml" /> <add key="ida:Issuer" value="http://adfs.MyCompany.com/adfs/ls/" /> <add key="ida:ProviderSelection" value="productionSTS" /> <add key="ida:EnforceIssuerValidation" value="false" />

    ...

  4. 將 WSFederationAuthenticationModule 和 SessionAuthenticationModule 添加到您的 system.Webserver 標記。 SO 站點中的某些錯誤不允許我在此處添加標簽,因此我將添加該標簽作為下面的評論。 WSFederationAuthenticationModule 中斷 401 授權拒絕 HTTP 響應並重定向到 ADFS。 ADFS 頒發安全令牌。 WSFederationAuthenticationModule 使用該令牌、創建 ClaimsPrincipal 並將該 ClaimsPrincipal 存儲在 cookie 中(這將瀏覽器標記為已通過身份驗證)。 在每次回發時,SessionAuthenticationModule 都使用該 cookie 來重建 ClaimsPrincipal(因此,用戶不需要在每次回發時使用 ADFS 重新進行身份驗證)。 諸如“IsAuthenticated”和“IsInRole()”以及 [Authorize] 標簽之類的調用都可以從這個 ClaimsPrincipal 對象工作。

  5. 設置您的 system.IdentityModel 標記以與您的 ADFS 通信。 完整說明超出了本答案的范圍,但這是我的標簽的樣子:

     <system.identityModel> <identityConfiguration> <audienceUris> <add value="https://MyServer.MyCompany.com/ADFSAuthentication/" /> </audienceUris> <!--certificationValidationMode set to "None" by the the Identity and Access Tool for Visual Studio. For development purposes.--> <certificateValidation certificateValidationMode="None" /> <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry"> <authority name="http://adfs.MyCompany.com/adfs/services/trust"> <keys> <add thumbprint="MyGuid" /> </keys> <validIssuers> <add name="http://adfs.MyCompany.com/adfs/services/trust" /> </validIssuers> </authority> </issuerNameRegistry> <securityTokenHandlers> <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </securityTokenHandlers> </identityConfiguration>

  6. 保留指向您的成員資格數據庫的現有連接字符串。 這通常默認為“LocalSqlServer”:

     <connectionStrings> <clear /> <add name="LocalSqlServer" connectionString="Server=MyServer; Database=MyMembershipDatabase; Integrated Security=SSPI;" providerName="System.Data.SqlClient" />

  7. 在 global.asax 中的 Authenticate_Request 事件中從成員資格數據庫加載用戶的角色。 在我們的例子中,我們使用 Active Directory“objectGUID”作為 AD 用戶的唯一標識符; 我們向會員數據庫中的 dbo.aspnet_users 表添加了一列,以將 AD 用戶與會員用戶聯系起來。 從那里,它是一個簡單的 SQL 調用,將角色從 Membership 數據庫加載到 CurrentPrincipal.Identity,方法是將后者轉換為 ClaimsIdentity。 在下面的示例中,我添加了 3 個硬編碼角色,但實際上這些角色將使用 objectGUID 從我們的會員數據庫中檢索。

     protected void Application_AuthenticateRequest(Object sender, EventArgs e) { var currentPrincipalIdentity = (System.Security.Claims.ClaimsIdentity)System.Threading.Thread.CurrentPrincipal.Identity; var claims = currentPrincipalIdentity.Claims.ToList(); //if WSFederationAuthenticationModule just fired (aka user's first visit) the claims have not been loaded yet. //if SessionAuthenticationModule just fired (aka the user has a valid security token cookie) then no need to reload the claims, they are a part of Thread.CurrentPrincipal if (!claims.Exists(o => o.Type == "MyCompany/objectGUID_decoded")) { //get the encoded guid. if this does not exist exit immediately, the user has no business in our web site var encodedGuidClaim = claims.FirstOrDefault(o => o.Type == "MyCompany/objectGUID"); if (encodedGuidClaim == null) return; currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("MyCompany/objectGUID_decoded", new Guid(Convert.FromBase64String(encodedGuidClaim.Value)).ToString())); //we will need a new column or table in membership database to link users to the ActiveDirectory objectGUID. //if the user has multiple identities we will load the default (the default must exist) //for this example I am hard-coding the MyCompany/userID guid, but in fact it will be the single or default userID guid for the user currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("MyCompany/userID", "310860D2-6329-41B7-AF44-E8DC2113B4C7")); //for this example I am hard-coding the roles, but in face we will load the user's roles from database using the userId retrieved in the line above. //when user changes identity then we need to write a new cookie with the new roles collection, see ExampleOfHowToChangeIdentity() above. currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "MyRole1")); currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "MyRole2")); currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "MyRole3")); } }
  8. 在我們的應用程序中,單個 Active Directory 用戶可以具有多個成員身份。 有時用戶會想要更改會員身份。 這很容易做到:

     private void ExampleOfHowToChangeIdentity(Guid newIdentity) { //assume that a user has multiple identies and is logged in as the default. //the user now selects a new identity var currentPrincipalIdentity = (System.Security.Claims.ClaimsIdentity)System.Threading.Thread.CurrentPrincipal.Identity; var allClaims = currentPrincipalIdentity.Claims.ToList(); //first remove all of the roles from old identity var allRoles = allClaims.Where(o => o.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").ToList(); foreach (var role in allRoles) { currentPrincipalIdentity.RemoveClaim(role); } //second, fetch the new claims from the database using the newIdentity //we will have a column or table in the Membership database that matches this guid to the UserId //below I am hard-coding some new claims, but in fact they will be added from a database call. currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "Nonesuch")); currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "Nonesuch2")); currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "Nonesuch3")); //third, replace the MyCompany/userID claim with that of the new identity //this will always be hard-coded. this is read by Application_AuthenticateRequest each time the user visits the site currentPrincipalIdentity.RemoveClaim(allClaims.Single(o => o.Type == "MyCompany/userID")); currentPrincipalIdentity.AddClaim(new System.Security.Claims.Claim("MyCompany/userID", newIdentity.ToString())); //four, create a new session security token //cast to pass into session security token constructor var claimsPrincipal = new System.Security.Claims.ClaimsPrincipal(currentPrincipalIdentity); var token = new System.IdentityModel.Tokens.SessionSecurityToken(claimsPrincipal); System.IdentityModel.Services.FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(token); }

這種方法適用於 WIF,但如果您使用的是 ADFS 4.0,您也可以使用 OWIN Katana 和 OpenID Connect。

OWIN 管道允許多個連接,例如這個

或者,您可以使用支持 ASP.NET 成員身份的身份服務器之類的東西,並且您可以將其與 ADFS 聯合。 然后,identityserver 將有兩個按鈕,用戶可以選擇他們可以使用哪個按鈕進行身份驗證。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM