Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-core/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何验证通过cookies传递的JWT?_C#_Asp.net Core_Cookies_Jwt - Fatal编程技术网

C# 如何验证通过cookies传递的JWT?

C# 如何验证通过cookies传递的JWT?,c#,asp.net-core,cookies,jwt,C#,Asp.net Core,Cookies,Jwt,ASP.NET Core中的UseJWTBeareAuthentication中间件使得在Authorization头中验证传入的JSON Web令牌变得容易 如何验证通过Cookie传递的JWT,而不是标头?类似于UseCookieAuthentication,但对于只包含JWT的cookie。我建议您查看以下链接 它们将JWT令牌存储在仅http的cookie中,以防止XSS攻击 然后,他们通过在Startup.cs中添加以下代码来验证cookie中的JWT令牌: app.UseCooki

ASP.NET Core中的
UseJWTBeareAuthentication
中间件使得在
Authorization
头中验证传入的JSON Web令牌变得容易


如何验证通过Cookie传递的JWT,而不是标头?类似于
UseCookieAuthentication
,但对于只包含JWT的cookie。

我建议您查看以下链接

它们将JWT令牌存储在仅http的cookie中,以防止XSS攻击

然后,他们通过在Startup.cs中添加以下代码来验证cookie中的JWT令牌:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    AuthenticationScheme = "Cookie",
    CookieName = "access_token",
    TicketDataFormat = new CustomJwtDataFormat(
        SecurityAlgorithms.HmacSha256,
        tokenValidationParameters)
});
    // use the AuthorizationHeader middleware
    app.UseMiddleware<AuthorizationHeader>();
    // Add a new middleware validating access tokens.
    app.UseOAuthValidation();
其中CustomJwtDataFormat()是此处定义的自定义格式:

public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
{
    private readonly string algorithm;
    private readonly TokenValidationParameters validationParameters;

    public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
    {
        this.algorithm = algorithm;
        this.validationParameters = validationParameters;
    }

    public AuthenticationTicket Unprotect(string protectedText)
        => Unprotect(protectedText, null);

    public AuthenticationTicket Unprotect(string protectedText, string purpose)
    {
        var handler = new JwtSecurityTokenHandler();
        ClaimsPrincipal principal = null;
        SecurityToken validToken = null;

        try
        {
            principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);

            var validJwt = validToken as JwtSecurityToken;

            if (validJwt == null)
            {
                throw new ArgumentException("Invalid JWT");
            }

            if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
            {
                throw new ArgumentException($"Algorithm must be '{algorithm}'");
            }

            // Additional custom validation of JWT claims here (if any)
        }
        catch (SecurityTokenValidationException)
        {
            return null;
        }
        catch (ArgumentException)
        {
            return null;
        }

        // Validation passed. Return a valid AuthenticationTicket:
        return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie");
    }

    // This ISecureDataFormat implementation is decode-only
    public string Protect(AuthenticationTicket data)
    {
        throw new NotImplementedException();
    }

    public string Protect(AuthenticationTicket data, string purpose)
    {
        throw new NotImplementedException();
    }
}
希望这有帮助

注意:同样需要注意的是,这种方法(仅http cookie中的令牌)将有助于防止XSS攻击,但不能免疫跨站点请求伪造(CSRF)攻击,因此您还必须使用防伪令牌或设置自定义头来防止这些攻击

此外,如果您不进行任何内容清理,攻击者仍然可以运行XSS脚本代表用户发出请求,即使启用了仅http cookie和CRSF保护。但是,攻击者将无法窃取包含令牌的仅http cookie,也无法从第三方网站发出请求

因此,您仍然应该对用户生成的内容(如评论等)执行严格的清理

编辑:这是写在博客帖子链接的评论中的,代码是OP几天前在问了这个问题后自己写的

对于那些对减少XSS暴露的另一种“cookie中的令牌”方法感兴趣的人,他们可以使用oAuth中间件,例如ASP.NET核心中的OpenId Connect服务器

在被调用以将令牌发送回客户端(ApplyTokenResponse())的令牌提供程序的方法中,您可以序列化令牌并将其存储到仅为http的cookie中:

using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Newtonsoft.Json;

namespace Shared.Providers
{
public class AuthenticationProvider : OpenIdConnectServerProvider
{

    private readonly IApplicationService _applicationservice;
    private readonly IUserService _userService;
    public AuthenticationProvider(IUserService userService, 
                                  IApplicationService applicationservice)
    {
        _applicationservice = applicationservice;
        _userService = userService;
    }

    public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
    {
        if (string.IsNullOrEmpty(context.ClientId))
        {
            context.Reject(
                error: OpenIdConnectConstants.Errors.InvalidRequest,
                description: "Missing credentials: ensure that your credentials were correctly " +
                             "flowed in the request body or in the authorization header");

            return Task.FromResult(0);
        }

        #region Validate Client
        var application = _applicationservice.GetByClientId(context.ClientId);

            if (applicationResult == null)
            {
                context.Reject(
                            error: OpenIdConnectConstants.Errors.InvalidClient,
                            description: "Application not found in the database: ensure that your client_id is correct");

                return Task.FromResult(0);
            }
            else
            {
                var application = applicationResult.Data;
                if (application.ApplicationType == (int)ApplicationTypes.JavaScript)
                {
                    // Note: the context is marked as skipped instead of validated because the client
                    // is not trusted (JavaScript applications cannot keep their credentials secret).
                    context.Skip();
                }
                else
                {
                    context.Reject(
                            error: OpenIdConnectConstants.Errors.InvalidClient,
                            description: "Authorization server only handles Javascript application.");

                    return Task.FromResult(0);
                }
            }
        #endregion Validate Client

        return Task.FromResult(0);
    }

    public override async Task HandleTokenRequest(HandleTokenRequestContext context)
    {
        if (context.Request.IsPasswordGrantType())
        {
            var username = context.Request.Username.ToLowerInvariant();
            var user = await _userService.GetUserLoginDtoAsync(
                // filter
                u => u.UserName == username
            );

            if (user == null)
            {
                context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "Invalid username or password.");
                return;
            }
            var password = context.Request.Password;

            var passWordCheckResult = await _userService.CheckUserPasswordAsync(user, context.Request.Password);


            if (!passWordCheckResult)
            {
                context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "Invalid username or password.");
                return;
            }

            var roles = await _userService.GetUserRolesAsync(user);

            if (!roles.Any())
            {
                context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidRequest,
                        description: "Invalid user configuration.");
                return;
            }
        // add the claims
        var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
        identity.AddClaim(ClaimTypes.NameIdentifier, user.Id, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
        identity.AddClaim(ClaimTypes.Name, user.UserName, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
         // add the user's roles as claims
        foreach (var role in roles)
        {
            identity.AddClaim(ClaimTypes.Role, role, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
        }
         context.Validate(new ClaimsPrincipal(identity));
        }
        else
        {
            context.Reject(
                    error: OpenIdConnectConstants.Errors.InvalidGrant,
                    description: "Invalid grant type.");
            return;
        }

        return;
    }

    public override Task ApplyTokenResponse(ApplyTokenResponseContext context)
    {
        var token = context.Response.Root;

        var stringified = JsonConvert.SerializeObject(token);
        // the token will be stored in a cookie on the client
        context.HttpContext.Response.Cookies.Append(
            "exampleToken",
            stringified,
            new Microsoft.AspNetCore.Http.CookieOptions()
            {
                Path = "/",
                HttpOnly = true, // to prevent XSS
                Secure = false, // set to true in production
                Expires = // your token life time
            }
        );

        return base.ApplyTokenResponse(context);
    }
}
}
然后,您需要确保每个请求都附加了cookie。您还必须编写一些中间件来拦截cookie并将其设置为标头:

public class AuthorizationHeader
{
    private readonly RequestDelegate _next;

    public AuthorizationHeader(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        var authenticationCookieName = "exampleToken";
        var cookie = context.Request.Cookies[authenticationCookieName];
        if (cookie != null)
        {

            if (!context.Request.Path.ToString().ToLower().Contains("/account/logout"))
            {
                if (!string.IsNullOrEmpty(cookie))
                {
                    var token = JsonConvert.DeserializeObject<AccessToken>(cookie);
                    if (token != null)
                    {
                        var headerValue = "Bearer " + token.access_token;
                        if (context.Request.Headers.ContainsKey("Authorization"))
                        {
                            context.Request.Headers["Authorization"] = headerValue;
                        }else
                        {
                            context.Request.Headers.Append("Authorization", headerValue);
                        }
                    }
                }
                await _next.Invoke(context);
            }
            else
            {
                // this is a logout request, clear the cookie by making it expire now
                context.Response.Cookies.Append(authenticationCookieName,
                                                "",
                                                new Microsoft.AspNetCore.Http.CookieOptions()
                                                {
                                                    Path = "/",
                                                    HttpOnly = true,
                                                    Secure = false,
                                                    Expires = DateTime.UtcNow.AddHours(-1)
                                                });
                context.Response.Redirect("/");
                return;
            }
        }
        else
        {
            await _next.Invoke(context);
        }
    }
}
此解决方案适用于api和mvc应用程序。但是,对于ajax和fetch请求,您必须编写一些自定义中间件,这些中间件不会将用户重定向到登录页面,而是返回401:

public class RedirectHandler
{
    private readonly RequestDelegate _next;

    public RedirectHandler(RequestDelegate next)
    {
        _next = next;
    }

    public bool IsAjaxRequest(HttpContext context)
    {
        return context.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
    }

    public bool IsFetchRequest(HttpContext context)
    {
        return context.Request.Headers["X-Requested-With"] == "Fetch";
    }

    public async Task Invoke(HttpContext context)
    {
        await _next.Invoke(context);
        var ajax = IsAjaxRequest(context);
        var fetch = IsFetchRequest(context);
        if (context.Response.StatusCode == 302 && (ajax || fetch))
        {
            context.Response.Clear();
            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            await context.Response.WriteAsync("Unauthorized");
            return;
        }
    }
}

我成功地实现了中间件(基于Darxtar回答):

//TokenController.cs
公共IActionResult Some()
{
...
var tokenString=new JwtSecurityTokenHandler().WriteToken(令牌);
Response.Cookies.Append(
“x”,
标记字符串,
新的CookieOptions()
{
Path=“/”
}
);
返回状态码(200,令牌字符串);
}
//JWTInHeaderMiddleware.cs
公共类JWTINHEADermiddware
{
private readonly RequestDelegate\u next;
公共jwtinheadermiddware(RequestDelegate-next)
{
_下一个=下一个;
}
公共异步任务调用(HttpContext上下文)
{
var name=“x”;
var cookie=context.Request.Cookies[name];
if(cookie!=null)
if(!context.Request.Headers.ContainsKey(“授权”))
context.Request.Headers.Append(“授权”、“承载者”+cookie);
wait_next.Invoke(上下文);
}
}
//Startup.cs
公共无效配置(IApplicationBuilder应用程序,IHostingEnvironment环境)
{
...
app.UseMiddleware();
...
}

好奇:如果你想使用cookies来传递不记名代币,那么使用不记名代币有什么意义?使用承载令牌而不是cookie的全部目的是为了避免诸如XSRF攻击之类的安全问题。如果您在等式中重新引入cookie,您将重新引入其威胁模型。@Pinpoint JWT不是严格意义上的承载令牌;它们可以通过承载头或cookie使用。我使用JWTs进行无状态“会话”,但仍然将它们存储在cookie中,因为浏览器支持很简单。XSS由cookie标志减轻。1。根据定义,JWT是不记名或PoP令牌(在第一种情况下,您不需要证明您是令牌的合法持有人,在第二种情况下,您需要向服务器提供占有证明)。2.恐怕,使用JWT表示“会话”并将其存储在身份验证cookie(它本身就是“会话”)中毫无意义。3.XSS与XSRF无关,它是一个完全不同的威胁。@Pinpoint我正在进行令牌身份验证,并将访问令牌JWT存储在(纯文本)cookie中,而不是HTML5存储中。我意识到XSS!=XSRF,你完全正确。我应该澄清一下:我选择Cookie是为了对XSS提供强大的安全性,这确实意味着我需要处理CSRF问题。TBH,您的场景听起来确实像是令牌和Cookie之间的奇怪混合。如果您真的想使用cookie,那么根本不要使用令牌身份验证,而是直接使用cookie进行身份验证。您必须处理XSRF风险,但这与您试图实现的场景没有什么不同。恕我直言,这并不值得,特别是当你知道这样做并不能真正缓解XSS攻击时:别忘了,如果我不能窃取HttpOnly cookie,当你的JS应用程序中存在XSS漏洞时,没有什么可以阻止我代表用户发送恶意API请求。我不得不问,你有没有查过那篇博文的作者是谁?你说的很有道理,不,我没有检查作者。我将研究一个更客观的解决方案。我已经用oauth2做了一些自定义的身份验证,我会很快编辑它以提供一个替代方案。哈哈,我仍然不确定你是否注意到:你链接了OP他自己的博客文章和代码。这就是我要问的。是的,我注意到了。因此,我想提供一个替代解决方案,这不仅仅是OP所写的。谢谢Darxtar,在我的应用程序中实现了中间件解决方案,它工作得很好。您使用此解决方案实现的cookie确实是一个仅用于HTTPC的cookie吗
    // use the AuthorizationHeader middleware
    app.UseMiddleware<AuthorizationHeader>();
    // Add a new middleware validating access tokens.
    app.UseOAuthValidation();
    [Authorize(Roles = "Administrator,User")]
public class RedirectHandler
{
    private readonly RequestDelegate _next;

    public RedirectHandler(RequestDelegate next)
    {
        _next = next;
    }

    public bool IsAjaxRequest(HttpContext context)
    {
        return context.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
    }

    public bool IsFetchRequest(HttpContext context)
    {
        return context.Request.Headers["X-Requested-With"] == "Fetch";
    }

    public async Task Invoke(HttpContext context)
    {
        await _next.Invoke(context);
        var ajax = IsAjaxRequest(context);
        var fetch = IsFetchRequest(context);
        if (context.Response.StatusCode == 302 && (ajax || fetch))
        {
            context.Response.Clear();
            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            await context.Response.WriteAsync("Unauthorized");
            return;
        }
    }
}
// TokenController.cs

public IActionResult Some()
{
    ...

    var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

    Response.Cookies.Append(
        "x",
        tokenString,
        new CookieOptions()
        {
            Path = "/"
        }
    );

    return StatusCode(200, tokenString);
}


// JWTInHeaderMiddleware.cs

public class JWTInHeaderMiddleware
{
    private readonly RequestDelegate _next;

    public JWTInHeaderMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        var name = "x";
        var cookie = context.Request.Cookies[name];

        if (cookie != null)
            if (!context.Request.Headers.ContainsKey("Authorization"))
                context.Request.Headers.Append("Authorization", "Bearer " + cookie);

        await _next.Invoke(context);
    }
}

// Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...

    app.UseMiddleware<JWTInHeaderMiddleware>();

    ...
}