我有一个FubuMvc网站,该网站使用来自WIF单点登录服务器的基于声明的授权。身份验证发生在SSO上,包括角色和一组自定义声明在内的声明将传递到网站进行授权。
SSO和网站与基于角色的授权一样正常,但我想要做的是拒绝访问整个网站,根据缺少自定义声明保存错误页面。目前,我使用的是自定义ClaimsAuthenticationManager
,用于检查索赔并检查是否存在所需索赔。如果声明丢失,则会抛出异常。这会导致500错误,但我真正想要的是系统抛出401错误并重定向到网站上的Not Authorized页面。
以下是自定义ClaimsAuthenticationManager
的示例。
public class CustomAuthenticationManager : ClaimsAuthenticationManager
{
private readonly string expectedClaim;
public CustomAuthenticationManager (object config)
{
var nodes = config as XmlNodeList;
foreach (XmlNode node in nodes)
{
using (var stringReader = new StringReader(node.OuterXml))
using (var rdr = new XmlTextReader(stringReader))
{
rdr.MoveToContent();
rdr.Read();
string claimType = rdr.GetAttribute("claimType");
if (claimType.CompareTo(ClaimTypes.CustomClaim) != 0)
{
throw new NotSupportedException("Only custom claims are supported");
}
expectedSystemName = rdr.GetAttribute("customClaimValue");
}
}
}
public override IClaimsPrincipal Authenticate(
string resourceName, IClaimsPrincipal incomingPrincipal)
{
var authenticatedIdentities = incomingPrincipal.Identities.Where(x => x.IsAuthenticated);
if (authenticatedIdentities.Any() &&
authenticatedIdentities.Where(x => x.IsAuthenticated)
.SelectMany(x => x.Claims)
.Where(x => x.ClaimType == ClaimTypes.CustomClaim)
.All(x => x.Value != expectedClaim))
{
throw new HttpException(
(int)HttpStatusCode.Unauthorized,
"User does not have access to the system");
}
return base.Authenticate(resourceName, incomingPrincipal);
}
}
上述工作并阻止访问系统,但ti不是非常友好,因为用户只是得到500错误。此外,由于用户基本上已登录但无法访问,因此无法注销。我暴露了一个未经授权的错误页面,该页面提供了对匿名用户的访问权限,但我还没有办法重定向用户。
答案 0 :(得分:4)
我想知道你为什么在身份验证管理器中实现了逻辑。
您是否考虑过切换到ClaimsAuthorizationManager
?为此,您需要将ClaimsAuthorizationModule
添加到管道中:
<system.webServer>
<modules>
...
<add name="SessionAuthenticationModule" ....
<add name="ClaimsAuthorizationModule" type="Microsoft.IndentityModel.Web.ClaimsAuthorizationModule, Microsoft.IndentityModel.Web" />
</modules>
</system.webServer>
然后您创建授权角色的结构:
<claimsAuthorizationManager type="yourtype, yourassembly">
<requiredClaim claimType="foobar" />
</claimsAuthorizationManager>
并且在经理中:
public class FederatedClaimsAuthorizationManager : ClaimsAuthorizationManager
{
private List<string> _requiredClaims = new List<string>();
public FederatedClaimsAuthorizationManager( object config )
{
XmlNodeList nodes = config as XmlNodeList;
foreach ( XmlNode node in nodes )
{
XmlTextReader xtr = new XmlTextReader( new StringReader( node.OuterXml ) );
xtr.MoveToContent();
_requiredClaims.Add( xtr.GetAttribute( "claimType" ) );
}
}
public override bool CheckAccess( AuthorizationContext context )
{
IClaimsIdentity identity = context.Principal.Identities[0];
return !identity.IsAuthenticated ||
identity.Claims.Any( c => _requiredClaims.Any( rq => c.ClaimType == rq ) );
}
}
这与您的方法之间的区别在于,只需重新调用身份验证过程即可处理失败的授权 - 没有例外,没有500。
例如 - 如果您在登录页面上使用<wif:FederatedPassiveSignIn />
,则失败的授权只会将用户重定向到登录页面,您可以在其中检查用户是否经过身份验证并显示“您被重定向到登录页面,这可能意味着您试图访问您无权访问的资源“。
答案 1 :(得分:1)
除了转换声明的典型情况之外,文档不清楚如何实现Authenticate。如果您需要根据声明拒绝用户,则可以自行决定。
我试过返回null并抛出各种异常,包括401(类似于你的例子)。我发现最有效的解决方案是创建并返回一个匿名主体。
using System.Security.Principal;
...
public class CustomAuthenticationManager : ClaimsAuthenticationManager
{
public override IClaimsPrincipal Authenticate(
string resourceName,
IClaimsPrincipal incomingPrincipal)
{
...
ClaimsIdentity identity = new GenericIdentity(String.Empty);
return new GenericPrincipal(identity, new string[0] { });
}
}
您必须传递一个空字符串作为标识名称,以便在通用声明标识上使用IsAuthenticated返回false。