• asp.net core 2.0 web api基于JWT自定义策略授权


    JWT(json web token)是一种基于json的身份验证机制,流程如下:

     

     

    通过登录,来获取Token,再在之后每次请求的Header中追加Authorization为Token的凭据,服务端验证通过即可能获取想要访问的资源。关于JWT的技术,可参考网络上文章,这里不作详细说明,

    这篇博文,主要说明在asp.net core 2.0中,基于jwt的web api的权限设置,即在asp.net core中怎么用JWT,再次就是不同用户或角色因为权限问题,即使援用Token,也不能访问不该访问的资源。

    基本思路是我们自定义一个策略,来验证用户,和验证用户授权,PermissionRequirement是验证传输授权的参数。在Startup的ConfigureServices注入验证(Authentication),授权(Authorization),和JWT(JwtBearer)

    自定义策略:

    已封闭成AuthorizeRolicy.JWT nuget包,并发布到nuget上:

    https://www.nuget.org/packages/AuthorizePolicy.JWT/

    源码如下:

    JwtToken.cs

            /// <summary>
            /// 获取基于JWT的Token
            /// </summary>
            /// <param name="username"></param>
            /// <returns></returns>
            public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement)
            {
                var now = DateTime.UtcNow;
                var jwt = new JwtSecurityToken(
                    issuer: permissionRequirement.Issuer,
                    audience: permissionRequirement.Audience,
                    claims: claims,
                    notBefore: now,
                    expires: now.Add(permissionRequirement.Expiration),
                    signingCredentials: permissionRequirement.SigningCredentials
                );
                var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
                var response = new
                {
                    Status = true,
                    access_token = encodedJwt,
                    expires_in = permissionRequirement.Expiration.TotalMilliseconds,
                    token_type = "Bearer"
                };
                return response;
            }
    

      

    Permission.cs

        /// <summary>
        /// 用户或角色或其他凭据实体
        /// </summary>
        public class Permission
        {
            /// <summary>
            /// 用户或角色或其他凭据名称
            /// </summary>
            public virtual string Name
            { get; set; }
            /// <summary>
            /// 请求Url
            /// </summary>
            public virtual string Url
            { get; set; }
        }
    

      PermissionRequirement.cs

     1 using Microsoft.AspNetCore.Authorization;
     2 using Microsoft.IdentityModel.Tokens;
     3 using System;
     4 using System.Collections.Generic;
     5 
     6 namespace AuthorizePolicy.JWT
     7 {
     8     /// <summary>
     9     /// 必要参数类
    10     /// </summary>
    11     public class PermissionRequirement : IAuthorizationRequirement
    12     {
    13         /// <summary>
    14         /// 用户权限集合
    15         /// </summary>
    16         public List<Permission> Permissions { get; private set; }
    17         /// <summary>
    18         /// 无权限action
    19         /// </summary>
    20         public string DeniedAction { get; set; }
    21 
    22         /// <summary>
    23         /// 认证授权类型
    24         /// </summary>
    25         public string ClaimType { internal get; set; }
    26         /// <summary>
    27         /// 请求路径
    28         /// </summary>
    29         public string LoginPath { get; set; } = "/Api/Login";
    30         /// <summary>
    31         /// 发行人
    32         /// </summary>
    33         public string Issuer { get; set; }
    34         /// <summary>
    35         /// 订阅人
    36         /// </summary>
    37         public string Audience { get; set; }
    38         /// <summary>
    39         /// 过期时间
    40         /// </summary>
    41         public TimeSpan Expiration { get; set; }
    42         /// <summary>
    43         /// 签名验证
    44         /// </summary>
    45         public SigningCredentials SigningCredentials { get; set; }
    46 
    47         /// <summary>
    48         /// 构造
    49         /// </summary>
    50         /// <param name="deniedAction">无权限action</param>
    51         /// <param name="userPermissions">用户权限集合</param>
    52 
    53         /// <summary>
    54         /// 构造
    55         /// </summary>
    56         /// <param name="deniedAction">拒约请求的url</param>
    57         /// <param name="permissions">权限集合</param>
    58         /// <param name="claimType">声明类型</param>
    59         /// <param name="issuer">发行人</param>
    60         /// <param name="audience">订阅人</param>
    61         /// <param name="signingCredentials">签名验证实体</param>
    62         public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration)
    63         {
    64             ClaimType = claimType;
    65             DeniedAction = deniedAction;
    66             Permissions = permissions;
    67             Issuer = issuer;
    68             Audience = audience;
    69             Expiration = expiration;
    70             SigningCredentials = signingCredentials;
    71         }
    72     }
    73 }

     自定义策略类PermissionHandler.cs

    /// <summary>
    /// 权限授权Handler
    /// </summary>
    public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
    {
        /// <summary>
        /// 验证方案提供对象
        /// </summary>
        public IAuthenticationSchemeProvider Schemes { get; set; }
        /// <summary>
        /// 自定义策略参数
        /// </summary>
        public PermissionRequirement Requirement
        { get; set; }
        /// <summary>
        /// 构造
        /// </summary>
        /// <param name="schemes"></param>
        public PermissionHandler(IAuthenticationSchemeProvider schemes)
        {
            Schemes = schemes;
        }
    
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            ////赋值用户权限       
            Requirement = requirement;
            //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
            var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;
            //请求Url
            var questUrl = httpContext.Request.Path.Value.ToLower();
            //判断请求是否停止
            var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
            foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
            {
                var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;
                if (handler != null && await handler.HandleRequestAsync())
                {
                    context.Fail();
                    return;
                }
            }
            //判断请求是否拥有凭据,即有没有登录
            var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
            if (defaultAuthenticate != null)
            {
                var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
                //result?.Principal不为空即登录成功
                if (result?.Principal != null)
                {
                    httpContext.User = result.Principal;
                    //权限中是否存在请求的url
                    if (Requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0)
                    {
                        var name = httpContext.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType).Value;
                        //验证权限
                        if (Requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() <= 0)
                        {
                            //无权限跳转到拒绝页面
                            httpContext.Response.Redirect(requirement.DeniedAction);
                        }
                    }
                    //判断过期时间
                    if (DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration).Value) >= DateTime.Now)
                    {
                        context.Succeed(requirement);
                    }
                    else
                    {
                        context.Fail();
                    }
                    return;
                }
            }
            //判断没有登录时,是否访问登录的url,并且是Post请求,并助是form表单提交类型,否则为失败
    
            if (!questUrl.Equals(Requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
            {
                context.Fail();
                return;
            }
            context.Succeed(requirement);
        }
    }
    

      

    新建asp.net core 2.0的web api项目,并在项目添加AuthorizePolicy.JWT如图

    先设置配置文件,用户可以定义密匙和发生人,订阅人

      "Audience": {

        "Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",

        "Issuer": "gsw",

        "Audience": "everone"

      }

    在ConfigureServices中注入验证(Authentication),授权(Authorization),和JWT(JwtBearer)

    Startup.cs

     1         public void ConfigureServices(IServiceCollection services)
     2         {
     3             var urls = "http://localhost:39287/";
     4             services.AddCors(options =>
     5             options.AddPolicy("MyDomain",
     6         builder => builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials()));
     7 
     8             //读取配置文件
     9             var audienceConfig = Configuration.GetSection("Audience");
    10             var symmetricKeyAsBase64 = audienceConfig["Secret"];
    11             var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
    12             var signingKey = new SymmetricSecurityKey(keyByteArray);
    13             var tokenValidationParameters = new TokenValidationParameters
    14             {
    15                 ValidateIssuerSigningKey = true,
    16                 IssuerSigningKey = signingKey,
    17                 ValidateIssuer = true,
    18                 ValidIssuer = audienceConfig["Issuer"],//发行人
    19                 ValidateAudience = true,
    20                 ValidAudience = audienceConfig["Audience"],//订阅人
    21                 ValidateLifetime = true,
    22                 ClockSkew = TimeSpan.Zero,
    23                 RequireExpirationTime = true,
    24 
    25             };
    26             var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
    27             //这个集合模拟用户权限表,可从数据库中查询出来
    28             var permission = new List<Permission> {
    29                               new Permission {  Url="/", Name="admin"},
    30                               new Permission {  Url="/api/values", Name="admin"},
    31                               new Permission {  Url="/", Name="system"},
    32                               new Permission {  Url="/api/values1", Name="system"}
    33                           };
    34             //如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名
    35             var permissionRequirement = new PermissionRequirement(
    36                 "/api/denied", permission,
    37                 ClaimTypes.Role,
    38                 audienceConfig["Issuer"],
    39                 audienceConfig["Audience"],
    40                 signingCredentials,
    41                 expiration: TimeSpan.FromSeconds(10)//设置Token过期时间
    42                 );
    43             services.AddAuthorization(options =>
    44             {
    45 
    46                 options.AddPolicy("Permission",
    47                           policy => policy.Requirements.Add(permissionRequirement));
    48 
    49             }).AddAuthentication(options =>
    50             {
    51                 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    52                 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    53             })
    54             .AddJwtBearer(o =>
    55             {
    56                 //不使用https
    57                 o.RequireHttpsMetadata = false;
    58                 o.TokenValidationParameters = tokenValidationParameters;
    59                 o.Events = new JwtBearerEvents
    60                 {
    61                     OnTokenValidated = context =>
    62                     {
    63                         if (context.Request.Path.Value.ToString() == "/api/logout")
    64                         {
    65                             var token = ((context as TokenValidatedContext).SecurityToken as JwtSecurityToken).RawData;
    66                         }
    67                         return Task.CompletedTask;
    68                     }
    69                 };
    70             });
    71             //注入授权Handler
    72             services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
    73             services.AddSingleton(permissionRequirement);
    74             services.AddMvc();
    75         }

    在需要授的Controller上添加授权特性

        [Authorize("Permission")]

    PermissionController类有两个方法,一个是登录,验证用户名和密码是否正确,如果正确就发放Token,如果失败,验证失败,别一个成功登后的无权限导航action。

     1     [Authorize("Permission")]
     2     [EnableCors("MyDomain")]
     3     public class PermissionController : Controller
     4     {
     5         /// <summary>
     6         /// 自定义策略参数
     7         /// </summary>
     8         PermissionRequirement _requirement;
     9         public PermissionController(PermissionRequirement requirement)
    10         {
    11             _requirement = requirement;
    12         }
    13         [AllowAnonymous]
    14         [HttpPost("/api/login")]
    15         public IActionResult Login(string username, string password, string role)
    16         {
    17             var isValidated = username == "gsw" && password == "111111";
    18             if (!isValidated)
    19             {
    20                 return new JsonResult(new
    21                 {
    22                     Status = false,
    23                     Message = "认证失败"
    24                 });
    25             }
    26             else
    27             {               
    28                 //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色
    29                 var claims = new Claim[] { new Claim(ClaimTypes.Name, username), new Claim(ClaimTypes.Role, role), new Claim(ClaimTypes.Expiration ,DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())};
    30                 //用户标识
    31                 var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
    32                 identity.AddClaims(claims);
    33              
    34                 var token = JwtToken.BuildJwtToken(claims, _requirement);
    35                 return new JsonResult(token);
    36             }
    37         }
    38 
    39         [HttpPost("/api/logout")]
    40         public  IActionResult Logout()
    41         {
    42             return Ok();
    43         }
    44 
    45         [AllowAnonymous]
    46         [HttpGet("/api/denied")]
    47         public IActionResult Denied()
    48         {
    49             return new JsonResult(new
    50             {
    51                 Status = false,
    52                 Message = "你无权限访问"
    53             });
    54         }
    55     }

    下面定义一个控制台(.NetFramewrok)程序,用RestSharp来访问我们定义的web api,其中1为admin角色登录,2为system角色登录,3为错误用户密码登录,4是一个查询功能,在startup.cs中,admin角色是具有查询/api/values的权限的,所以用admin登录是能正常访问的,用system登录,能成功登录,但没有权限访问/api/values,用户名密码错误,访问/aip/values,直接是没有授权的

      class Program
        {
            /// <summary>
            /// 访问Url
            /// </summary>
            static string _url = "http://localhost:39286";
            static void Main(string[] args)
            {
                dynamic token = null;
                while (true)
                {
                    Console.WriteLine("1、登录【admin】 2、登录【system】 3、登录【错误用户名密码】 4、查询数据 ");
                    var mark = Console.ReadLine();
                    var stopwatch = new Stopwatch();
                    stopwatch.Start();
                    switch (mark)
                    {
                        case "1":
                            token = AdminLogin();
                            break;
                        case "2":
                            token = SystemLogin();
                            break;
                        case "3":
                            token = NullLogin();
                            break;
                        case "4":
                            AdminInvock(token);
                            break;
                    }
                    stopwatch.Stop();
                    TimeSpan timespan = stopwatch.Elapsed;
                    Console.WriteLine($"间隔时间:{timespan.TotalSeconds}");
                }
            }
            static dynamic NullLogin()
            {
                var loginClient = new RestClient(_url);
                var loginRequest = new RestRequest("/api/login", Method.POST);
                loginRequest.AddParameter("username", "gswaa");
                loginRequest.AddParameter("password", "111111");
                //或用用户名密码查询对应角色
                loginRequest.AddParameter("role", "system");
                IRestResponse loginResponse = loginClient.Execute(loginRequest);
                var loginContent = loginResponse.Content;
                Console.WriteLine(loginContent);
                return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);
            }
            static dynamic SystemLogin()
            {
                var loginClient = new RestClient(_url);
                var loginRequest = new RestRequest("/api/login", Method.POST);
                loginRequest.AddParameter("username", "gsw");
                loginRequest.AddParameter("password", "111111");
                //或用用户名密码查询对应角色
                loginRequest.AddParameter("role", "system");
                IRestResponse loginResponse = loginClient.Execute(loginRequest);
                var loginContent = loginResponse.Content;
                Console.WriteLine(loginContent);
                return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);
            }
            static dynamic AdminLogin()
            {
                var loginClient = new RestClient(_url);
                var loginRequest = new RestRequest("/api/login", Method.POST);
                loginRequest.AddParameter("username", "gsw");
                loginRequest.AddParameter("password", "111111");
                //或用用户名密码查询对应角色
                loginRequest.AddParameter("role", "admin");
                IRestResponse loginResponse = loginClient.Execute(loginRequest);
                var loginContent = loginResponse.Content;
                Console.WriteLine(loginContent);
                return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);
            }
            static void AdminInvock(dynamic token)
            {
                var client = new RestClient(_url);
                //这里要在获取的令牌字符串前加Bearer
                string tk = "Bearer " + Convert.ToString(token?.access_token);
                client.AddDefaultHeader("Authorization", tk);
                var request = new RestRequest("/api/values", Method.GET);
                IRestResponse response = client.Execute(request);
                var content = response.Content;
                Console.WriteLine($"状态:{response.StatusCode}  返回结果:{content}");
            }
        }
    

      

    运行结果:

    源码:https://github.com/axzxs2001/AuthorizePolicy.JWT

  • 相关阅读:
    排序——字符串怀疑人生
    广搜的变形+最短路思想 变色龙
    阿斯顿发发顺丰
    莫队暴力 一知半解
    P3384 【模板】树链剖分
    U74201 旅行计划 树上找链长度
    数据结构:线性表基本操作和简单程序
    数据结构:循环链表实现约瑟夫环
    Codeforces 215D. Hot Days(贪心)
    Codeforces 1080C- Masha and two friends
  • 原文地址:https://www.cnblogs.com/axzxs2001/p/7530929.html
Copyright © 2020-2023  润新知