简体   繁体   English

具有 Asp.Net 会员资格的 ADFS 单点登录

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

Here is the challenge—I maintain a hybrid asp.net mvc/web forms application which uses forms authentication and the old asp.net Membership provider (aspnet_Users, aspnet_Membership and so on).这就是挑战——我维护了一个混合的 asp.net mvc/web 表单应用程序,它使用表单身份验证和旧的 asp.net 成员身份提供程序(aspnet_Users、aspnet_Membership 等)。 Our company is moving to single-sign-on using ADFS.我们公司正在转向使用 ADFS 的单点登录。 We must alter the hybrid asp.net application to authenticate using ADFS.我们必须更改混合 asp.net 应用程序以使用 ADFS 进行身份验证。

My question is, can I alter the hybrid asp.net application to use ADFS for authentication, but keep using the existing Membership provider to handle authorization?我的问题是,我是否可以更改混合 asp.net 应用程序以使用 ADFS 进行身份验证,但继续使用现有的成员身份提供程序来处理授权?

Will this plan work?这个计划会奏效吗? Are my assumptions correct?我的假设正确吗?

  1. Use Windows Identity Foundation 4.5 passive redirection as described in this link: https://docs.microsoft.com/en-us/dotnet/framework/security/how-to-build-claims-aware-aspnet-mvc-web-app-using-wif .使用此链接中所述的 Windows Identity Foundation 4.5 被动重定向: https : //docs.microsoft.com/en-us/dotnet/framework/security/how-to-build-claims-aware-aspnet-mvc-web-app - 使用-wif Unauthenticated users will automatically be redirected to our ADFS security token server.未经身份验证的用户将自动重定向到我们的 ADFS 安全令牌服务器。

  2. In the asp.net web site, read the authenticated user's username from the ADFS token and call FormsAuthentication.SetAuthCookie to make the Membership provider available.在 asp.net 网站中,从 ADFS 令牌中读取经过身份验证的用户的用户名并调用 FormsAuthentication.SetAuthCookie 以使成员资格提供程序可用。 This would be done in a base page class (for web forms) or the custom authorize attribute (for mvc controllers, overriding AuthorizeCore).这将在基页类(对于 Web 表单)或自定义授权属性(对于 mvc 控制器,覆盖 AuthorizeCore)中完成。 The call would only be made once for a particular user, and I will use a Session variable to track whether or not the call has been made.对于特定用户只会进行一次调用,我将使用 Session 变量来跟踪是否已进行调用。

In part it boils down to this question: since we will be using ADFS for authentication the web.config for the asp.net web site will have an authentication mode of "None" and deny all anonymous users.部分归结为这个问题:由于我们将使用 ADFS 进行身份验证,因此 asp.net 网站的 web.config 将具有“无”身份验证模式并拒绝所有匿名用户。 With this web.config setting, will the call to FormsAuthentication.SetAuthCookie alone enable the Membership provider?使用此 web.config 设置,单独调用 FormsAuthentication.SetAuthCookie 是否会启用成员身份提供程序? Or does the Membership provider require that the authentication mode be set to "Forms"?或者会员提供商是否要求将身份验证模式设置为“表单”?

In case you are wondering "why don't you just try it?", it is because the ADFS server will not be available for several months, but I'm charged with coming up with a development plan now.如果您想知道“为什么不尝试一下?”,那是因为 ADFS 服务器将在几个月内无法使用,但我现在负责制定开发计划。 I do know that if I merely take a regular asp.net mvc application, set the authentication mode to "None" and make the calls to Membership.ValidateUser with the correct username and password and then to FormsAuthentication.SetAuthCookie the membership provider does seem to work properly, although Request.IsAuthenticated is of course false, so I've no convenient way to give this a full test, since every Authorization check first looks to see if the user is authenticated before looking at roles.我确实知道,如果我只是采用常规的 asp.net mvc 应用程序,请将身份验证模式设置为“无”并使用正确的用户名和密码调用 Membership.ValidateUser,然后调用 FormsAuthentication.SetAuthCookie 成员资格提供商似乎确实如此正常工作,虽然 Request.IsAuthenticated 当然是假的,所以我没有方便的方法来进行完整的测试,因为每次授权检查首先查看用户是否在查看角色之前通过身份验证。

This turned out to be surprisingly simple.事实证明,这出奇地简单。 I'm using Framework 4.7 and Windows Server 2016. Caution--other versions of the Framework and Windows Server have completely different instructions.我使用的是 Framework 4.7 和 Windows Server 2016。注意——其他版本的 Framework 和 Windows Server 有完全不同的说明。

After following the steps below I successfully integrated ADFS into an asp.net web application that uses Membership.按照以下步骤操作后,我成功地将 ADFS 集成到使用 Membership 的 asp.net Web 应用程序中。 All of the existing calls to the Membership database work (for example, Membership.Getuser(), Roles.GetRolesForUser()).对 Membership 数据库的所有现有调用都有效(例如,Membership.Getuser()、Roles.GetRolesForUser())。 In addition, System.Threading.Thread.CurrentPrincipal.Identity is fully-functional.此外,System.Threading.Thread.CurrentPrincipal.Identity 是全功能的。 Calls such as IsInRole() and the [Authorize] attribute work with no change needed. IsInRole() 和 [Authorize] 属性等调用无需更改即可工作。

This is not a detailed walkthrough, just a rough description of what I had to change in the web application (setting up the ADFS is an entirely separate matter).这不是一个详细的演练,只是对我必须在 Web 应用程序中更改的内容的粗略描述(设置 ADFS 是完全独立的事情)。

After setting up ADFS, create a FederationMetadata.xml file in the web application that points to the ADFS.设置 ADFS 后,在指向 ADFS 的 Web 应用程序中创建一个 FederationMetadata.xml 文件。 Google for instructions on creating a FederationMetadata.xml file. Google 以获取有关创建 FederationMetadata.xml 文件的说明。 Caution: do NOT use the Framework 3.5 Windows Identity Federation Utility to create your FederationData.xml;注意:不要使用 Framework 3.5 Windows Identity Federation Utility 来创建您的 FederationData.xml; the utility will alter your web.config to use the deprecated Micorsoft.Identity libraries.该实用程序将更改您的 web.config 以使用已弃用的 Micorsoft.Identity 库。 You instead will want to use the System.Identity libraries.相反,您需要使用 System.Identity 库。 My FederationMetadata.xml file looks like this:我的 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>

In your web.config: 1. Make your FederationMetadata file visible在您的 web.config 中: 1. 使您的 FederationMetadata 文件可见

<location path="FederationMetadata">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
  1. Turn authentication off, and deny all unauthorized users.关闭身份验证,并拒绝所有未经授权的用户。

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

    ... ...

  2. Keep your existing membership and role providers for convenience.为方便起见,保留您现有的成员资格和角色提供者。 In the role provider set cacheRolesInCookie="false".在角色提供程序中设置 cacheRolesInCookie="false"。 Roles are now maintained by the SessionAuthenticationModule.角色现在由 SessionAuthenticationModule 维护。 If you cache roles in cookies you will break SessionAuthenticationModule.如果您在 cookie 中缓存角色,您将破坏 SessionAuthenticationModule。

  3. Add the appsettings needed by system.Identity, which point to the ADFS.添加 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. Add WSFederationAuthenticationModule and SessionAuthenticationModule to your system.Webserver tag.将 WSFederationAuthenticationModule 和 SessionAuthenticationModule 添加到您的 system.Webserver 标记。 Some fault in the SO site is not letting me add the tag here, so I'll add that tag as a comment below. SO 站点中的某些错误不允许我在此处添加标签,因此我将添加该标签作为下面的评论。 WSFederationAuthenticationModule interrupts a 401 authorization denied HTTP response and redirects to ADFS. WSFederationAuthenticationModule 中断 401 授权拒绝 HTTP 响应并重定向到 ADFS。 ADFS issues a security token. ADFS 颁发安全令牌。 WSFederationAuthenticationModule consumes that token, creates a ClaimsPrincipal, and stores that ClaimsPrincipal in a cookie (this marks the browser as authenticated). WSFederationAuthenticationModule 使用该令牌、创建 ClaimsPrincipal 并将该 ClaimsPrincipal 存储在 cookie 中(这将浏览器标记为已通过身份验证)。 On every postback SessionAuthenticationModule uses that cookie to reconstruct the ClaimsPrincipal (because of this the user does not need to re-authenticate with ADFS on every postback).在每次回发时,SessionAuthenticationModule 都使用该 cookie 来重建 ClaimsPrincipal(因此,用户不需要在每次回发时使用 ADFS 重新进行身份验证)。 Calls like "IsAuthenticated" and "IsInRole()" and the [Authorize] tag all work from this ClaimsPrincipal object.诸如“IsAuthenticated”和“IsInRole()”以及 [Authorize] 标签之类的调用都可以从这个 ClaimsPrincipal 对象工作。

  5. Set up your system.IdentityModel tag to communicate with your ADFS.设置您的 system.IdentityModel 标记以与您的 ADFS 通信。 Full instructions are beyond the scope of this answer, but here is what my tag looks like:完整说明超出了本答案的范围,但这是我的标签的样子:

     <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. Keep your existing connection string that points to your membership database.保留指向您的成员资格数据库的现有连接字符串。 This typically defaults to "LocalSqlServer":这通常默认为“LocalSqlServer”:

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

  7. Load the user's roles from the membership database in the Authenticate_Request event in the global.asax.在 global.asax 中的 Authenticate_Request 事件中从成员资格数据库加载用户的角色。 In our case we are using the Active Directory "objectGUID" as an AD user's unique identifier;在我们的例子中,我们使用 Active Directory“objectGUID”作为 AD 用户的唯一标识符; we added a column to the dbo.aspnet_users table in our membership database to tie AD users to membership users.我们向会员数据库中的 dbo.aspnet_users 表添加了一列,以将 AD 用户与会员用户联系起来。 From there it is a simple SQL call to load roles from the Membership database into the CurrentPrincipal.Identity by casting the latter as ClaimsIdentity.从那里,它是一个简单的 SQL 调用,将角色从 Membership 数据库加载到 CurrentPrincipal.Identity,方法是将后者转换为 ClaimsIdentity。 In the example below I am adding 3 hard-coded roles, but in fact these will be retrieved from our membership datasbase using the objectGUID.在下面的示例中,我添加了 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. In our application a single Active Directory user can have multiple Membership identities.在我们的应用程序中,单个 Active Directory 用户可以具有多个成员身份。 Sometimes users will want to change Membership identities.有时用户会想要更改会员身份。 This is easily done:这很容易做到:

     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); }

This approach is true for WIF but you can also use OWIN Katana and OpenID Connect if you are using ADFS 4.0.这种方法适用于 WIF,但如果您使用的是 ADFS 4.0,您也可以使用 OWIN Katana 和 OpenID Connect。

The OWIN plumbing allows multiple connections eg this . OWIN 管道允许多个连接,例如这个

Or you can use something like identityserver that does support ASP.NET membership and you can federate this with ADFS.或者,您可以使用支持 ASP.NET 成员身份的身份服务器之类的东西,并且您可以将其与 ADFS 联合。 identityserver will then have two buttons and the user can choose which one they can authenticate with.然后,identityserver 将有两个按钮,用户可以选择他们可以使用哪个按钮进行身份验证。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM