zoukankan      html  css  js  c++  java
  • .net Core jwt策略参数

    一、实现

    1、Permission文件

    代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace Blog.Jwt
    {
        /// <summary>
        /// 用户或角色或其他凭据实体
        /// </summary>
        public class Permission
        {
            /// <summary>
            /// 用户或角色或其他凭据名称
            /// </summary>
            public virtual string RoleName{ get; set; }
            /// <summary>
            /// 请求Url
            /// </summary>
            public virtual string Url{ get; set; }
        }
    }

    如图所示:

     2、PermissionHandler.cs

    代码如下:

    using Blog.Jwt;
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Security.Claims;
    using System.Threading.Tasks;
    
    
    namespace Blog.Jwt
    {
        /// <summary>
        /// 权限授权Handler
        /// </summary>
        public class PermissionHandler : AuthorizationHandler<PermissionRequirement>//我们自定义用户属性,并自己验证
        {
            /// <summary>
            /// 验证方案提供对象
            /// </summary>
            public IAuthenticationSchemeProvider Schemes { get; set; }
    
            private readonly IHttpContextAccessor _accessor;
    
            /// <summary>
            /// 构造函数注入
            /// </summary>
            /// <param name="schemes"></param>
            /// <param name="accessor"></param>
            public PermissionHandler(IAuthenticationSchemeProvider schemes, IHttpContextAccessor accessor)
            {
                Schemes = schemes;
                _accessor = accessor;
            }
            /// <summary>
            /// 摘要:根据特定需求决定是否允许授权 特定就是add参数,但是未包含在创建token属性内
            /// 
            /// 二点注意:
            /// 一、没必要重写加参数,我们可以从上下文中获取到token(老张完全多此一句) 我们完全可以从token获取用户信息并对比 requirement完全多余  
            /// 二、HandleRequirementAsync用了 这个方法 没有办法HttpContext.Response.WriteAsync,不然会报错报错{StatusCode cannot be set because the response has already started.}因为响应已经开始
            /// 所以我们尽量不在此方法里面写,我们只需要知道时间过期则请求头部 context.Response.Headers.Add("Token-Expired", "true"); 即可,其他一律为未授权
            /// 其实我很想 提示 未授权和认证上失败的 区分下
            /// 
            /// 第二次回顾
            /// 1、具体提示信息既然无法返回 body内容,就返回状态码(这个是可行的),有个全局拦截来根据返回自定义状态码处理,状态码状态信息是对外一致(也是全局拦截的统一状态码信息的好处)
            /// 2、我之前认为说为什么add参数呢,甚至你不用系统提供的auth,直接写个中间件一样能实现。微软这么设计,是为了更复杂的抽象层而已。
            /// 
            /// 我还是选择不继承 AuthorizationHandler  ,不添加add 参数,直接用
            /// 
            /// reqirement 用来定义授权认证的参数  拿他来验证的 
            /// 
            /// </summary>
            /// <param name="context"></param>
            /// <param name="requirement"></param>
            /// <returns></returns>
            protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
            {
    
                #region 想返回具体提示
                /*
                if (!_accessor.HttpContext.Response.HasStarted)//先判断context.Response.HasStarted
                {
                    var results = JsonConvert.SerializeObject(new ApiResultModels { Success = false, Message = "测试返回结果", Code = "406" });
                    await _accessor.HttpContext.Response.WriteAsync(results);
                    //写入后报错{StatusCode cannot be set because the response has already started.}因为响应已经开始
    
                    if (true)
                    {
                        context.Fail();
                        return CompletedTask;
                    }
                }  //想返回想要的返回具体结果比如 过期时间提示token已过期、Audience颁布者则提示token无效!
                只能通过设置状态码解决
                _accessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;//403
                context.Fail();
                if (true)
               */
                #endregion
    
                ////赋值用户权限       
    
                //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
                //var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;
                var httpContext = _accessor.HttpContext;
    
                /*//动态获取权限项目
                if (!requirement.Permissions.Any())
                {
                    var data = await _role.GetPermissions();
                    var list = (from item in data
                                where item.IsDeleted = false
                                orderby item.Id
                                select new Permission
                                {
                                    Url = "",
                                    Name = ""
                                }).ToList();
                    requirement.Permissions = list;
                }*/
    
                //请求Url
                var questUrl = httpContext.Request.Path.Value.ToLower();
    
                #region 判断请求是否停止
                //判断请求是否停止
                var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();// IAuthenticationSchemeProvider 用来提供对Scheme的注册和查询
                foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())//按请求处理的优先级顺序返回方案(Scheme)。
                {
                    //来获取指定的Scheme的Hander
                    var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;
                    if (handler != null && await handler.HandleRequestAsync()) //handler.HandleRequestAsync 如果请求处理应停止,则返回true
                    {
                        context.Fail();
                        return;
                    }
                }
                //后台 怎么根据 HttpContext判断请求是否停止?(保留疑问)
                #endregion 
    
                //判断请求是否拥有凭据,即有没有登录  GetDefaultAuthenticateSchemeAsync 是获取默认的授权方案信息
                var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();//public IAuthenticationSchemeProvider Schemes 验证方案提供对象 1、获取认证方案实例对象不为null
                if (defaultAuthenticate == null)
                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
                else
                {
                    //扩展方法 public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) 2、context上下文通过身份验证方案的名称,获取认证方案信息
                    //判断使用是否授权
                    var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);//身份认证 defaultAuthenticate.Name==Bearer(header里面有Bearer)
                    //result?.Principal不为空即登录成功
                    if (result?.Principal != null)
                    {
                        httpContext.User = result.Principal;//3、token字符串
    
                        var strToken = result.Properties.Items.FirstOrDefault().Value;//token字符串
    
                        //权限中是否存在请求的url
                        if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key?.ToLower() == questUrl).Count() > 0)
                        {
                            // 获取当前用户的角色信息
                            var currentUserRoles = (from item in httpContext.User.Claims
                                                    where item.Type == requirement.ClaimType
                                                    select item.Value).ToList();
                            //验证权限 失败则
                            if (currentUserRoles.Count <= 0 || requirement.Permissions.Where(w => currentUserRoles.Contains(w.RoleName) && w.Url.ToLower() == questUrl).Count() <= 0)
                            {
                                // 可以在这里设置跳转页面,不过还是会访问当前接口地址的
                                //httpContext.Response.Redirect(requirement.DeniedAction);
                                _accessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;//401 未授权
                                context.Fail();
                                //if (true)
                                return;
                            }
                        }
                        else
                        {
                            //context.Fail();
                            //return;
    
                            _accessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;//404 此url不存在
                            context.Fail();
                            //if (true)
                            return;
                        }
                        //判断过期时间
                        if ((httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) != null && DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.UtcNow)
                        {
                            context.Succeed(requirement);
                        }
                        else
                        {
                            context.Fail();
                            return;
                        }
                        return;
                    }
                    else
                    {
                        _accessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;//403 禁止访问
                        context.Fail();
                        //if (true)
                        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);
            }
        }
    }

    3、PermissionRequirement.cs如下

    代码如下:

    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace Blog.Jwt
    {
        /// <summary>
        /// https://blog.csdn.net/qq_25086397/article/details/103765090
        /// 必要参数类 继承 IAuthorizationRequirement 方便从PermissionRequirement将属性取出赋值道PermissionRequirement类成员上
        /// </summary>
        public class PermissionRequirement : IAuthorizationRequirement
        {
            /// <summary>
            /// 用户权限集合
            /// </summary>
            public List<Permission> Permissions { get; private set; }
            /// <summary>
            /// 无权限action
            /// </summary>
            public string DeniedAction { get; set; }
            /// <summary>
            /// 认证授权类型
            /// </summary>
            public string ClaimType { internal get; set; }
            /// <summary>
            /// 请求路径
            /// </summary>
            public string LoginPath { get; set; } = "/Api/Login";
            /// <summary>
            /// 发行人
            /// </summary>
            public string Issuer { get; set; }
            /// <summary>
            /// 订阅人
            /// </summary>
            public string Audience { get; set; }
            /// <summary>
            /// 过期时间
            /// </summary>
            //public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5000);
            public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(1);
            /// <summary>
            /// 签名验证
            /// </summary>
            public SigningCredentials SigningCredentials { get; set; }
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="deniedAction">拒约请求的url</param>
            /// <param name="permissions">权限集合</param>
            /// <param name="claimType">声明类型</param>
            /// <param name="issuer">发行人</param>
            /// <param name="audience">订阅人</param>
            /// <param name="signingCredentials">签名验证实体</param>
            public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials)
            {
                ClaimType = claimType;
                DeniedAction = deniedAction;
                Permissions = permissions;
                Issuer = issuer;
                Audience = audience;
                SigningCredentials = signingCredentials;
    
                //DI容器,注册到容器内,此时无New实例化,仅构造函数用到的时候才会new,内存才会有该实例对象
                /*//没有权限则跳转到这个路由
                DeniedAction = new PathString("/api/nopermission");
                //用户有权限访问的路由配置,当然可以从数据库获取
                Permissions = new List<Permission> {
                                  new Permission {  Url="/api/value3", Name="admin"},
                              };*/
            }
        }
    }

    JwtMiddlewareExtensions.cs

    using Common;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.IdentityModel.Tokens;
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Blog.Jwt
    {
        //1、自定义授权策略验证 2、颁发者token和刷新token验证 
        public static class JwtMiddlewareExtensions
        {
            public static IServiceCollection AddJwtMiddleware(this IServiceCollection services, IConfiguration Configuration)
            {
                #region  注册Jwt验证
                //在ConfigureServices中注入验证(Authentication),授权(Authorization),和JWT(JwtBearer)
                var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]));
                //↓Token 的信息配置
                var tokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,////是否验证SecurityKey
                    IssuerSigningKey = signingKey,//拿到SecurityKey
                    ValidateIssuer = true,//是否验证Issuer
                    ValidIssuer = Configuration["Jwt:Issuer"],//Issuer,这两项和前面签发jwt的设置一致
                    ValidateAudience = false,//是否验证Audience  //为了验证token和刷新token两套Audience,后续处理
                    ValidAudience = Configuration["Jwt:Audience"],//Audience,这两项和前面签发jwt的设置一致
                    ValidateLifetime = true,//是否验证超时  当设置exp和nbf时有效 同时启用ClockSkew 
                    ClockSkew = TimeSpan.Zero
                    //这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了  //https://www.cnblogs.com/7tiny/p/11019698.html
                    /*,AudienceValidator = (m, n, z) =>
                    {
                          return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
                     },*/
                };
                //
                var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
                //这个集合模拟用户权限表,可从数据库中查询出来 // 如果要数据库动态绑定,这里先留个空,后边处理器里动态赋值
                var permission = new List<Permission> {
                              new Permission {  Url="/", RoleName="Admin"},
                              new Permission {  Url="/api/home/values", RoleName="Admin"},
                              new Permission {  Url="/", RoleName="SysTem"},
                              new Permission {  Url="/api/home/values1", RoleName="Admin"},
                              new Permission {  Url="/api/home/values2", RoleName="Admin"},
                              new Permission {  Url="/api/home/values1", RoleName="SysTem"},
                              new Permission {  Url="/api/home/values2", RoleName="SysTem"}
                          };
                //var permission = new List<Permission>();
                //New一个PermissionRequirement实体类,是为了从扩展额外参数,先注入,然后在PermissionHandler验证使用这些参数,这个参数我们自己定义的,方便验证的时候使用
                //如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名
                var permissionRequirement = new PermissionRequirement("/api/denied", permission, ClaimTypes.Role, Configuration["Jwt:Issuer"], Configuration["Jwt:Audience"], signingCredentials);
                services.AddAuthorization(options => //↓导入角色身份授权策略
                {
                    options.AddPolicy("Permission", policy => policy.Requirements.Add(permissionRequirement));
    
                }).AddAuthentication(options => //↓身份认证类型
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(o =>//↓Jwt 认证配置
                {
                    //不使用https
                    o.RequireHttpsMetadata = false;
                    o.TokenValidationParameters = tokenValidationParameters;
                    o.Events = new JwtBearerEvents
                    {
                        //此处为权限验证失败后触发的事件
                        OnChallenge = context =>
                        {
                            //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦,必须
                            context.HandleResponse();
                            //自定义自己想要返回的数据结果,我这里要返回的是Json对象,通过引用Newtonsoft.Json库进行转换
                            var result = new ApiResultModels();
                            result.Code = false;
                            result.Message = "很抱歉,您无权访问该接口!";
                            //自定义返回的数据类型
                            context.Response.ContentType = "application/json";
                            //自定义返回状态码,默认为401 我这里改成 200
                            context.Response.StatusCode = StatusCodes.Status200OK;
                            //context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                            //输出Json数据结果
                            context.Response.WriteAsync(JsonConvert.SerializeObject(result));
                            return Task.FromResult(0);
                        }
                        ,OnAuthenticationFailed = context =>
                        {
                            //如果过期,则把<是否过期>添加到,返回头信息中
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }
                            return Task.CompletedTask;
                        }
    
                    };
                });
                //注入授权Handler
                services.AddScoped<IAuthorizationHandler, PermissionHandler>();
                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
                services.AddSingleton(permissionRequirement);
    
                #endregion
                return services;
            }
        }
    
    
    }

    方便在Startup.cs使用

    二、 只验证 角色的

     PermissionRequirement requiremen 相当于new 一个实体吧(虽然以前是构造方法的写法)

    从数据库查出权限列表给requiremen,然后在去比较

     角色都不需要

  • 相关阅读:
    [循环卷积]总结
    [FFT/NTT/MTT]总结
    [BZOJ 4870] 组合数问题
    [BZOJ 4809] 相逢是问候
    [BZOJ 4591] 超能粒子炮-改
    __getattribute__
    __repr__
    __reduce__
    数据库查询转excel小工具
    Git常用操作
  • 原文地址:https://www.cnblogs.com/fger/p/12202362.html
Copyright © 2011-2022 走看看