• Asp.NET Core 中的 认证授权 详解和使用


    阅读要求

    • 你需要对 netcore 有一些了解
    • 有对 JWT 有一定的了解

    我强烈建议你阅读:验证(官方)   和  授权(官方)    的解读

    什么是 验证 和 授权?

    身份验证(authentication):是确定用户身份的过程

    授权(authorization ):是确定用户(已经验证成功的用户)是否有权访问资源的过程。

    身份验证

    职责:

    • 对用户进行身份验证。
    • 在未经身份验证的用户试图访问受限资源时作出响应。

    现在,我们对一个 action 方法上添加 authorize 特性,这表明我们对这个接口进行了授权: 

    [HttpGet]
    [Authorize]
    public IEnumerable Get(){
        return new string[] {"数据1", "数据2"};
    } 

    如果我们直接访问这个接口,会报如下错误:

    意思是:你定义了授权,但没有指定任何(包括自定义和官方的) 身份验证方案;

    授权Authorization 和 认证Authentication 是相辅相成的;两者缺一不可。

    解决的方法,其实报错信息已经告诉你了;即:添加认证方案的支持,其实,认证方案有很多,但是现在主要推荐的还是 Jwt Bearer 身份验证方案:

    1、Nuget 中安装 Microsoft.AspNetCore.Authentication.JwtBearer 包;

    2、然后再 ConfigurationServices 中添加对 身份验证的方案(包括使用什么方案,这个方案需要做什么样子的配置) 做注入容器中处理:

    SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("ixkeE8eu2345k4zs"));    // 注意:这里的key不能低于16位
    services.AddAuthentication("Bearer")        // 注入认证服务,认证类型:Bearer
        .AddJwtBearer(o =>        // 注入 Jwt Bearer认证 服务,对其进行配置
        {   // 对 jwt 进行配置
            o.TokenValidationParameters = new TokenValidationParameters()        // 对Token的认证是哪些参数,这里设置
            {
                // 这里的参数遵循 3(必要) + 2(可选) 个参数的规则
                // 1、是否开启秘钥认证,验证秘钥
                ValidateIssuerSigningKey = true,        // 验证发行者签名秘钥
                IssuerSigningKey = securityKey,        // 发行者签名秘钥是?
    
                // 2、验证发行人
                ValidateIssuer = true,        // 验证发行者
                ValidIssuer = "issuer",        // 验证发行者的名称是?
    
                // 3、验证订阅人
                ValidateAudience = true,        // 是否验证订阅者
                ValidAudience = "audience",        // 验证订阅者的名称是?
    
                // 1+1
                // 过期时间 和 生命周期
                RequireExpirationTime = true,        // 使用过期时间
                ValidateLifetime = true,        // 验证生命周期
            };
        });

    提醒:根据报错信息,他有两种写法,下面是第二种:

    services.AddAuthentication(x => {
        // 认证方案:Bearer
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;   // 即:Bearer
        // 默认Challeng质询方案:
      Bearer x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(o =>{ 
        // 内容和上面一样
        ...
    });

     配置好后,我们再次访问接口,发现这回不报错了,但是返回的状态码时 401;

    401 Unauthorized:未经授权,身份认证不通过,未认证,可能:无令牌,令牌无效、失效(因为你没有使用有效的token,无法通过 身份认证 Authentication)

    403 Forbidden:被禁止,即:令牌通过,但是你无权限

    说明,我们验证已经起作用了,但因为我们没有传递 jwt token 信息(即没有权限),对于这个接口的访问作出了拒绝响应;

    接下来,我们新建一个 action 方法,作用是 创建 有效的 token 令牌,然后用这令牌访问需要授权的api:

    // Jwt Token 的生成
    [HttpGet]
    public string GetToekn() { // 注意,必须和上面的 JwtBearer 配置一致,且密钥最少16位,太少会报错! SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("ixkeE8eu2345k4zs")); // 同样,我们在上面的 JwtBearer 配置中,需要验证的是什么,这里也要生成对应的条件,缺了就会导致认证失败,假如这里发行人改成其他,验证那边就不通过 SecurityToken securityToken = new JwtSecurityToken( // 和上面一样,同样遵循 3+2 规则 issuer: "issuer", // 发行人 audience: "audience", // 订阅人 signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256), // 安全密钥 和 加密算法 expires: DateTime.Now.AddHours(1), // 过期时间 claims: new Claim[] { } // 添加 Claim(声明主体),添加uid、username、role等都放在这里 ); return new JwtSecurityTokenHandler().WriteToken(securityToken); // 返回 Token字符串 }

     最后,我们还需要添加 身份验证 UseAuthentication 中间件,授权 Authorization 中间件,要不然http请求管道中没有处理 身份认证 了:

    app.UseHttpsRedirection();
    
    // 注意下面 Routing Authentication Authorization 这三个中间件的放置顺序,必须按照这个顺序:
    
    app.UseRouting();
    // 添加 身份验证 中间件(注意顺序,中间件这里是:先身份验证再授权)、
    // 而且 身份验证 和 授权 都要放在Routing 之后
    app.UseAuthentication();        // 添加 身份认证中间件
    // 添加 授权中间件
    app.UseAuthorization();
    
    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

    运行项目,先获取 token:

     使用 这个token访问需要授权的api:

     或者你也可以这么访问:

    至此,验证 和 授权 已经讲完了,你需要好好思考下上面的流程;

    上面内容中,大多介绍了身份验证,如何使用,而接下去,我们将着重介绍 授权;

    授权

    上面例子中,我么用 authorize 这个特性作用于一个 action 方法上了,这就是授权,对这个action进行了权限限制;

    但是,这种简单的授权,是只要有效的token(即:身份验证通过),就能访问这个接口,而没有精细化处理(就好比:董事长有这权限、行政也有着权限、而员工没有这权限);

    asp.net core 的授权分三种

    1、普通的授权(上面已经讲了)

    2、基于角色的授权

    3、基于策略的授权

    基于角色的授权:

    [HttpGet]
    [Authorize(Roles="Vip")]
    public string Get(){
        ...
    }

    之前,我们在编写获取 Jwt Token 时,定义的 Claim 是空数组,现在,我们加点东西下去:

    ...
    expires: DateTime.Now.AddHours(1),
    claims: new Claim[] { 
        new Claim("kuozhan", "kuozhanneirong"),        // 可以用我们自己定义的 name 
        new Claim(ClaimTypes.Role, "Vip"),    // 也可以用内置的name,如 Role,这里就是我们的授权,给谁
        new Claim(ClaimTypes.Email, "123@qq.com")
    }        // 添加 Claim(声明主体),添加uid、username、role等都放在这里
    );
    ...

    ClaimTyps.Role的参数对应的就是 action 上面特性的Role参数;

    我们通过这种方式生成的 Token 就可以访问对 Roles=Vip 的action的权限访问了;

    基于策略

    假如,一个action不止 Vip 这个权限,还有五六个呢?如何把他们合并在一起?这就使用到了 基于策略的 授权机制了:

    [HttpGet]
    [Authorize(Policy="AdminAndUser")]
    public IEnumerate Get(){
        return new string[] {"数据1", "数据2"};l
    }

    然后我们注入服务:

    // 策略注入服务:
    services.AddAuthorization( o => {
        o.AddPolicy("AdminOrUser", o=>{
            o.RequireRole("Admin", "User").Build();
        });
    });

    这样,A用户有Admin、B用户有User,它们两都能访问整个接口;

    如果要求,用户必须有Admin 和 User 才能访问,那么可以:

    // 策略注入服务:
    services.AddAuthorization( o => {
        o.AddPolicy("AdminOrUser", o=>{
            o.RequireRole("Admin").RequireRole("User").Build();
        });
    });

    自定义策略的授权

    // 新建一个类:  /PolicyRequirement/MustRoleAdminHandle.cs
    using Microsoft.AspNetCore.Authorization;
    using System.Linq;
    using System.Threading.Tasks;
    // 自定义策略授权,继承至 IAuthorizationHandler 且实现里面的 HandleAsync 方法 即可 public class MustRoleAdminHandle : IAuthorizationHandler { public Task HandleAsync(AuthorizationHandlerContext context) { // 做验证判断,如果验证通过,则 context.Successed(); // 可以看形参 context 里的各种属性 // 比如: //var requirement = context.Requirements.FirstOrDefault(); //context.Succeed(requirement); // 这样设置后就返回 200 // 或者设置 Fail() // context.Fail(); return Task.CompletedTask; // 直接这么返回,会返回 403 } }
    // 服务注入(注意这个): using Microsoft.AspNetCore.Authorization; services.AddSingleton<IAuthorizationHandler, MustRoleAdminHandle>();

    思考:context.Requirements 里面的值是什么? 是对应 api接口上策略名里面的所有Role集合,即:
    action上定义的是 UserAndAdmin时,其策略注册是 o.RequireRole("Admin", "User") 那么,集合就是: Admin和User;

    基于 Claim声明 的授权

    services.AddAuthorization(o=>{
        o.AddPolicy("AdminClaim2", 0 => {
            o.RequireClaim("Email", "123@qq.com", "456@qq.com");
            // JWT Token里面的 Claim 中设置了这些,那么就会通过 AdminClaim2
        });
    });

    基于Requirement需要,大多数开发都是用这种方式:

    [HttpGet]
    [Authorize(Policy="AdminRequireMent")]
    public string Get(){ return string.Empty();}
    
    // 服务注入
    services.AddAuthorization(o=>{
        o.AddPolicy("AdminRequireMent", o => {
            var myAdminRequirement =  new AdminRequirement(myName =  "zhangsanfeng");    // 可以传递参数
            o.Requirements.Add(myAdminRequirement);        // 完全自定义
        });
    });
    
    // 需要新建一个 /PolicyRequirement/AdminRequirement.cs 并继承至 IAuthorizationRequirement
    using using Microsoft.AspNetCore.Authorization;
    public class AdminRequirement: IAuthorizationRequirement{
        public string myName { get; set; };
    }

     这样,我们重启再次访问有授权的Get接口时,会先进入这里:

    public class MustRoleAdminHandle : IAuthorizationHandler
    {
        public Task HandleAsync(AuthorizationHandlerContext context)
        {
             context.Requirements  // 该参数返回的就是 myAdminRequirement 实例
            // 这样,我们就可以通过这个自定义判断
            // 非常的灵活
        }
    }

    但是,上面代码中,IAuthorizationHandler 接口并不是很灵活,微软又抽象了一个 抽象类 AuthorizationHandler ,这样,我们就更加容易去使用了:

    public class MustRoleAdminHandle:AuthorizationHandler<AdminRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement)
        {
            // context.Succeed(requirement);
            return Task.CompletedTask;        // 直接这么返回,会返回 403
        }
    }

    JwtBearer认证中,默认是通过http的Authorization头来获取的,也是推荐这种做法,但是某些场景下需要通过url或者cookie中来传递Token,如何实现呢?

    从url中获取的,可以:

    SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("abcdefghijklnmopq"));    // 为key不能低于16位
    services.AddAuthentication("Bearer")        // 注入认证服务,认证类型:Bearer
        .AddJwtBearer(o =>        // 注入 Jwt Bearer认证 服务,对其进行配置
        {
            // 主要是这里:
            o.Events = new JwtBearerEvents(){
                OnMessageREceived = context => {
                    context.Token  = context.Request.Query["access_token"];
                    return Task.CompletedTask; 
                }
            };
    
            o.TokenValidationParameters = new TokenValidationParameters{
                //...
            }
        });

     除了OnMessageReceived外,还提供了如下几个事件:

    1. TokenValidated:在Token验证通过后调用。
    2. AuthenticationFailed: 认证失败时调用。
    3. Challenge: 未授权时调用。

    使用OIDC服务(即:OpenId Connect,身份认证(核心部分))

    简单来说:OIDC是OpenID Connect的简称,OIDC=(Identity, Authentication) + OAuth 2.0。它在OAuth2上构建了一个身份层,是一个基于OAuth2协议的身份认证标准协议。

    在上面的示例中,我们简单模拟的Token颁发,功能非常简单,但是这并不适合在生产环境中使用,可是微软也没有提供OIDC服务的实现,好在.NET社区中提供了几种实现,可供我们选择:

    AspNet.Security.OpenIdConnect.Server (ASOS)、IdentityServer4、OpenIddict 和 PwdLess

    我们在这里使用IdentityServer4来搭建一个OIDC服务器,具体代码会给大家带来混淆,所以这里忽略了。

    至此,验证、授权 这部分已经讲完了;
     
     
    在 Swagger 中启用 Jwt Bearer
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo
        {
            Title = "xxxProjectNet5.Api",
            Version = "v1"
        });
    
    
        // =======================================================按照下面的格式即可
    
        // 开启小锁
        c.OperationFilter<AddResponseHeadersFilter>();
        c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
    
        // 在 header 中添加token,传递到后台
        c.OperationFilter<SecurityRequirementsOperationFilter>();
        c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme()
        {
            Description = "JWT 授权(数据将在请求头中进行传输)直接在下框中输入 Bearer(token)(注意两者之间是一个空格)",
            Name = "Authorization",    // jwt 默认的参数名称
            In = ParameterLocation.Header,  // jwt 将默认存放 Authorization 信息的位置(请求头中)
            Type = SecuritySchemeType.ApiKey  // 除了ApiKey 外,还有 Http、Oauth2、OpenIdConnect
        });
    });
    如果你有任何问题,欢迎留下评论,我们一起探讨
  • 相关阅读:
    Azure HPC Pack Cluster添加辅助节点
    Azure HPC Pack 辅助节点模板配置
    Azure HPC Pack配置管理系列(PART6)
    Windows HPC Pack 2012 R2配置
    Azure HPC Pack 节点提升成域控制器
    Azure HPC Pack VM 节点创建和配置
    Azure HPC Pack 部署必要条件准备
    Azure HPC Pack 基础拓扑概述
    Azure VM 性能计数器配置
    Maven私仓配置
  • 原文地址:https://www.cnblogs.com/abc1069/p/16058146.html
Copyright © 2020-2023  润新知