如何将Windows身份验证和JWT与.Net Core 2.1结合使用

时间:2018-09-03 09:31:38

标签: authentication asp.net-core jwt windows-authentication

我尝试将Windows身份验证和JWT与.NET Core 2.1一起使用。

我具有以下身份验证的启动设置:

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

<IfModule mod_rewrite.c>
    Options -MultiViews
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

<IfModule !mod_rewrite.c>
<IfModule mod_alias.c>
    RedirectMatch 302 ^/$ /index.php/
</IfModule>
</IfModule>

IIS设置:

services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,

                    ValidIssuer = "Test",
                    ValidAudience = "Test",
                    IssuerSigningKey = JwtSecurityKey.Create("677efa87-aa4d-42d6-adc8-9f866e5f75f7")
                };

                options.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = OnAuthenticationFailed
                };
            });

我尝试使用以下代码段通过Windows身份验证创建JWT令牌:

"iisSettings": {
    "windowsAuthentication": true, 
    "anonymousAuthentication": true, 
    ..
  }

在另一个控制器中,我只需要使用JWT身份验证:

[Route("api/[controller]")]
    [ApiController]
    [Authorize(AuthenticationSchemes = "Windows")]
    public class AuthController : ControllerBase
    {
        [HttpPost("token")]
        public IActionResult Token()
        {
            //Setup claims
            var claims = new[]
            {
                new Claim(ClaimTypes.Name, User.Identity.Name),
                //Add additional claims
            };

            //Read signing symmetric key
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("677efa87-aa4d-42d6-adc8-9f866e5f75f7"));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            //Create a token
            var token = new JwtSecurityToken(
                issuer: "Test",
                audience: "Test",
                claims: claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

            //Return signed JWT token
            return Ok(new
            {
                token = new JwtSecurityTokenHandler().WriteToken(token)
            });
        }
    }

如果JWT令牌已过期,那么我正确地收到了响应代码[Route("api/[controller]")] [ApiController] [Authorize(AuthenticationSchemes = "Bearer")] public class ProductController : ControllerBase { [HttpGet] public IActionResult Get() { var userName = User.Identity.Name; var claims = User.Claims.Select(x => new { x.Type, x.Value }); return Ok(new { userName, claims }); } } ,但是我仍然在浏览器中看到用于放置凭据的对话框。

当我要创建JWT令牌并禁用负责显示带有凭据的浏览器对话框的响应时,如何仅对一部分配置Windows身份验证?如何正确组合这些东西?

7 个答案:

答案 0 :(得分:2)

这是我在 .net 5 中如何在 Web api 上使用 Windows 身份验证和 Jwt。我首先让 Windows 身份验证获取身份名称,然后检查数据库表以获取角色等的授权,然后返回一个 jwt 令牌。客户端网站使用 jwt 令牌访问受 jwt 保护的资源。

  1. 在调试时在启动设置中启用匿名和 Windows 身份验证。如果部署在服务器上,则在 iis 中。

  2. 如果您想通过 Kestrel 进行调试,在撰写本文时,在启动时,请勿使用 services.AddAuthentication(IISDefaults.AuthenticationScheme);
    相反,使用

     services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
    
  3. 标准设置在启动configureservices 中添加jwt 认证。在配置函数中,添加身份验证。

  4. 用这个装饰你想要使用 Windows 身份验证的函数:

    [Authorize(AuthenticationSchemes = NegotiateDefaults.AuthenticationScheme)]
    [Route("GetToken")]
    [HttpGet]
    public IActionResult GetToken() 
    {}
  1. 装饰您要使用 jwt 令牌的控制器:
    [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
    [ApiController]
    [Route("[controller]")]
    public class ReportController : ControllerBase
    {
    }

仅此而已。

答案 1 :(得分:1)

我处理此问题的方式是创建两个不同的Web应用程序:一个用于Windows身份验证,一个用于JWT令牌身份验证。

Windows身份验证Web应用程序将很小,只能做一件事。通过Windows身份验证在端点上对用户进行身份验证,然后返回JWT令牌。

然后,该令牌可用于主应用程序。只要您的签名密钥和访问者相同,就可以在其他Web应用程序上创建令牌。

您无需费力尝试同时处理这两者。

答案 2 :(得分:1)

要同时使用Windows和JWT承载身份验证-默认情况下,Windows身份验证会应用到所有页面,它超越了JWT承载功能。 要将两者合并到一个单个应用程序中:-

  1. 在提供程序上应用Windows身份验证,该提供程序用于使用其上的[AllowAnonymous]标记生成JWt Bearer令牌,默认情况下使用Windows身份验证
  2. 在应用程序的其余页面上使用[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]来应用Jwt Bearer身份验证

因此,在上述情况下,我们使用Windows身份验证生成JWT令牌,并使用JWT令牌对其余页面进行身份验证 在申请中。

对于“测试”方案,您可以尝试将邮递员使用具有JWTAuthentication的NTLM(Windows身份验证)作为令牌生成控制器,使用BearerToken进行页面

答案 3 :(得分:0)

此答案可能会有所帮助:https://stackoverflow.com/a/51055082/1212994

  

您需要确保在尝试使用Windows Auth时不要设置Authorization:Bearer HTTP标头。这里的关键是“ Windows Auth”的实际工作方式。让我们看看它如何与浏览器一起工作。

答案 4 :(得分:0)

到目前为止,我发现完成此任务的唯一方法是使用Kestrel。它允许我控制为每个请求发送哪些身份验证方案。在IIS上,我仅控制是否发送Bearer,默认情况下如上所述发送NTLM和Negotiate。

答案 5 :(得分:0)

在.Net 5.0中,我已经能够在单个应用程序中成功处理Windows,OIDC,Cookie和JWT方案。 IIS / LaunchSettings.json配置必须将匿名身份验证和Windows身份验证都指定为true,因为如果没有针对匿名服务器的配置,则承载身份验证将失败,并且如果没有针对Windows的服务器配置,Windows身份验证将无法正常工作。

从AllowAnonymous api开始并发出响应重定向(此处的重定向是强制非匿名auth方案发出质询的关键,并且在Windows情况下无缝接收所需的协商授权标头)到您需要的其他路由支持,并用适当的授权方案属性装饰它们。

我更喜欢从每个非jwt方案中获取相关数据,使用令牌作为声明来生成新的声明标识,并使除应用程序所使用的身份验证控制器外的所有其他路由都使用承载方案。我发现有必要在授权实现上覆盖AllowAnonymous和Authorize属性,以确保始终确保所有请求都使用有效的承载令牌转换为ClaimsIdentity,以实现所有上下文的一致性。

为了支持Windows身份验证上下文的匿名上下文,我提供了一个标头,用于控制是否尝试进行无缝身份验证,或者以其他方式简单地将ClaimsIdentity生成为匿名用户。初始化声明身份时,请务必将身份验证类型设置为空字符串。

答案 6 :(得分:0)

这是 IIS 所需的行为。一旦应用程序返回 401,IIS 就会将 WWW-Authenticate 附加到标题中,这就是为什么我们会看到浏览器对话框来输入凭据。

欲知更多:https://github.com/aspnet/Security/issues/1853

要修复此行为,我们需要将状态从 401 更改为其他内容,以便通过客户端框架(blazor 或 angular)将用户重定向到登录模块(调用 Authenticate Endpoint 以获取令牌)而不是显示凭据对话框。

最简单的方法是覆盖 JWT Bearer 的 OnChallenge 行为并将状态从 401 更改为 600(或您喜欢的任何值)。

services.AddAuthentication(options =>
{
  options.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
  options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddNegotiate()
 .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
        options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = Configuration["Jwt:Issuer"],
                ValidAudience = Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])),
                ClockSkew = TimeSpan.FromMinutes(25)
            };
            options.Events = new JwtBearerEvents();
            // override the challenge behaviour and change the status to 600
            options.Events.OnChallenge = context =>
            {

                context.HandleResponse();
                context.Response.StatusCode = (int)CustomHttpStatusCode.JwtTokenExpire;
                return Task.CompletedTask;
            };

        });