'safe handle has been closed' 使用 FederatedAuthentication.SessionAuthenticationModule 时出错
'safe handle has been closed' error when using FederatedAuthentication.SessionAuthenticationModule
对于基于 MVC5 的 Intranet 应用程序,我正在尝试在主体的声明感知 Windows 身份之上使用自定义声明实现基于 Windows 的身份验证。
一切都很顺利,直到我尝试从之前存储在会话 cookie 中的 SessionSecurityToken 中读取身份,出于某种原因,这给了我“安全句柄已关闭”登陆我的 Home/index 视图后尝试转到任何其他视图时出错。
以下是我目前得到的 -
- 在项目属性中 - Windows Auth = Enabled,Anonymous Auth = disabled。
- 作为 .net 4.5 上的 MVC5 项目,该项目默认设计为使用 OWIN(是的,如果您在创建项目时未选择 Windows 身份验证,VS 会这样做),我觉得 OWIN不与 Windows Auth 结合,所以我一开始就通过在 startup.cs
中评论对 'ConfigAuth' 的调用来禁用 OWIN
写了一个 class 来自定义 windows 主体身份并使用 FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie[= 将 sessionSecurityToken 写入 cookie 18=]
public class CustomClaimsTransformer : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
ClaimsPrincipal transformedPrincipal = CustomizePrincipal(ClaimsPrincipal.Current.Identities.First(), incomingPrincipal.Identity.Name);
CreateSession(transformedPrincipal);
return transformedPrincipal;
}
private void CreateSession(ClaimsPrincipal transformedPrincipal)
{
SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(12));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
}
private ClaimsPrincipal CustomizePrincipal(ClaimsIdentity userClaimsIdentity, String userName)
{
List<Claim> claims = new List<Claim>();
PrincipalContext princiContxt = null; ;
UserPrincipal princi = null;
ClaimsIdentity custClaimsIdentity = new ClaimsIdentity();
princiContxt = new PrincipalContext(ContextType.Domain);
princi = UserPrincipal.FindByIdentity(princiContxt, userName);//);
userClaimsIdentity.AddClaims(new[] {
new Claim("CustGroup", "CustTeam"),
new Claim(ClaimTypes.Email, princi.EmailAddress),
... ///more claims added here
});
return new ClaimsPrincipal(userClaimsIdentity);
}
}
在 Web.config 中,我添加了以下内容:
在'configSections'下:
<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" />
在'system.identityModel'中:
<system.identityModel>
<identityConfiguration>
<claimsAuthenticationManager type="myProject.CustomClaimsTransformer,myProject"/>
</identityConfiguration>
</system.identityModel>
还在 system.webserver 中添加了以下模块:
<modules>
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"></add>
</modules>
接下来,我有自定义授权过滤器属性以使用上面定义的 CustomClaimsTransformer class 来授权使用自定义声明:
public class PROJClaimsAuthorizeAttribute : AuthorizeAttribute {
public string ClaimType { get; set; }
public string ClaimValue { get; set; }
//Called when access is denied
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
//User isn't logged in
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Home", action = "Index" })
);
}
//User is logged in but has no access
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Error", action = "AccessDenied" })
);
}
}
//Core authentication, called before each action
protected override bool AuthorizeCore(HttpContextBase context)
{
SessionSecurityToken token;
ClaimsIdentity claimsIdentity = null;
ClaimsIdentity userClaimsIdentity = null;
ClaimsPrincipal customClaimsPrinci = null;
ClaimsAuthenticationManager authManager = null;
var isAuthorized = false;
try
{
claimsIdentity = context.User.Identity as ClaimsIdentity; // get current user's ClaimsIdentity (Widnow's identity as ClaimsIdentity)
isAuthorized = base.AuthorizeCore(context);
if (!context.Request.IsAuthenticated || !isAuthorized)
{
return false;
}
///////******* THE Error Causing IF statement *******************************************
//check if the SessionSecurityToken is available in cookie
if (FederatedAuthentication.SessionAuthenticationModule.TryReadSessionTokenFromCookie(out token))
{
//var accessToken = await tokenManager.GetTokenFromStoreAsync(token.ClaimsPrincipal.Identity.Name);
claimsIdentity = token.ClaimsPrincipal.Identity as ClaimsIdentity;
}
else
{
//else get the principal with Custom claims identity using CustomClaimsTransformer, which also sets it in cookie
ClaimsPrincipal currentPrincipal = ClaimsPrincipal.Current;
CustomClaimsTransformer customClaimsTransformer = new CustomClaimsTransformer();
ClaimsPrincipal tranformedClaimsPrincipal = customClaimsTransformer.Authenticate(string.Empty, currentPrincipal);
Thread.CurrentPrincipal = tranformedClaimsPrincipal;
HttpContext.Current.User = tranformedClaimsPrincipal;
}
isAuthorized = checkClaimValidity(claimsIdentity, ClaimType, ClaimValue);
}
catch (Exception e)
{
// Error handling code
var exptnMsg = "error setting AuthorizeCore" + e.Message;
return false;
}
return isAuthorized;
} // </ protected override bool AuthorizeCore >
//checks Claim type/value in the given Claims Identity
private Boolean checkClaimValidity(ClaimsIdentity pClaimsIdentity, string pClaimType, string pClaimValue)
{
Boolean blnClaimsValiditiy = false;
//now check the passed in Claimtype has the passed in Claimvalue
if (pClaimType != null && pClaimValue != null)
{
if ((pClaimsIdentity.HasClaim(x => x.Type.ToLower() == pClaimType.ToLower() && x.Value.ToLower() == pClaimValue.ToLower())))
{
blnClaimsValiditiy = true;
}
}
return blnClaimsValiditiy;
}
}
在此之后,我可以使用我的自定义授权属性 'PROJClaimsAuthorizeAttribute' 装饰我的控制器 class,如下所示:
[PROJClaimsAuthorizeAttribute(ClaimType = "CustGroup", ClaimValue = "CustTeam")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
一切正常。问题是 - 从索引视图中,如果我尝试导航到其他视图 - 我会收到“safe handle has been closed”错误。 (当我删除上面标有“THE Error Causing IF statement******”的 if 语句的 'if' 部分,并只保留 else 部分中的任何内容时,它工作正常,但我不是使用 sessionSecurityToken cookie)。
过去几天我一直在绞尽脑汁想找出这个错误,搜索了 google/SO 等,但到目前为止没有运气。
所以最后想到把它扔给这里的 SO 专家社区,如果有人阐明 what/where 问题可能是什么,我们将不胜感激。在此先衷心感谢您的帮助。
我修好了 - this SO post 提供了线索。与其自定义现有的 Windows 主体 claimsIdentity,不如创建一个新身份并向该身份添加自定义声明有助于消除 'safe handle has been closed' 错误。
在 CustomClaimsTransformer 中 class - CustomClaimsTransformer.CustomizePrincipal
/* commented this earlier code of adding claims to userClaimsIdentity
userClaimsIdentity.AddClaims(new[] {
new Claim("CustGroup", "CustTeam"),
new Claim(ClaimTypes.Email, princi.EmailAddress),
... ///more claims added here
});
return new ClaimsPrincipal(userClaimsIdentity);
*/
并替换为创建新 ClaimsIdentity 的代码,如下所示
List<Claim> newClaims = new List<Claim>();
newClaims.Add(new Claim("CustGroup ", "CustTeam"));
newClaims.Add(new Claim(ClaimTypes.Email, princi.EmailAddress));
... ///more claims added here
ClaimsIdentity ci = new ClaimsIdentity(newClaims, "CustomClaims");
return new ClaimsPrincipal(ci); //userClaimsIdentity);
现在它运行良好,用户 authentication/identity/claims 被保存在 SessionTokenCookie
对于基于 MVC5 的 Intranet 应用程序,我正在尝试在主体的声明感知 Windows 身份之上使用自定义声明实现基于 Windows 的身份验证。
一切都很顺利,直到我尝试从之前存储在会话 cookie 中的 SessionSecurityToken 中读取身份,出于某种原因,这给了我“安全句柄已关闭”登陆我的 Home/index 视图后尝试转到任何其他视图时出错。
以下是我目前得到的 -
- 在项目属性中 - Windows Auth = Enabled,Anonymous Auth = disabled。
- 作为 .net 4.5 上的 MVC5 项目,该项目默认设计为使用 OWIN(是的,如果您在创建项目时未选择 Windows 身份验证,VS 会这样做),我觉得 OWIN不与 Windows Auth 结合,所以我一开始就通过在 startup.cs 中评论对 'ConfigAuth' 的调用来禁用 OWIN
写了一个 class 来自定义 windows 主体身份并使用 FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie[= 将 sessionSecurityToken 写入 cookie 18=]
public class CustomClaimsTransformer : ClaimsAuthenticationManager { public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal) { if (!incomingPrincipal.Identity.IsAuthenticated) { return base.Authenticate(resourceName, incomingPrincipal); } ClaimsPrincipal transformedPrincipal = CustomizePrincipal(ClaimsPrincipal.Current.Identities.First(), incomingPrincipal.Identity.Name); CreateSession(transformedPrincipal); return transformedPrincipal; } private void CreateSession(ClaimsPrincipal transformedPrincipal) { SessionSecurityToken sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(12)); FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken); } private ClaimsPrincipal CustomizePrincipal(ClaimsIdentity userClaimsIdentity, String userName) { List<Claim> claims = new List<Claim>(); PrincipalContext princiContxt = null; ; UserPrincipal princi = null; ClaimsIdentity custClaimsIdentity = new ClaimsIdentity(); princiContxt = new PrincipalContext(ContextType.Domain); princi = UserPrincipal.FindByIdentity(princiContxt, userName);//); userClaimsIdentity.AddClaims(new[] { new Claim("CustGroup", "CustTeam"), new Claim(ClaimTypes.Email, princi.EmailAddress), ... ///more claims added here }); return new ClaimsPrincipal(userClaimsIdentity); } }
在 Web.config 中,我添加了以下内容:
在'configSections'下:
<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" />
在'system.identityModel'中:
<system.identityModel>
<identityConfiguration>
<claimsAuthenticationManager type="myProject.CustomClaimsTransformer,myProject"/>
</identityConfiguration>
</system.identityModel>
还在 system.webserver 中添加了以下模块:
<modules>
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"></add>
</modules>
接下来,我有自定义授权过滤器属性以使用上面定义的 CustomClaimsTransformer class 来授权使用自定义声明:
public class PROJClaimsAuthorizeAttribute : AuthorizeAttribute { public string ClaimType { get; set; } public string ClaimValue { get; set; } //Called when access is denied protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext) { //User isn't logged in if (!filterContext.HttpContext.User.Identity.IsAuthenticated) { filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary(new { controller = "Home", action = "Index" }) ); } //User is logged in but has no access else { filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }) ); } } //Core authentication, called before each action protected override bool AuthorizeCore(HttpContextBase context) { SessionSecurityToken token; ClaimsIdentity claimsIdentity = null; ClaimsIdentity userClaimsIdentity = null; ClaimsPrincipal customClaimsPrinci = null; ClaimsAuthenticationManager authManager = null; var isAuthorized = false; try { claimsIdentity = context.User.Identity as ClaimsIdentity; // get current user's ClaimsIdentity (Widnow's identity as ClaimsIdentity) isAuthorized = base.AuthorizeCore(context); if (!context.Request.IsAuthenticated || !isAuthorized) { return false; } ///////******* THE Error Causing IF statement ******************************************* //check if the SessionSecurityToken is available in cookie if (FederatedAuthentication.SessionAuthenticationModule.TryReadSessionTokenFromCookie(out token)) { //var accessToken = await tokenManager.GetTokenFromStoreAsync(token.ClaimsPrincipal.Identity.Name); claimsIdentity = token.ClaimsPrincipal.Identity as ClaimsIdentity; } else { //else get the principal with Custom claims identity using CustomClaimsTransformer, which also sets it in cookie ClaimsPrincipal currentPrincipal = ClaimsPrincipal.Current; CustomClaimsTransformer customClaimsTransformer = new CustomClaimsTransformer(); ClaimsPrincipal tranformedClaimsPrincipal = customClaimsTransformer.Authenticate(string.Empty, currentPrincipal); Thread.CurrentPrincipal = tranformedClaimsPrincipal; HttpContext.Current.User = tranformedClaimsPrincipal; } isAuthorized = checkClaimValidity(claimsIdentity, ClaimType, ClaimValue); } catch (Exception e) { // Error handling code var exptnMsg = "error setting AuthorizeCore" + e.Message; return false; } return isAuthorized; } // </ protected override bool AuthorizeCore > //checks Claim type/value in the given Claims Identity private Boolean checkClaimValidity(ClaimsIdentity pClaimsIdentity, string pClaimType, string pClaimValue) { Boolean blnClaimsValiditiy = false; //now check the passed in Claimtype has the passed in Claimvalue if (pClaimType != null && pClaimValue != null) { if ((pClaimsIdentity.HasClaim(x => x.Type.ToLower() == pClaimType.ToLower() && x.Value.ToLower() == pClaimValue.ToLower()))) { blnClaimsValiditiy = true; } } return blnClaimsValiditiy; } }
在此之后,我可以使用我的自定义授权属性 'PROJClaimsAuthorizeAttribute' 装饰我的控制器 class,如下所示:
[PROJClaimsAuthorizeAttribute(ClaimType = "CustGroup", ClaimValue = "CustTeam")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
一切正常。问题是 - 从索引视图中,如果我尝试导航到其他视图 - 我会收到“safe handle has been closed”错误。 (当我删除上面标有“THE Error Causing IF statement******”的 if 语句的 'if' 部分,并只保留 else 部分中的任何内容时,它工作正常,但我不是使用 sessionSecurityToken cookie)。
过去几天我一直在绞尽脑汁想找出这个错误,搜索了 google/SO 等,但到目前为止没有运气。 所以最后想到把它扔给这里的 SO 专家社区,如果有人阐明 what/where 问题可能是什么,我们将不胜感激。在此先衷心感谢您的帮助。
我修好了 - this SO post 提供了线索。与其自定义现有的 Windows 主体 claimsIdentity,不如创建一个新身份并向该身份添加自定义声明有助于消除 'safe handle has been closed' 错误。
在 CustomClaimsTransformer 中 class - CustomClaimsTransformer.CustomizePrincipal
/* commented this earlier code of adding claims to userClaimsIdentity
userClaimsIdentity.AddClaims(new[] {
new Claim("CustGroup", "CustTeam"),
new Claim(ClaimTypes.Email, princi.EmailAddress),
... ///more claims added here
});
return new ClaimsPrincipal(userClaimsIdentity);
*/
并替换为创建新 ClaimsIdentity 的代码,如下所示
List<Claim> newClaims = new List<Claim>();
newClaims.Add(new Claim("CustGroup ", "CustTeam"));
newClaims.Add(new Claim(ClaimTypes.Email, princi.EmailAddress));
... ///more claims added here
ClaimsIdentity ci = new ClaimsIdentity(newClaims, "CustomClaims");
return new ClaimsPrincipal(ci); //userClaimsIdentity);
现在它运行良好,用户 authentication/identity/claims 被保存在 SessionTokenCookie