web-dev-qa-db-fra.com

JWT sur .NET Core 2.0

JWT a eu l’aventure de travailler sur DotNet Core 2.0 (version finale à ce jour). Il existe une tonne de documentation, mais tout l'exemple de code semble utiliser des API obsolètes et être redéfini pour Core. Il est absolument vertigineux de comprendre comment exactement il est supposé être implémenté. J'ai essayé d'utiliser Jose, mais app. UseJwtBearerAuthentication est obsolète et il n'y a pas de documentation sur ce qu'il faut faire ensuite.

Quelqu'un a-t-il un projet Open Source utilisant le noyau 2.0 de Dotnet qui peut simplement analyser un JWT à partir de l'en-tête d'autorisation et me permettre d'autoriser les requêtes pour un jeton JWT codé HS256?

La classe ci-dessous ne lance aucune exception, mais aucune demande n'est autorisée et je ne reçois aucune indication pourquoi elles ne sont pas autorisées. Les réponses sont des 401 vides, donc pour moi cela indique qu'il n'y a pas eu d'exception, mais que le secret ne correspond pas.

Une chose étrange est que mes jetons sont cryptés avec l'algorithme HS256, mais je ne vois aucun indicateur pour lui dire de le forcer à utiliser cet algorithme n'importe où.

Voici le cours que j'ai jusqu'à présent:

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;
        }
    }
}
76
Michael Draper

Voici un échantillon minimum de travail complet avec un contrôleur. J'espère que vous pourrez le vérifier en utilisant un appel Postman ou JavaScript.

  1. appsettings.json, appsettings.Development.json. Ajouter une section. Notez que la clé doit être assez longue et que l'émetteur est une adresse du service:

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

    !!! Dans un projet réel, ne gardez pas la clé dans le fichier appsettings.json. Il doit être conservé dans la variable Environment et le prendre comme ceci:

    Environment.GetEnvironmentVariable("JWT_KEY");
    

UPDATE: En voyant comment fonctionnent les paramètres de base .net, vous n'avez pas besoin de le prendre exactement à partir d'Environnement. Vous pouvez utiliser le réglage. Cependant, au lieu de cela, nous pouvons écrire cette variable dans les variables d’environnement en production, notre code préférera les variables d’environnement à la configuration.

  1. AuthRequest.cs: Dto conservant les valeurs pour le login et le mot de passe:

    public class AuthRequest
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
    
  2. Startup.cs dans la méthode Configure () AVANT app.UseMvc ():

    app.UseAuthentication();
    
  3. Startup.cs dans 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. Ajouter un contrôleur:

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

C'est tout les gens! À votre santé!

PDATE: Les gens demandent comment obtenir l'utilisateur actuel. Faire:

  1. Dans Startup.cs dans ConfigureServices (), ajoutez

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  2. Dans un contrôleur, ajoutez au constructeur:

    private readonly int _currentUser;
    public MyController(IHttpContextAccessor httpContextAccessor)
    {
       _currentUser = httpContextAccessor.CurrentUser();
    }
    
  3. Ajoutez quelque part une extension et utilisez-la dans votre contrôleur (avec ....)

    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;
        }
    }
    
81
alerya

Mon tokenValidationParameters fonctionne quand ils ressemblent à ceci:

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

et

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

De plus, ajoutez options.RequireHttpsMetadata = false comme ceci:

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

EDIT:

N'oubliez pas d'appeler

 app.UseAuthentication();

dans Startup.cs -> Configurer la méthode avant app.UseMvc ();

17

Voici une solution pour vous.

Dans votre startup.cs, commencez par le configurer en tant que services:

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

deuxièmement, appelez ces services dans la configuration

          app.UseAuthentication();

maintenant vous pouvez l'utiliser dans votre contrôleur en ajoutant un attribut

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

Pour plus de détails sur le code source qui utilise angular en tant que Frond-end, voir ici

7
Long Field

Implémentation de l'authentification de jeton de porteur JWT Asp.net Core 2.0 avec Web Api

Ajouter un package " 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 Configurer ()

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

User.cs // C'est une classe de modèle, par exemple. Cela peut être n'importe quoi.

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 // C'est juste une classe de contexte. Cela peut être n'importe quoi.

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

Test sur PostMan: You will receive token in response.

Passez TokenType et AccessToken dans l'en-tête dans d'autres services Web. enter image description here

Bonne chance! Je suis juste débutant. Je n'ai passé qu'une semaine à commencer à apprendre le noyau asp.net.

7
Abdul Hameed

Voici mon implémentation pour une 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"
  },
  ...
}

Le code ci-dessus active l’authentification sur tous les contrôleurs. Pour autoriser l'accès anonyme, vous pouvez décorer un contrôleur entier:

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

ou simplement décorer une méthode pour autoriser un seul point de terminaison:

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

Remarques:

  • Ceci est ma première tentative d'authentification AD - si quelque chose ne va pas, s'il vous plaît faites le moi savoir!
  • Audience doit correspondre à ID de la ressource demandé par le client. Dans notre cas, notre client (une application Web Angular a été enregistré séparément dans Azure AD et il a utilisé son ID client, que nous avons enregistré en tant que public dans l'API.
  • ClientId est appelée ID d'application dans le portail Azure (pourquoi ??), l'ID d'application de l'enregistrement de l'application pour l'API.
  • TenantId s'appelle ID d'annuaire dans le portail Azure (pourquoi ??), situé sous Azure Active Directory> Propriétés
  • Si vous déployez l'API en tant qu'application Web hébergée sur Azure, assurez-vous de définir les paramètres de l'application:

    par exemple. AzureAD: Audience/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

4
pcdev

Juste pour mettre à jour l'excellente réponse de @alerya, j'ai dû modifier la classe d'assistance pour ressembler à ceci.

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

Ensuite, je pourrais obtenir l'ID utilisateur dans ma couche de service. Je sais que c'est facile dans le contrôleur, mais un défi plus bas.

3
spankymac