JWT trên .NET Core 2.0


83

Tôi đã trải qua một cuộc phiêu lưu khá thú vị để JWT hoạt động trên DotNet core 2.0 (hiện đã có bản phát hành cuối cùng ngày hôm nay). Có một tấn tài liệu, nhưng tất cả các mẫu mã có vẻ là sử dụng API phản đối và sắp tới trong tươi Core, Nó tích cực chóng mặt để tìm ra cách chính xác nó là nghĩa vụ phải được thực hiện. Tôi đã thử sử dụng Jose, nhưng ứng dụng. UseJwtBearerAuthentication đã không còn được dùng nữa và không có tài liệu nào về việc cần làm tiếp theo.

Có ai có dự án nguồn mở sử dụng dotnet core 2.0 có thể chỉ cần phân tích cú pháp JWT từ tiêu đề ủy quyền và cho phép tôi ủy quyền yêu cầu mã thông báo JWT được mã hóa HS256 không?

Lớp bên dưới không đưa ra bất kỳ ngoại lệ nào, nhưng không có yêu cầu nào được cho phép và tôi không nhận được dấu hiệu tại sao chúng không được phép. Các câu trả lời trống 401, vì vậy đối với tôi điều đó cho thấy không có ngoại lệ, nhưng bí mật không khớp.

Một điều kỳ lạ là các mã thông báo của tôi được mã hóa bằng thuật toán HS256, nhưng tôi không thấy chỉ báo nào cho biết buộc nó phải sử dụng thuật toán đó ở bất kỳ đâu.

Đây là lớp tôi có cho đến nay:

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace Site.Authorization
{
    public static class SiteAuthorizationExtensions
    {
        public static IServiceCollection AddSiteAuthorization(this IServiceCollection services)
        {
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SECRET_KEY"));

            var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKeys = new List<SecurityKey>{ signingKey },


                // Validate the token expiry
                ValidateLifetime = true,
            };

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;


            })

            .AddJwtBearer(o =>
            {
                o.IncludeErrorDetails = true;
                o.TokenValidationParameters  = tokenValidationParameters;
                o.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();

                        c.Response.StatusCode = 401;
                        c.Response.ContentType = "text/plain";

                        return c.Response.WriteAsync(c.Exception.ToString());
                    }

                };
            });

            return services;
        }
    }
}

Tôi tắt ký xác nhận không có thay đổi, tất cả các yêu cầu sử dụng [Duyệt] 401
Michael Draper

2
Bạn có thể đăng mã đầy đủ? hoặc một dự án trình diễn tôi rất thích xem làm thế nào bạn nhận được điều này để làm việc ...
Piotr Stulinski


Có một bài khác có thể giúp bạn. stackoverflow.com/a/48295906/8417618
Marco Barbero

Câu trả lời:


87

Đây là một mẫu tối thiểu hoạt động đầy đủ với một bộ điều khiển. Tôi hy vọng bạn có thể kiểm tra nó bằng cách sử dụng Postman hoặc JavaScript call.

  1. appsettings.json, appsettings.Development.json. Thêm một phần. Lưu ý, Khóa phải khá dài và Nhà phát hành là một địa chỉ của dịch vụ:

    ...
    ,"Tokens": {
        "Key": "Rather_very_long_key",
        "Issuer": "http://localhost:56268/"
    }
    ...
    

    !!! Trong dự án thực, không giữ Key trong tệp appsettings.json. Nó nên được giữ trong biến Môi trường và sử dụng nó như sau:

    Environment.GetEnvironmentVariable("JWT_KEY");
    

CẬP NHẬT : Xem cách cài đặt lõi .net hoạt động như thế nào, bạn không cần phải lấy chính xác nó từ Môi trường. Bạn có thể sử dụng cài đặt. Tuy nhiên, thay vào đó, chúng tôi có thể viết biến này vào các biến môi trường trong quá trình sản xuất, khi đó mã của chúng tôi sẽ thích các biến môi trường thay vì cấu hình.

  1. AuthRequest.cs: Để giữ các giá trị để chuyển thông tin đăng nhập và mật khẩu:

    public class AuthRequest
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
    
  2. Startup.cs trong phương thức Configure () BEFORE app.UseMvc ():

    app.UseAuthentication();
    
  3. Startup.cs trong ConfigureServices ():

    services.AddAuthentication()
        .AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
    
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidIssuer = Configuration["Tokens:Issuer"],
                ValidAudience = Configuration["Tokens:Issuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
            };
    
        });
    
  4. Thêm bộ điều khiển:

        [Route("api/[controller]")]
        public class TokenController : Controller
        {
            private readonly IConfiguration _config;
            private readonly IUserManager _userManager;
    
            public TokenController(IConfiguration configuration, IUserManager userManager)
            {
                _config = configuration;
                _userManager = userManager;
            }
    
            [HttpPost("")]
            [AllowAnonymous]
            public IActionResult Login([FromBody] AuthRequest authUserRequest)
            {
                var user = _userManager.FindByEmail(model.UserName);
    
                if (user != null)
                {
                    var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
                    if (checkPwd)
                    {
                        var claims = new[]
                        {
                            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                            new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
                        };
    
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
                        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
                        var token = new JwtSecurityToken(_config["Tokens:Issuer"],
                        _config["Tokens:Issuer"],
                        claims,
                        expires: DateTime.Now.AddMinutes(30),
                        signingCredentials: creds);
    
                        return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
                    }
                }
    
                return BadRequest("Could not create token");
            }}
    

Đó là tất cả mọi người! Chúc mừng!

CẬP NHẬT: Mọi người hỏi làm thế nào để có được Người dùng Hiện tại. Làm:

  1. Trong Startup.cs trong ConfigureServices () thêm

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  2. Trong một bộ điều khiển, hãy thêm vào hàm tạo:

    private readonly int _currentUser;
    public MyController(IHttpContextAccessor httpContextAccessor)
    {
       _currentUser = httpContextAccessor.CurrentUser();
    }
    
  3. Thêm vào đâu đó một tiện ích mở rộng và sử dụng nó trong Bộ điều khiển của bạn (sử dụng ....)

    public static class IHttpContextAccessorExtension
    {
        public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {
            var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
            int.TryParse(stringId ?? "0", out int userId);
    
            return userId;
        }
    }
    

1
Điều này rất hữu ích cho tôi. Điều duy nhất tôi vẫn chưa rõ là cách kiểm tra mã thông báo cho các cuộc gọi tiếp theo hoặc cách xác định người dùng hiện đang đăng nhập là ai.
Travesty

Nó thực sự dễ dàng. Trong lệnh gọi phương thức của bộ điều khiển: var currentUser = HttpContext.User.Identity.Name; Chúc mừng!
alerya

1
Dude đây là một sự trợ giúp rất lớn, cảm ơn bạn rất nhiều vì câu trả lời chi tiết!
Ryanman

1
Cảm ơn những thông tin sau đây. Nó đã cứu tôi khỏi lỗi 401 Không được phép. 3.Startup.cs trong phương thức Configure () TRƯỚC app.UseMvc (): app.UseAuthentication ();
Sumia

1
Đây là một mâu thuẫn, chúng tôi không nên lưu trữ khóa trong tệp cài đặt ứng dụng nhưng chúng tôi đang truy xuất nó ở đây Cấu hình ["Mã thông báo: Khóa"]). Nếu chúng ta có một dịch vụ khác truy xuất khóa một cách an toàn, chúng ta sẽ kết hợp nó như thế nào trong ConfigureServices?
War Gravy

18

tokenValidationParametersTác phẩm của tôi khi chúng trông như thế này:

 var tokenValidationParameters = new TokenValidationParameters
  {
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = GetSignInKey(),
      ValidateIssuer = true,
      ValidIssuer = GetIssuer(),
      ValidateAudience = true,
      ValidAudience = GetAudience(),
      ValidateLifetime = true,
      ClockSkew = TimeSpan.Zero
   };

    static private SymmetricSecurityKey GetSignInKey()
    {
        const string secretKey = "very_long_very_secret_secret";
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));

        return signingKey;
    }

    static private string GetIssuer()
    {
        return "issuer";
    }

    static private string GetAudience()
    {
        return "audience";
    }

Hơn nữa, hãy thêm các tùy chọn.RequireHttpsMetadata = false như thế này:

         .AddJwtBearer(options =>
       {         
           options.TokenValidationParameters =tokenValidationParameters         
           options.RequireHttpsMetadata = false;
       });

CHỈNH SỬA :

Đừng quên gọi

 app.UseAuthentication();

trong Startup.cs -> Định cấu hình phương thức trước app.UseMvc ();


Tôi cá rằng đó là app.UseAuthentication (); gọi đó sẽ làm thủ thuật, tôi không biết rằng tôi cần điều đó. Cảm ơn bạn!
Michael Draper

Tôi nghĩ rằng bạn cũng cần chỉ định ValidAudience, ValidIssuer và IssuerSagingKey. Nó đã không làm việc mà không có nó cho tôi
Adrian Księżarczyk

Vâng, đó chính xác là những gì nó đã được. Tôi cần thêm app.UseAuthentication () và đó là tất cả những gì cần làm. Cảm ơn rât nhiều!
Michael Draper

3
+1 cho app.UseAuthentication();ghi chú được gọi trước đó app.UseMvc();nếu bạn không, bạn sẽ nhận được 401 ngay cả khi mã thông báo được ủy quyền thành công - Tôi đã dành khoảng 2 ngày để làm việc đó!
pcdev

1
"app.UseAuthentication ();", tôi đã dành cả ngày để khắc phục sự cố 401 sau khi nâng cấp lõi .net từ 1.0 lên 2.0, nhưng không tìm thấy giải pháp cho đến khi tôi xem bài đăng này. Cảm ơn Adrian.
Chan

8

Triển khai xác thực mã thông báo mang mã hiệu JWT của Asp.net Core 2.0 với Web Api Demo

Thêm gói " Microsoft.AspNetCore.Authentication.JwtBearer "

Startup.cs ConfigureServices ()

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(cfg =>
            {
                cfg.RequireHttpsMetadata = false;
                cfg.SaveToken = true;

                cfg.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = "me",
                    ValidAudience = "you",
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret
                };

            });

Startup.cs Configure ()

// ===== Use Authentication ======
        app.UseAuthentication();

User.cs // Nó chỉ là một lớp mô hình. Nó có thể là bất cứ thứ gì.

public class User
{
    public Int32 Id { get; set; }
    public string Username { get; set; }
    public string Country { get; set; }
    public string Password { get; set; }
}

UserContext.cs // Nó chỉ là lớp ngữ cảnh. Nó có thể là bất cứ thứ gì.

public class UserContext : DbContext
{
    public UserContext(DbContextOptions<UserContext> options) : base(options)
    {
        this.Database.EnsureCreated();
    }

    public DbSet<User> Users { get; set; }
}

AccountController.cs

[Route("[controller]")]
public class AccountController : Controller
{

    private readonly UserContext _context;

    public AccountController(UserContext context)
    {
        _context = context;
    }

    [AllowAnonymous]
    [Route("api/token")]
    [HttpPost]
    public async Task<IActionResult> Token([FromBody]User user)
    {
        if (!ModelState.IsValid) return BadRequest("Token failed to generate");
        var userIdentified = _context.Users.FirstOrDefault(u => u.Username == user.Username);
            if (userIdentified == null)
            {
                return Unauthorized();
            }
            user = userIdentified;

        //Add Claims
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.UniqueName, "data"),
            new Claim(JwtRegisteredClaimNames.Sub, "data"),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken("me",
            "you",
            claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: creds);

        return Ok(new
        {
            access_token = new JwtSecurityTokenHandler().WriteToken(token),
            expires_in = DateTime.Now.AddMinutes(30),
            token_type = "bearer"
        });
    }
}

UserController.cs

[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly UserContext _context;

    public UserController(UserContext context)
    {
        _context = context;
        if(_context.Users.Count() == 0 )
        {
            _context.Users.Add(new User { Id = 0, Username = "Abdul Hameed Abdul Sattar", Country = "Indian", Password = "123456" });
            _context.SaveChanges();
        }
    }

    [HttpGet("[action]")]
    public IEnumerable<User> GetList()
    {
        return _context.Users.ToList();
    }

    [HttpGet("[action]/{id}", Name = "GetUser")]
    public IActionResult GetById(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if(user == null)
        {
            return NotFound();
        }
        return new ObjectResult(user);
    }


    [HttpPost("[action]")]
    public IActionResult Create([FromBody] User user)
    {
        if(user == null)
        {
            return BadRequest();
        }

        _context.Users.Add(user);
        _context.SaveChanges();

        return CreatedAtRoute("GetUser", new { id = user.Id }, user);

    }

    [HttpPut("[action]/{id}")]
    public IActionResult Update(long id, [FromBody] User user)
    {
        if (user == null)
        {
            return BadRequest();
        }

        var userIdentified = _context.Users.FirstOrDefault(u => u.Id == id);
        if (userIdentified == null)
        {
            return NotFound();
        }

        userIdentified.Country = user.Country;
        userIdentified.Username = user.Username;

        _context.Users.Update(userIdentified);
        _context.SaveChanges();
        return new NoContentResult();
    }


    [HttpDelete("[action]/{id}")]
    public IActionResult Delete(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound();
        }

        _context.Users.Remove(user);
        _context.SaveChanges();

        return new NoContentResult();
    }
}

Thử nghiệm trên PostMan: Bạn sẽ nhận được mã thông báo để đáp lại.

Chuyển TokenType và AccessToken vào Header trong các dịch vụ web khác. nhập mô tả hình ảnh ở đây

May mắn nhất! Tôi chỉ là người mới bắt đầu. Tôi chỉ dành một tuần để bắt đầu học phần lõi của asp.net.


Tôi nhận được InvalidOperationException: Không thể giải quyết dịch vụ cho loại 'WebApplication8.UserContext' trong khi cố gắng kích hoạt 'AccountController'. khi tôi thử gọi người đưa thư để Đăng lên tài khoản / api / token
Kirsten Greed

7

Đây là một giải pháp cho bạn.

Trong startup.cs của bạn, trước tiên, hãy định cấu hình nó thành các dịch vụ:

  services.AddAuthentication().AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                IssuerSigningKey = "somethong",
                ValidAudience = "something",
                :
            };
        });

thứ hai, gọi các dịch vụ này trong cấu hình

          app.UseAuthentication();

bây giờ bạn có thể sử dụng nó trong bộ điều khiển của mình bằng cách thêm thuộc tính

          [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
          [HttpGet]
          public IActionResult GetUserInfo()
          {

Để biết đầy đủ chi tiết mã nguồn sử dụng góc cạnh làm Frond-end xem tại đây


Đây là câu trả lời đã cứu miếng thịt xông khói của tôi! Sẽ rất tuyệt nếu chỉ có thể sử dụng [Ủy quyền]. Hãy tưởng tượng điều này có thể được xử lý trong Startup.cs
Simon

1
Simon, bởi vì, bạn có thể có nhiều hơn một lược đồ trong cùng một ứng dụng mvc lõi asp.net, như services.AddAuthentication (). AddCookie (). AddJwtBearer ();
Trường dài

1
bạn cũng có thể đặt một Lược đồ xác thực mặc định bằng services.AddAuthorizationchức năng khi khởi động.
Neville Nazerane

Để theo dõi tuyên bố của @NevilleNazerane, mã để thiết lập Sơ đồ xác thực mặc định (sẽ được sử dụng với Trình trang trí [Authorize] thuần túy), mã này là câu trả lời cho câu hỏi này. Đó là services.AddAuthentication (sharedOptions => {sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;})
Ryanman,

Nếu tôi cố gắng làm theo ví dụ này cho IssuerSignedKey, tôi gặp lỗi Không thể chuyển đổi loại nguồn 'chuỗi' thành loại mục tiêu 'Microsoft.IdentityModel.Tokens.SecurityKey'
Kirsten Greed

4

Đây là cách triển khai của tôi cho API .Net Core 2.0:

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(
        config =>
        {
            // This enables the AuthorizeFilter on all endpoints
            var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
            
        }
        ).AddJsonOptions(opt =>
        {
            opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });

        services.AddLogging();

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Audience = Configuration["AzureAD:Audience"];  
            options.Authority = Configuration["AzureAD:AADInstance"] + Configuration["AzureAD:TenantId"];
        });            
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication(); // THIS METHOD MUST COME BEFORE UseMvc...() !!
        app.UseMvcWithDefaultRoute();            
    }

appsettings.json:

{
  "AzureAD": {
    "AADInstance": "https://login.microsoftonline.com/",
    "Audience": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Domain": "mydomain.com",
    "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  ...
}

Đoạn mã trên cho phép xác thực trên tất cả các bộ điều khiển. Để cho phép truy cập ẩn danh, bạn có thể trang trí toàn bộ bộ điều khiển:

[Route("api/[controller]")]
[AllowAnonymous]
public class AnonymousController : Controller
{
    ...
}

hoặc chỉ trang trí một phương thức để cho phép một điểm cuối duy nhất:

    [AllowAnonymous]
    [HttpPost("anonymousmethod")]
    public async Task<IActionResult> MyAnonymousMethod()
    {
        ...
    }

Ghi chú:

  • Đây là nỗ lực đầu tiên của tôi với AD auth - nếu có gì sai sót, vui lòng cho tôi biết!

  • Audiencephải khớp với ID tài nguyên do khách hàng yêu cầu. Trong trường hợp của chúng tôi, khách hàng của chúng tôi (một ứng dụng web Angular) đã được đăng ký riêng trong Azure AD và nó đã sử dụng Id khách hàng mà chúng tôi đã đăng ký làm Đối tượng trong API

  • ClientIdđược gọi là ID ứng dụng trong Azure Portal (tại sao ??), ID ứng dụng của đăng ký ứng dụng cho API.

  • TenantIdđược gọi là ID thư mục trong Cổng Azure (tại sao ??), được tìm thấy trong Azure Active Directory> Thuộc tính

  • Nếu triển khai API dưới dạng Ứng dụng web được lưu trữ trên Azure, hãy đảm bảo bạn đặt Cài đặt ứng dụng:

    ví dụ. AzureAD: Khán giả / xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx


3

Để cập nhật câu trả lời tuyệt vời của @alerya, tôi đã phải sửa đổi lớp trợ giúp để trông như thế này;

public static class IHttpContextAccessorExtension
    {
        public static string CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {           
            var userId = httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; 
            return userId;
        }
    }

Sau đó, tôi có thể lấy userId trong lớp dịch vụ của mình. Tôi biết điều đó thật dễ dàng trong bộ điều khiển, nhưng còn một thách thức nữa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.