C# 具有Asp.Net成员资格的ADFS单点登录
这是一个挑战——我维护一个混合的asp.net mvc/web表单应用程序,它使用表单身份验证和旧的asp.net成员资格提供程序(aspnet_用户、aspnet_成员资格等)。我们公司正在使用ADFS进行单点登录。我们必须修改混合asp.net应用程序以使用ADFS进行身份验证 我的问题是,我是否可以将混合asp.net应用程序更改为使用ADFS进行身份验证,但继续使用现有的成员资格提供程序来处理授权 这个计划行得通吗?我的假设正确吗C# 具有Asp.Net成员资格的ADFS单点登录,c#,asp.net,asp.net-mvc,asp.net-membership,adfs,C#,Asp.net,Asp.net Mvc,Asp.net Membership,Adfs,这是一个挑战——我维护一个混合的asp.net mvc/web表单应用程序,它使用表单身份验证和旧的asp.net成员资格提供程序(aspnet_用户、aspnet_成员资格等)。我们公司正在使用ADFS进行单点登录。我们必须修改混合asp.net应用程序以使用ADFS进行身份验证 我的问题是,我是否可以将混合asp.net应用程序更改为使用ADFS进行身份验证,但继续使用现有的成员资格提供程序来处理授权 这个计划行得通吗?我的假设正确吗 >P>使用Windows身份基础4.5被动重定向,如该链
如果您想知道“为什么不试试呢?”,这是因为ADFS服务器将在几个月内不可用,但我现在负责提出一个开发计划。我知道,如果我只使用常规的asp.net mvc应用程序,将身份验证模式设置为“无”,然后使用正确的用户名和密码调用Membership.ValidateUser,然后调用FormsAuthentication.SetAuthCookie,则成员资格提供方似乎工作正常,尽管Request.IsAuthenticated当然是false,因此,我没有方便的方法对此进行全面测试,因为每次授权检查都首先查看用户是否经过身份验证,然后再查看角色 这种方法适用于WIF,但如果您使用的是ADFS 4.0,也可以使用OWIN Katana和OpenID Connect OWIN管道允许多个连接,例如
或者您可以使用像identityserver这样的支持ASP.NET成员身份的工具,并且您可以将其与ADF联合。identityserver将有两个按钮,用户可以选择其中一个进行身份验证。结果证明这非常简单。我正在使用Framework 4.7和Windows Server 2016。注意--框架的其他版本和Windows Server的说明完全不同 遵循以下步骤后,我成功地将ADFS集成到使用成员资格的asp.net web应用程序中。对成员数据库的所有现有调用都有效(例如,Membership.Getuser()、Roles.GetRolesForUser())。此外,System.Threading.Thread.CurrentPrincipal.Identity功能齐全。诸如IsInRole()和[Authorize]属性之类的调用可以正常工作,无需进行任何更改 这不是一个详细的演练,只是对我在web应用程序中必须更改的内容的粗略描述(设置ADF是一个完全独立的问题) 设置ADF后,在web应用程序中创建指向ADF的FederationMetadata.xml文件。Google获取有关创建FederationMetadata.xml文件的说明。警告:不要使用Framework 3.5 Windows Identity Federation实用程序创建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>
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);
}
事后看来,我关于调用“FormsAuthentication.SetAuthCookie”的问题非常愚蠢。此呼叫绝对不需要,也与成员资格提供商无关。通过在web.config中添加标记来启用成员资格提供程序;表单身份验证是一个完全不同的领域。
<system.web>
<authorization>
<deny users="?" />
</authorization>
<authentication mode="None" />
<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" />
<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>
<connectionStrings>
<clear />
<add name="LocalSqlServer" connectionString="Server=MyServer; Database=MyMembershipDatabase; Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
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"));
}
}
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);
}