# 十二、安全授权

## 关于授权

授权是指确定用户可执行的操作的过程。 例如，允许管理用户创建文档库、添加文档、编辑文档和删除文档。 使用库的非管理用户仅获得读取文档的权限。

## 关于身份验证

身份验证是确定用户身份的过程。 授权是确定用户是否有权访问资源的过程。

授权与身份验证相互独立。 但是，授权需要一种身份验证机制。 身份验证是认定用户的过程。 身份验证可为当前用户创建一个或多个标识。

## 授权几种方式

不同的应用系统中，授权方式也有不同的方式，但无外乎以下几种：

* 简单授权，对每一个资源进行一对一授权配置
* 基于角色授权，资源和角色绑定，角色和用户绑定。
* 基于策略授权，通过配置策略对资源进行控制
* 基于 JWT Token 授权方式，目前主流授权方式
* **Hoa Framework 内置强大灵活的授权方式**

## 简单授权

asp.net core 中的AuthorizeAttribute授权通过属性及其各种参数进行控制。

&#x20;最简单的情况是， AuthorizeAttribute将属性应用于控制器或操作会将控制器或操作的访问权限限制为任何经过身份验证的用户。&#x20;

例如，以下代码将访问权限限制为任何AccountController经过身份验证的用户。

```csharp
[Authorize]
public class AccountController : Controller
{
    public ActionResult Login()
    {
    }

    public ActionResult Logout()
    {
    }
}
```

如果要对操作（而不是控制器）应用授权，请将AuthorizeAttribute属性应用于操作本身：

```csharp
public class AccountController : Controller
{
   public ActionResult Login()
   {
   }

   [Authorize]
   public ActionResult Logout()
   {
   }
}
```

现在只有经过身份验证的用户`Logout`可以访问该函数。

你还可以使用`AllowAnonymous`属性，以允许未通过身份验证的用户访问各个操作。 例如：

```csharp
[Authorize]
public class AccountController : Controller
{
    [AllowAnonymous]
    public ActionResult Login()
    {
    }

    public ActionResult Logout()
    {
    }
}
```

这将仅允许经过身份验证的AccountController用户访问，但Login操作除外，无论用户是否经过身份验证或未经身份验证/匿名状态，都可以访问该操作。

{% hint style="info" %}
`[AllowAnonymous]` 跳过所有授权语句。 如果组合 `[AllowAnonymous]` 和任何 `[Authorize]` 属性，则 `[Authorize]` 忽略属性。 例如，如果在控制器`[AllowAnonymous]`级别应用，则会忽略 `[Authorize]` 同一控制器（或其中的任何操作）上的任何属性。
{% endhint %}

## 基于角色授权

角色授权目前是应用系统中最为常用的一种方式，将资源和角色绑定到一起，然后为用户分配特定的角色，这样用户就可以访问他所拥有的角色的资源权限了。

RBAC（Role-Based Access Control，基于角色的访问控制），就是用户通过角色与权限进行关联。简单地说，一个用户拥有若干角色，每一个角色拥有若干权限。这样，就构造成“用户-角色-权限”的授权模型。

> 在这种模型中，用户与角色之间，角色与权限之间，一般都是多对多的关系。

如图所示：

![](/files/-M88Z4Olgn6uFgEWbJbQ)

由于 角色授权属于 Hoa Framework 授权的一部分，这里就不单独设立主题说明。

## 基于策略授权

策略授权方式是asp.net core 内置的新授权方式，也是目前比较推荐的授权方式。

自定义策略授权提供了一种非常灵活的授权方式，支持RABC（角色授权）的通知，又能支持更加复杂的颗粒化授权方式。

Hoa Framework 中内置了 **多系统多应用 策略JWT Token 授权**方式，这里也不单独设立主题说明，想了解更多 策略授权方式[可查阅官方文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-3.1)。

## JWT 授权方式

### 什么是 JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准（(RFC 7519).该token被设计为紧凑且安全的，特别适用于分布式站点的单点登录（SSO）场景。

JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息，以便于从资源服务器获取资源，也可以增加一些额外的其它业务逻辑所必须的声明信息，该token也可直接被用于认证，也可被加密。

jwt是由3段信息构成的，将这三段信息用.链接到一起就成了jwt字符串。

> eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDY3Nzg5ODQsInVzZXJuYW1lIjoiamVzc2UifQ.IFLQaDTRkY4t2lP38-8DyJ0i6GYeFVI7DTNPBqBhD0g

### JWT 构成

第一部分称之为头部，第二部分称之为载荷，第三部分是签证。

#### :flag\_black: header&#x20;

头部承担两部分信息：

* 声明类型 `JWT`
* 加密算法 `HMAC` `SHA256`等

如完整的头部，将头部进行 `base64` 加密，构成了第一部分。

```javascript
{
    'typ': 'JWT',
    'alg': 'HS256'
}
```

#### :flag\_black: playload

载荷就是存放有效信息的地方，这个名字像是特指飞机上承载的货品，这些有效信息包含。包含三部分：

* 标准中注册的声明
* 公共的声明
* 私有的声明

**标准中注册的声明** (建议但不强制使用) ：

* `iss`：jwt签发者
* `sub`：jwt所面向的用户
* `aud`：接收jwt的一方
* `exp`：jwt的过期时间，这个过期时间必须要大于签发时间
* `nbf`：定义在什么时间之前，该jwt都是不可用的.
* `iat`：jwt的签发时间
* `jti`：jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击。

**公共的声明**：

公共的声明可以添加任何的信息，一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息，因为该部分在客户端可解密

**私有的声明**：

私有声明是提供者和消费者所共同定义的声明，一般不建议存放敏感信息，因为base64是对称解密的，意味着该部分信息可以归类为明文信息。

#### :flag\_black: signature

jwt的第三部分是一个签证信息，这个签证信息由三部分组成：

* `header` (base64处理)
* `payload` (base64处理)
* `secret`

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串， 然后通过header中声明的加密方式进行加盐secret组合加密，然后就构成了jwt的第三部分。

Hoa Framework 中内置了 **多系统多应用 策略JWT Token 授权**方式，这里也不单独设立主题说明，想了解更多 JWT Token[可查阅官方文档](https://jwt.io/)。

## 内置强大授权

Hoa Framework 内置了强大的授权方式 `HoaMultipleClassifyPolicy`，并**包含了上述说明的 4 种全部方式**，是系统开发安全授权的第一选择。

### HoaMultipleClassifyPolicy

`HoaMultipleClassifyPolicy` 授权方式是 Hoa Framework 内置的授权方式，支持 简单授权、策略授权、JWT Token 授权，以及RABC 授权方式。该授权方式极易拓展和使用，只需要简简单单在类或方法申明处贴特性即可。如果有特定验证需求，也非常容易实现。

`HoaMultipleClassifyPolicy` 默认支持 JWT Token 验证方式，同时在用户登录授权之时存储以下字段供用户访问资源时鉴定。

```javascript
{
  "applications": "Admin Tool",  // 系统名称，支持多个系统
  "unique_name": "6",  // 用户唯一 ID
  "nameid": "SNRC SOFT",  // 用户名
  "email": "admin@snrcsoft.com",  // 用户邮箱
  "usertype": "ADMIN",  // 用户类型
  "iat": 1590376770,  // 签发时间
  "nbf": 1590376770, // 在什么时间之前不可用
  "exp": 1590383970,  // 过期时间
  "iss": "Hoa.Server",  // 签发方
  "aud": "Hoa.Client"  // 接受方
}
```

**当用户访问某资源时，自动检查请求头中是否包含 `Authorization` 键值对，并对该值进行 `JWT Token` 有效性验证。同时解析 `JWT Token` 信息并存储在 `HttpContext` 中。**

### 使用说明

Hoa Framework 在用户登录后，会将用户一些信息配置到 `JWT Payload` 中并生成 `Token` 字符串返回到请求方。之后的每一次请求，都需要将该 `Token` 字符串放置在 `Authorization` 请求报文头中返回，格式为：

```javascript
{
    "headers":{
        "Authorization":"Bearer 你的Token字符串"
    }
}
```

### 示例一

支持在类或方法申明中贴 `[HoaMultipleClassifyAuthorize(系统名称1，系统名称2)]` 特性

```csharp
using Hoa.Dependencies;
using Hoa.ServiceController.Attributes;

namespace Hoa.Application.Test
{
    // 只允许 Token 中 applications 为 "ADMIN" 的才能访问
    [HoaMultipleClassifyAuthorize("ADMIN")]
    [HoaServiceController]
    public class TestAppService : ITestAppService, IAppServiceDependency
    {
        public string GetName([Required] string name)
        {
           // ...Other codes
        }

        public void Curd()
        {
           // ...Other codes
        }
    }
}
```

`[HoaMultipleClassifyAuthorize("ADMIN")]` 将该类中所有的方法加入授权控制，并只允许 `Token` 中 `applications` 为 `ADMIN` 访问。

### 示例二

支持多系统多用户访问同一接口

```csharp
using Hoa.Dependencies;
using Hoa.ServiceController.Attributes;

namespace Hoa.Application.Test
{
    // 只允许 Token 中 applications 为 "ADMIN"、"MEMBER"、"SALE" 都可以访问。
    [HoaMultipleClassifyAuthorize("ADMIN","MEMBER","SALE")]
    [HoaServiceController]
    public class TestAppService : ITestAppService, IAppServiceDependency
    {
        public string GetName([Required] string name)
        {
           // ...Other codes
        }

        public void Curd()
        {
           // ...Other codes
        }
    }
}
```

`[HoaMultipleClassifyAuthorize("ADMIN","MEMBER","SALE")]` 将该类中所有的方法加入授权控制，并允许 `Token` 中 `applications` 为 `ADMIN` 、`MEMBER` 和 `SALE` 访问。

### 示例三

重写授权方式，支持匿名访问

```csharp
using Hoa.Dependencies;
using Hoa.ServiceController.Attributes;

namespace Hoa.Application.Test
{
    // 只允许 Token 中 applications 为 "ADMIN" 的才能访问
    [HoaMultipleClassifyAuthorize("ADMIN")]
    [HoaServiceController]
    public class TestAppService : ITestAppService, IAppServiceDependency
    {
        public string GetName([Required] string name)
        {
           // ...Other codes
        }

        public void Curd()
        {
           // ...Other codes
        }
        
        [AllowAnonymous] // 允许匿名访问
        public void Ok()
        {
           // ...Other codes
        }
    }
}
```

将 `[AllowAnonymous]` 设置到类或方法中，该类所有方法或特别方法将支持匿名访问。

## 自定义授权

Hoa Framework 提供了基于 `HoaMultipleClassifyPolicy` 的授权方式，只需要创建自定义的类并继承 `HoaMultipleClassifyPolicy` 即可。如：

```csharp
using Hoa.Authorization.HoaMultipleClassifyPolicy;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.JsonWebTokens;
using System;

namespace Hoa.Web.Core.Security
{
    public class CustomSecurityAuthorizationHandler : HoaMultipleClassifyAuthorizationHandler
    {

        public CustomSecurityAuthorizationHandler()
        {   
        }
        
        // 自定义授权管道，验证成功返回 true，否则 false
        public override bool AuthorizationPipeline(
            AuthorizationHandlerContext context,     // 授权上下文对象
            JsonWebToken jsonWebTokens,     // JWT Token对象
            HoaMultipleClassifyRequirement requirement)    // 授权需求对象
        {
            // 读取 Token 信息
            var email = jsonWebTokens.GetPayloadValue<string>(JwtRegisteredClaimNames.Email);
            var userType = jsonWebTokens.GetPayloadValue<string>(nameof(UserType).ToLower());

            // 读取当前请求的资源（方法）信息
            var endpoint = (Endpoint)context.Resource;
            var securityItemAttributes = endpoint.Metadata.GetOrderedMetadata<SecurityItemAttribute>();
           
            // 你的代码.....
        }
    }
}

```

然后在 `Hoa.Web.Core.ServiceExtensions.HoaAuthenticationConfigureExtension.cs` 中**注册你的自定义授权**即可。

```csharp
using Hoa.Authorization.HoaMultipleClassifyPolicy;
using Hoa.Web.Core.Security;
using Microsoft.AspNetCore.Authorization;
using System;

namespace Hoa.Web.Core.ServiceExtensions
{
    public static class HoaAuthenticationConfigureExtension
    {
        public static IServiceCollection AddHoaAuthentication(this IServiceCollection services)
        {
            services.AddSingleton<IAuthorizationPolicyProvider, HoaMultipleClassifyPolicyProvider>();
            //services.AddSingleton<IAuthorizationHandler, HoaMultipleClassifyAuthorizationHandler>();
            // 注册你的授权
            services.AddSingleton<IAuthorizationHandler, CustomSecurityAuthorizationHandler>();

            return services;
        }
    }
}
```

## 全局授权配置

默认情况下，Hoa Framework 允许所有用户访问接口，包括匿名用户，如果需要启动全局授权访问，只需要修改 `Hoa.Web.Hoa` 根目录下的 `appsetting.json` 配置文件即可。

```javascript
{
  "AppConfigOptions": {
    "EnableGlobalAuthorizationFilter": true // 设置为true，默认false
  }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://monksoul.gitbook.io/hoa/anquanshouquan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
