2016-05-23 19 views
7

Các phần mềm trung gian UseJwtBearerAuthentication trong ASP.NET Core giúp dễ dàng xác thực các Thẻ Web JSON vào trong các tiêu đề Authorization.Làm cách nào để xác thực JWT được chuyển qua cookie?

Làm cách nào để xác thực JWT được chuyển qua cookie, thay vì tiêu đề? Một cái gì đó như UseCookieAuthentication, nhưng đối với một cookie chỉ chứa một JWT.

+2

Tò mò: điểm của việc sử dụng mã thông báo mang là gì nếu bạn muốn sử dụng cookie để lưu chúng? Toàn bộ điểm sử dụng mã thông báo của người mang thay vì cookie là để tránh các vấn đề bảo mật như tấn công XSRF. Nếu bạn giới thiệu lại cookie trong phương trình, bạn sẽ giới thiệu lại mô hình mối đe dọa của nó. – Pinpoint

+1

@Pinpoint JWTs không phải là mã thông báo mang nhãn hiệu nghiêm ngặt; họ có thể được sử dụng hoặc thông qua một tiêu đề Bearer, hoặc thông qua cookie. Tôi đang sử dụng JWTs để thực hiện "phiên" không trạng thái, nhưng vẫn lưu trữ chúng trong cookie vì trình duyệt hỗ trợ rất đơn giản. XSS được giảm thiểu bởi các cờ cookie. –

+0

1. theo định nghĩa, JWTs là mã nhận dạng hoặc mã thông báo PoP (trong trường hợp đầu tiên, bạn không cần phải chứng minh bạn là chủ sở hữu hợp pháp của mã thông báo, trong thẻ thứ hai, bạn cần cung cấp cho máy chủ bằng chứng chiếm hữu). 2. sử dụng JWTs để đại diện cho một "phiên" và lưu trữ chúng trong một cookie xác thực (mà chính nó là một "phiên") làm cho không có ý nghĩa, tôi sợ. 3. XSS không có gì để làm với XSRF, đó là một mối đe dọa hoàn toàn khác. – Pinpoint

Trả lời

7

Tôi khuyên bạn nên xem liên kết sau.

https://stormpath.com/blog/token-authentication-asp-net-core

Họ lưu trữ JWT thẻ trong một http chỉ Cookie để ngăn chặn các cuộc tấn công XSS.

Họ sau đó xác nhận token JWT trong cookie bằng cách thêm đoạn mã sau trong Startup.cs:

app.UseCookieAuthentication(new CookieAuthenticationOptions 
{ 
    AutomaticAuthenticate = true, 
    AutomaticChallenge = true, 
    AuthenticationScheme = "Cookie", 
    CookieName = "access_token", 
    TicketDataFormat = new CustomJwtDataFormat(
     SecurityAlgorithms.HmacSha256, 
     tokenValidationParameters) 
}); 

đâu CustomJwtDataFormat() là định dạng tùy chỉnh của họ được xác định ở đây:

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(); 
    } 
} 

Một giải pháp sẽ là viết một số phần mềm trung gian tùy chỉnh sẽ chặn từng yêu cầu, xem nó có cookie hay không, giải nén JWT khỏi cookie và thêm tiêu đề Cấp phép ngay trước khi nó đến bộ lọc ủy quyền của bộ điều khiển của bạn. Dưới đây là một số mã mà làm việc cho tokens OAuth, để có được những ý tưởng:

using System.Threading.Tasks; 
using Microsoft.AspNetCore.Http; 
using Microsoft.Extensions.Logging; 

namespace MiddlewareSample 
{ 
    public class JWTInHeaderMiddleware 
    { 
     private readonly RequestDelegate _next; 

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

     public async Task Invoke(HttpContext context) 
     { 
      var authenticationCookieName = "access_token"; 
      var cookie = context.Request.Cookies[authenticationCookieName]; 
      if (cookie != null) 
      { 
       var token = JsonConvert.DeserializeObject<AccessToken>(cookie); 
       context.Request.Headers.Append("Authorization", "Bearer " + token.access_token); 
      } 

      await _next.Invoke(context); 
     } 
    } 
} 

... nơi AccessToken là lớp sau:

public class AccessToken 
{ 
    public string token_type { get; set; } 
    public string access_token { get; set; } 
    public string expires_in { get; set; } 
} 

Hope this helps. Lưu ý: Điều quan trọng cần lưu ý là cách làm việc này (token trong http chỉ cookie) sẽ giúp ngăn chặn các cuộc tấn công XSS nhưng không miễn nhiễm với các cuộc tấn công Cross Site Request Forgery (CSRF), do đó bạn cũng phải sử dụng mã thông báo chống giả mạo hoặc đặt tiêu đề tùy chỉnh để ngăn chặn các thẻ đó. Ngoài ra, nếu bạn không thực hiện bất kỳ nội dung nào về vệ sinh, kẻ tấn công vẫn có thể chạy tập lệnh XSS để thực hiện yêu cầu thay mặt cho người dùng, ngay cả khi chỉ bật cookie http và bảo vệ CRSF. Tuy nhiên, kẻ tấn công sẽ không thể ăn cắp các cookie chỉ http chứa các thẻ, cũng như kẻ tấn công sẽ không thể thực hiện các yêu cầu từ trang web của bên thứ ba.

Do đó bạn nên vẫn thực hiện thanh trùng nặng về nội dung do người dùng tạo ra như bình luận vv ...

EDIT: Nó được viết trong các ý kiến ​​rằng bài viết trên blog được liên kết và các mã đã được viết bởi các OP mình một vài ngày trước sau khi hỏi câu hỏi này.

Đối với những người quan tâm đến cách tiếp cận "mã thông báo trong cookie" khác để giảm phơi nhiễm XSS, họ có thể sử dụng phần mềm trung gian oAuth như OpenId Connect Server trong ASP.NET Core.

Trong phương pháp của nhà cung cấp token được viện dẫn để gửi mã thông báo lại (ApplyTokenResponse()) cho khách hàng, bạn có thể serialize token và lưu nó vào một cookie là http chỉ:

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); 
    } 
} 
} 

Sau đó, bạn cần đảm bảo rằng mỗi yêu cầu đều có cookie được đính kèm.Bạn cũng phải viết một số trung gian để đánh chặn cookie và đặt nó vào tiêu đề:

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); 
     } 
    } 
} 

Trong Configure() của startup.cs:

// use the AuthorizationHeader middleware 
    app.UseMiddleware<AuthorizationHeader>(); 
    // Add a new middleware validating access tokens. 
    app.UseOAuthValidation(); 

Sau đó bạn có thể sử dụng thuộc tính Authorize bình thường.

[Authorize(Roles = "Administrator,User")] 

Giải pháp này hoạt động cho cả ứng dụng api và mvc. Đối với ajax và lấy yêu cầu tuy nhiên bạn phải viết một số trung gian tùy chỉnh mà sẽ không chuyển hướng người dùng đến trang đăng nhập và thay vào đó trả về một 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; 
     } 
    } 
} 
+3

Tôi phải hỏi, bạn có bao giờ kiểm tra xem tác giả của bài đăng trên blog đó là ai không? https://i.ytimg.com/vi/OGAu_DeKckI/hqdefault.jpg – KreepN

+0

Bạn thực hiện một điểm rất hợp lệ, không tôi không kiểm tra tác giả. Tôi sẽ xem xét một giải pháp khách quan hơn. Tôi đã thực hiện một số xác thực xác thực hợp lệ tùy chỉnh bằng oauth2, tôi sẽ chỉnh sửa sớm để cung cấp thay thế. – Darxtar

+0

Rất tiếc, tôi vẫn không chắc chắn bạn nhận thấy: bạn đã liên kết OP với bài đăng và mã blog của riêng mình. Đó là tất cả những gì tôi đã hỏi. – KreepN

0

tôi thực hiện các middleware thành công (Darxtar câu trả lời):

// TokenController.cs 

[AllowAnonymous] 
[HttpGet] 
public IActionResult Get([FromQuery]string username, [FromQuery]string password) 
{ 
    ... 

    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>(); 

    ... 

} 
Các vấn đề liên quan