.NET CORE 3.1 intègre l'authentification et l'autorisation JWT

 Site Web officiel : JSON Web Tokens - jwt.io JSON Web Token (JWT) est un moyen compact et sûr pour les URL de représenter les revendications à transférer entre deux parties. Les revendications dans un JWT sont encodées sous la forme d'un objet JSON qui est signé numériquement à l'aide de JSON Signature Web (JWS) https://jwt.io/ JWT : le nom complet est JSON Web Token, qui est actuellement la solution la plus populaire pour l'authentification inter-domaines, la connexion distribuée et l'authentification unique.

 

En termes simples, JWT est un jeton qui peut représenter l'identité de l'utilisateur, et le jeton JWT peut être utilisé pour vérifier l'identité de l'utilisateur dans l'interface API afin de confirmer si l'utilisateur est autorisé à accéder à l'API.

Autorisation : il s'agit du scénario le plus courant pour l'utilisation de JWT. Une fois l'utilisateur connecté, chaque demande ultérieure inclura le JWT, permettant à l'utilisateur d'accéder aux itinéraires, services et ressources autorisés par ce jeton.

Lors de l'authentification, lorsqu'un utilisateur se connecte avec succès avec ses informations d'identification, un jeton Web JSON est renvoyé. Étant donné que les jetons sont des informations d'identification, il faut faire très attention pour éviter les problèmes de sécurité. En général, vous ne devez pas conserver les jetons plus longtemps que nécessaire.

Chaque fois qu'un utilisateur souhaite accéder à une route ou à une ressource protégée, l'agent utilisateur doit envoyer le JWT en mode porteur, généralement dans l'en-tête Authorization , le contenu de l'en-tête doit ressembler à ceci :

Autorisation : Porteur <token>

1. L'application demande l'autorisation au serveur d'autorisation ;

2. Vérifiez l'identité de l'utilisateur, si la vérification réussit, renvoyez le jeton ;

3. L'application utilise le jeton d'accès pour accéder à la ressource protégée.

 L'implémentation de JWT consiste à stocker les informations utilisateur sur le client et le serveur ne les enregistre pas. Chaque demande apporte le jeton pour vérifier l'état de connexion de l'utilisateur, de sorte que le service devienne sans état et que le cluster de serveurs soit facile à étendre.

Pour plus de connaissances théoriques, vous pouvez consulter le site officiel ou consulter les articles des internautes concernés, les articles recommandés suivants :

Reportez-vous au package intégré jwt dans nuget. Il convient de noter ici que si vous utilisez le framework de .NET Core 3.1, la version du package doit être 3.1.7

Microsoft.AspNetCore.Authentication.JwtBearerMicrosoft.AspNetCore.Authentication.JwtBearer

Ajouter une API de simulation d'accès aux données, créer un nouveau contrôleur ValuesController

Parmi eux, api/value1 est directement accessible et api/value2 a ajouté l'étiquette de fonctionnalité de vérification des autorisations [Autoriser].

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace jwtWebAPI.Controllers
{
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        [Route("api/values1")]
        public ActionResult<IEnumerable<string>> values1()
        {
            return new string[] { "value1", "value1" };
        }

        /**
         * 该接口用Authorize特性做了权限校验,如果没有通过权限校验,则http返回状态码为401
         * 调用该接口的正确姿势是:
         * 1.登陆,调用api/Auth接口获取到token
         * 2.调用该接口 api/value2 在请求的Header中添加参数 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiIxNTYwMzM1MzM3IiwiZXhwIjoxNTYwMzM3MTM3LCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiemhhbmdzYW4iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ.1S-40SrA4po2l4lB_QdzON_G5ZNT4P_6U25xhTcl7hI
         * Bearer后面有空格,且后面是第一步中接口返回的token值
         * */
        [HttpGet]
        [Route("api/value2")]
        [Authorize]
        public ActionResult<IEnumerable<string>> value2()
        {
            //这是获取自定义参数的方法
            var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
            var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
            return new string[] { "访问成功:这个接口登陆过的用户都可以访问", $"userName={userName}" };
        }

     
    }
}

Ajoutez l'API qui simule la connexion pour générer un jeton et créez un nouveau contrôleur AuthController

Voici une simulation de la vérification de connexion, qui vérifie uniquement que le mot de passe de l'utilisateur n'est pas vide et réussit la vérification. L'environnement réel améliore la logique de vérification de l'utilisateur et du mot de passe.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace jwtWebAPI.Controllers
{
    [ApiController]
    public class AuthController : Controller
    {
        /// <summary>
        /// 通过账号+密码获取Token
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="pwd"></param>
        /// <returns>Token</returns>
        [AllowAnonymous]
        [HttpGet]
        [Route("api/auth")]
        public IActionResult GetToken(string userName, string pwd)
        {
            if (!string.IsNullOrEmpty(userName))
            {
                //每次登陆动态刷新
                Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
                // push the user’s name into a claim, so we can identify the user later on.
                //这里可以随意加入自定义的参数,key可以自己随便起
                var claims = new[]
                {
                    new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                    new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(3)).ToUnixTimeSeconds()}"),
                    new Claim(ClaimTypes.NameIdentifier, userName)
                };
                //sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                //.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
                var token = new JwtSecurityToken(
                    //颁发者
                    issuer: Const.Domain,
                    //接收者
                    audience: Const.ValidAudience,
                    //过期时间(可自行设定,注意和上面的claims内部Exp参数保持一致)
                    expires: DateTime.Now.AddMinutes(3),
                    //签名证书
                    signingCredentials: creds,
                    //自定义参数
                    claims: claims
                    );

                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token)
                });
            }
            else
            {
                return BadRequest(new { message = "username or password is incorrect." });
            }
        }
    }
}

Le démarrage ajoute des configurations associées pour la vérification JWT

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace jwtWebAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //添加jwt验证:
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateLifetime = true,//是否验证失效时间
                        ClockSkew = TimeSpan.FromSeconds(30),  //时间偏移量(允许误差时间)
                        ValidateAudience = true,//是否验证Audience(验证之前的token是否失效)
                        //ValidAudience = Const.GetValidudience(),//Audience
                        //这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了
                        AudienceValidator = (m, n, z) =>
                        {
                            return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
                        },
                        ValidateIssuer = true,//是否验证Issuer(颁发者)
                        ValidAudience = Const.Domain,//Audience    【Const是新建的一个常量类】  接收者 
                        ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致      颁发者
                        ValidateIssuerSigningKey = true,//是否验证SecurityKey
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到秘钥SecurityKey
                    };
                    options.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            //Token expired
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }
                            return Task.CompletedTask;
                        }
                    };
                });

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        { 
            //添加jwt验证
            app.UseAuthentication();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Créer une classe constante Const

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace jwtWebAPI
{
    public class Const
    {
        /// <summary>
        /// 这里为了演示,写死一个密钥。实际生产环境可以从配置文件读取,这个是用网上工具随便生成的一个密钥(md5或者其他都可以)
        /// </summary>
        public const string SecurityKey = "48754F4C58F9EA428FE09D714E468211";

        /// <summary>
        /// 站点地址(颁发者、接受者),这里测试和当前本地运行网站相同,实际发到正式环境应为域名地址
        /// </summary>
        public const string Domain = "https://localhost:44345";

        /// <summary>
        /// 受理人,之所以弄成可变的是为了用接口动态更改这个值以模拟强制Token失效
        /// 真实业务场景可以在数据库或者redis存一个和用户id相关的值,生成token和验证token的时候获取到持久化的值去校验
        /// 如果重新登陆,则刷新这个值
        /// </summary>
        public static string ValidAudience;
    }
}

Le test d'autorisation de connexion JWT a réussi

Un code d'état de 401 a été renvoyé, qui est Non autorisé : l'accès est refusé en raison d'informations d'identification non valides. Cela signifie que la vérification JWT a pris effet et que notre interface a été protégée.

Appelez l'interface d'autorisation de connexion simulée : https://localhost : 44345 /api/auth?userName=xiongze&pwd=123456

Le mot de passe utilisateur ici est écrit avec désinvolture, car notre connexion simulée est seulement vérifiée comme non vide, donc tout peut être écrit.

Ensuite, nous avons obtenu une valeur de jeton au format xxx.yyy.zzz. Nous copions le jeton.

Ajoutez les paramètres JWT dans l'en-tête de requête de l'interface 401 ( https://localhost:44345/api/values2 ) tout à l'heure et ajoutez notre jeton

Appelez à nouveau notre interface de données simulée, mais cette fois nous ajoutons un en-tête, KEY : Authorization Value : la valeur de Bearer Tokne

Il convient de noter ici qu'il y a un espace après Bearer, puis c'est le jeton que nous avons obtenu à l'étape précédente.

Obtenez la valeur de retour, l'autorisation correcte est réussie, nous prenons en charge les paramètres de retour personnalisés, le code ci-dessus contient un contenu pertinent, tel que le nom d'utilisateur et d'autres informations non sensibles peuvent être renvoyées avec.

Lorsque le délai d'expiration défini par le jeton est écoulé, ou qu'un nouveau jeton est régénéré et non mis à jour à temps, notre autorisation expirera également, 401,

Opération de mise à niveau : isolation de l'autorité d'interface

L'opération ci-dessus signifie que tous les rôles avec une autorisation de connexion réussie peuvent appeler toutes les interfaces, nous voulons donc maintenant implémenter des restrictions d'isolation d'interface,

C'est-à-dire que bien que la connexion soit autorisée, mon interface est accessible avec des autorisations spécifiées.

Par exemple : l'interface de suppression ne peut être exploitée que par le rôle d'administrateur, donc bien que d'autres rôles soient autorisés à se connecter, ils n'ont pas la permission d'appeler l'interface de suppression.

Jetons un coup d'œil à la transformation et à la mise à niveau de l'opération d'origine.

ajouter une classe

Créez un nouveau dossier AuthManagement , ajoutez la classe PolicyRequirement et la classe PolicyHandler ,

Classe PolicyRequirement :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace jwtWebAPI.AuthManagement
{
    /// <summary>
    /// 权限承载实体
    /// </summary>
    public class PolicyRequirement : IAuthorizationRequirement
    {
        /// <summary>
        /// 用户权限集合
        /// </summary>
        public List<UserPermission> UserPermissions { get; private set; }
        /// <summary>
        /// 无权限action
        /// </summary>
        public string DeniedAction { get; set; }
        /// <summary>
        /// 构造
        /// </summary>
        public PolicyRequirement()
        {
            //没有权限则跳转到这个路由
            DeniedAction = new PathString("/api/nopermission");
            //用户有权限访问的路由配置,当然可以从数据库获取
            UserPermissions = new List<UserPermission> {
                              new UserPermission {  Url="/api/values3", UserName="admin"},
                          };
        }
    }

    /// <summary>
    /// 用户权限承载实体
    /// </summary>
    public class UserPermission
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// 请求Url
        /// </summary>
        public string Url { get; set; }
    }
}

Classe PolicyHandler (notez la différence entre 2.x et 3.x)

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace jwtWebAPI.AuthManagement
{
    public class PolicyHandler : AuthorizationHandler<PolicyRequirement>
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        public PolicyHandler(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
        {
            //赋值用户权限
            var userPermissions = requirement.UserPermissions;
            var httpContext = _httpContextAccessor.HttpContext;

            //请求Url
            var questUrl = httpContext.Request.Path.Value.ToUpperInvariant();
            //是否经过验证
            var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
            if (isAuthenticated)
            {
                if (userPermissions.GroupBy(g => g.Url).Any(w => w.Key.ToUpperInvariant() == questUrl))
                {
                    //用户名
                    var userName = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.NameIdentifier).Value;
                    if (userPermissions.Any(w => w.UserName == userName && w.Url.ToUpperInvariant() == questUrl))
                    {
                        context.Succeed(requirement);
                    }
                    else
                    {
                        无权限跳转到拒绝页面
                        //httpContext.Response.Redirect(requirement.DeniedAction);
                        return Task.CompletedTask;
                    }
                }
                else
                {
                    context.Succeed(requirement);
                }
            }
            return Task.CompletedTask;
        }
    }
}

Ajouter un rôle spécifié

Ajoutez des paramètres personnalisés à l'autorisation GetToken du contrôleur AuthController, comme  suit 

new Claim("Role", userName) //Voici le rôle, j'utilise plutôt le compte de connexion admin

Ajouter une méthode d'accès non autorisé dans le contrôleur   AuthController 

[AllowAnonymous]
[HttpGet]
[Route("api/nopermission")]
public IActionResult NoPermission()
{
     return Forbid("No Permission!");
}

Modifier la configuration de démarrage

Ajoutez le mode d'authentification de stratégie, ajoutez le schéma JWT et injectez le gestionnaire d'autorisation dans la méthode ConfigureServices de startup.cs 

Le fichier modifié est le suivant

using jwtWebAPI.AuthManagement;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace jwtWebAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services
                //添加策略鉴权模式
                .AddAuthorization(options =>
                {
                    options.AddPolicy("Permission", policy => policy.Requirements.Add(new PolicyRequirement()));
                })
                //添加JWT Scheme
                .AddAuthentication(s =>
                {
                    s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                //添加jwt验证:
                .AddJwtBearer(options => {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateLifetime = true,//是否验证失效时间
                        ClockSkew = TimeSpan.FromSeconds(30),  //时间偏移量(允许误差时间)
                        ValidateAudience = true,//是否验证Audience(验证之前的token是否失效)
                        //ValidAudience = Const.GetValidudience(),//Audience
                        //这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了
                        AudienceValidator = (m, n, z) =>
                        {
                            return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
                        },
                        ValidateIssuer = true,//是否验证Issuer(颁发者)
                        ValidAudience = Const.Domain,//Audience    【Const是新建的一个常量类】  接收者 
                        ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致      颁发者
                        ValidateIssuerSigningKey = true,//是否验证SecurityKey
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到秘钥SecurityKey
                    };
                    options.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            //Token expired
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }
                            return Task.CompletedTask;
                        }
                    };
                });

            //注入授权Handler
            services.AddSingleton<IAuthorizationHandler, PolicyHandler>();
            //注入获取HttpContext
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        { 
            //添加jwt验证
            app.UseAuthentication();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
            
        }
    }
}

Ajouter la méthode d'accès à l'API

Ajoutez la méthode de spécification de l'accès aux autorisations dans  le contrôleur ValuesController , comme suit :

/**
        * 这个接口必须用admin
        **/
        [HttpGet]
        [Route("api/values3")]
        [Authorize("Permission")]
        public ActionResult<IEnumerable<string>> values3()
        {
            //这是获取自定义参数的方法
            var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
            var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
            var role = auth.FirstOrDefault(t => t.Type.Equals("Role"))?.Value;

            return new string[] { "访问成功:这个接口有管理员权限才可以访问", $"userName={userName}", $"Role={role}" };
        }

Tester l'accès avec différentes autorisations

Nous utilisons la même méthode pour simuler la connexion, https://localhost:44345/api/auth?userName=xiongze&pwd=123

Notez que le compte n'a pas besoin de se connecter d'abord avec l'administrateur, puis d'utiliser le jeton renvoyé pour demander l'interface que nous venons d'ajouter pour accéder à l'autorisation spécifiée. Pour le moment, il n'y a pas d'autorisation d'accès, car il s'agit d'une autorisation d'administrateur accès.

Nous utilisons la même méthode pour simuler la connexion, https://localhost:44345/api/auth?userName=xiongze&pwd=123

Notez que le compte n'a pas besoin de se connecter d'abord avec l'administrateur, puis d'utiliser le jeton renvoyé pour demander l'interface que nous venons d'ajouter pour accéder à l'autorisation spécifiée. Pour le moment, il n'y a pas d'autorisation d'accès, car il s'agit d'une autorisation d'administrateur accès.

Nous utilisons la même méthode pour simuler la connexion, https://localhost:44345/api/auth?userName=admin&pwd=123

La visite a été un succès.

Adresse de réimpression du blog : https://www.cnblogs.com/xiongze520/p/15540035.html https://www.cnblogs.com/xiongze520/p/15540035.html

Je suppose que tu aimes

Origine blog.csdn.net/qq_26695613/article/details/129822445
conseillé
Classement