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

 

如果你有任何问题,欢迎留下评论,我们一起探讨
posted @ 2022-03-26 12:57  醉马踏千秋  阅读(4086)  评论(2编辑  收藏  举报