为什么我的 ClaimsIdentity IsAuthenticated 总是 false(用于 Web API 的授权过滤器)?

93

我正在一个Web API项目中,为了检查令牌而覆盖了正常的身份验证过程。 代码大致如下:

if ( true ) // validate the token or whatever here
{
    var claims = new List<Claim>();
    claims.Add( new Claim( ClaimTypes.Name, "MyUser" ) );
    claims.Add( new Claim( ClaimTypes.NameIdentifier, "MyUserID" ) );
    claims.Add( new Claim( ClaimTypes.Role, "MyRole" ) );

    var claimsIdentity = new ClaimsIdentity( claims );

    var principal = new ClaimsPrincipal( new[] { claimsIdentity } );
    Thread.CurrentPrincipal = principal;
    HttpContext.Current.User = principal;
}

然后当我在控制器上应用[Authorize]属性时,它无法进行授权。

调试代码证实了同样的行为:

// ALWAYS FALSE!
if ( HttpContext.Current.User.Identity.IsAuthenticated ) {
    // do something
}

尽管我已经构建了一个有效的ClaimsIdentity并将其分配给线程,为什么它仍然认为用户未经过身份验证?

2个回答

166
问题是由于.Net 4.5中的一个重大变化引起的。正如这篇文章所解释的那样,仅仅构造声明标识不再使其IsAuthenticated返回true。相反,您需要将一些字符串(无论是什么)传递给构造函数。
因此,上述代码中的这行代码:
var claimsIdentity = new ClaimsIdentity( claims );

变成这样:

// exact string doesn't matter
var claimsIdentity = new ClaimsIdentity( claims, "CustomApiKeyAuth" );

问题已经解决。更新:请参考Leo的其他答案。确切的AuthenticationType值可能重要或不重要,这取决于您在身份验证管道中还有什么其他内容。 更新2:如评论中Robin van der Knaap所建议的一样,System.Security.Claims.AuthenticationTypes值之一可能是合适的。
var claimsIdentity = new ClaimsIdentity( claims, AuthenticationTypes.Password );

// and elsewhere in your application...
if (User.Identity.AuthenticationType == AuthenticationTypes.Password) {
    // ...
}

11
尽管您可以添加任何字符串,但根据 MSDN 的说法,通常应该选择身份验证类型类中定义的值之一。http://msdn.microsoft.com/zh-cn/library/system.security.claims.claimsidentity.authenticationtype(v=vs.110).aspx - Robin van der Knaap
1
var claimsIdentity = new ClaimsIdentity(claims, AuthenticationTypes.Password); - Robin van der Knaap
4
字符串的值在User.Identity.AuthenticationType中显示。 - Robin van der Knaap
2
哇,这真的很难懂!感谢你在这里分享!我卡了一个多小时。 - DavidEdwards

20
虽然提供的答案有一定的有效性,但并非完全正确。您不能假设只是添加任何字符串就会奇妙地起作用。正如其中一条评论所述,这个字符串必须与AuthenticationTypes 枚举中的一个匹配,而该枚举又必须与 OWIN 认证/授权中间件中指定的枚举匹配......例如...
public void ConfigureOAuth(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);

            OAuthAuthorizationServerOptions serverOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                AuthenticationType = AuthenticationTypes.Password,
                AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
                Provider = new AppAuthServerProvider()
            };


            app.UseOAuthAuthorizationServer(serverOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
                {
                    AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
                    AuthenticationType = AuthenticationTypes.Password
                });            
        }

然而,在上述情况下,这并不重要。但是,如果您使用更多的身份验证/授权级别,则声明将与匹配相同AuthenticationType的声明相关联...另一个例子是当您使用Cookie身份验证时...

public void Configuration(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "ApplicationCookie",
                LoginPath = new PathString("/auth/login")
            });
        }

其中AuthenticationType表示cookie的名称,因为您的应用程序可能从其他提供方获取了其他cookie,所以在实例化声明时设置AuthenticationType非常重要,以便将其关联到正确的cookie。


4
在.NET Core中,您可以将常量用作“AuthenticationType”,例如CookieAuthenticationDefaults.AuthenticationSchemeJwtBearerDefaults.AuthenticationScheme - Alex Klaus
4
注意,在创建ClaimsIdentity时,需要将模式名称作为AuthenticationType传递(例如new ClaimsIdentity(claims, AuthenticationScheme))。否则,标识中的IsAuthenticated标志将为false。在过去的两年中,我遇到了相同的非直观问题两次,而这个答案再次帮助了我。不幸的是,我不能再次点赞这个答案。 - Alex Klaus
@AlexKlaus,感谢您的贡献,非常有价值。我甚至考虑在几年前更新答案,但然后我意识到原始问题是“.NET Core之前”的,并且明确针对ASP.NET 4.5。因此,我现在将其保留为原样。 - Leo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接