# @lingxiteam/security &middot;  [![monthly downloads](https://img.shields.io/npm/dm/@lingxiteam/security)](https://www.npmjs.com/package/@lingxiteam/security) [![gitHub license](https://img.shields.io/badge/license-ISC-blue.svg)]() [![npm version](https://img.shields.io/npm/v/@lingxiteam/security.svg?style=flat)](https://www.npmjs.com/package/@lingxiteam/security)

🚀一个简单、易用、灵活的请求数据安全工具：防报文被篡改、对请求数据加解密。



## 环境支持

✅ web浏览器  [  ajax, fetch, axios, umi-request ]

✅ nodejs后端

✅ 微信小程序

✅ React Native

## 使用文档

### 安装

```shell
npm install @lingxiteam/security
```

or

```shell
yarn add @lingxiteam/security
```



### 使用

#### 简单使用

在web端：

```javascript
import { httpEncryption } from '@lingxiteam/security';

httpEncryption.start({ mode: '1.0' });
```



在小程序：

```javascript
const security = require('@lingxiteam/security').default;

security.httpEncryption.start({ mode: '1.0' });
```



#### 安全模式

| 模式值 | 模式           | 说明                                                         |
| ------ | -------------- | ------------------------------------------------------------ |
| 1.0    | 默认参数签名   | 对url地址、query参数、header参数、body参数、盐值等按照一定方式生成签名 |
| 1.1    | 时间限制性签名 | 签名内容增加时间因子，在一段时间内有效                       |
| 1.2    | 唯一限制性签名 | 生成uuid签名，仅可使用1次                                    |
| 2.0    | RSA参数加密    | 使用RSA非对称加密算法进行内容加密                            |
| 3.0    | AES参数加密    | 使用AES加密算法进行内容加密                                  |

> [!NOTE]
>
> 1. 请求内容和响应内容模式独立，即请求时使用2.0，响应可使用3.0，我们可以针对部分服务进行差异性处理，只需在响应头部返回 `Xa-type` 字段告知模式值即可。
>
> 2. 签名和加解密默认使用灵犀平台预置方式，如对接其他平台，可以通过参数配置完成[自定义](#自定义加解密)加解密处理。



#### 规则

##### mode优先级

最高：通过start方法或setConfig方法设置mode，security.httpEncryption.start({ mode: '1.0' });
其次：[后端控制](#后端控制)，当mode配置项未设置时，将读取后端的配置
缺省值 1.1

##### 全局忽略

有时候安全模式并不适用某些服务，如非灵犀平台服务、CORS请求，可以通过配置 `ignore` 规则来对符合规则的服务不触发请求和响应内容的处理。

```javascript
import { setConfig } from '@lingxiteam/security';

// 指定服务地址
setConfig({
  ignore: "api/test",
});

// 正则表达式
setConfig({
  ignore: /^https/, 
});

// 函数判断，返回true表示忽略
setConfig({
  ignore: (url, {headers, body, method}) => { /* const isIgnore = ... */ return isIgnore; }, 
});

// 多条件组合, 满足其中一个即忽略
setConfig({
  ignore: [/^https/, (url, {headers, body, method}) => { /* const isIgnore = ... */ return isIgnore; }], 
});

```



##### 单服务指定

在一些需求场景中，我们可以针对某一服务指定对应的安全模式，在请求头添加 `Xa-type` 并设置一个模式值即可，如果将该字段设置为‘false’，将不触发该请求和响应内容的处理。

注意📢：被 `ignore` 的服务无法进行指定



#### 签名加盐

在使用 cookie session的场景，往往安全要求sessionId需要设置为httpOnly，这样签名程序无法对该值进行签名，攻击者通过更改sessionId对服务发起攻击，如果接口存在疏忽没有对权限正确校验，将会出现越权操作。为最大限度进行防范，建议增加一个以用户信息(userId、userCode)作为盐。

```javascript
// web端
import { setConfig, lxEncrypt } from '@lingxiteam/security';
setConfig({ sign: { saltKey: 'userId' } }); // 自动从cookie上获取值

// 非web端或cookie不可使用
setConfig({ sign: { saltKey: 'userId', saltValue: '...' } });
```



#### 资源地址签名

当通过 `img、script、link` 等标签方式加载资源时，无法通过请求头进行添加签名，我们可以使用 `createHttpSignWithUrl` 方法为资源地址后添加签名参数

```javascript
import { createHttpSignWithUrl } from '@lingxiteam/security';

const imgSrc = createHttpSignWithUrl('/app/file/id/123');

```



#### 自定义加解密

通过 `setConfig` 方法进行自定义配置

```javascript
import { setConfig, lxEncrypt } from '@lingxiteam/security';

// 密钥配置均支持经过lxEncrypt方法二次加密内容
setConfig({
  rsa: {
    // RSA公钥
    publicKey: '...',
    // RSA私钥
    privKey: '...',
    // 自定义RSA加密，返回加密后内容
    encrypt: (conetnt, publicKey) => '...',
    // 自定义RSA解密，返回解密后内容
    decrypt: (conetnt, privKey) => '...',
  },
  
  aes: {
    // AES密钥
    key: '...',
    // 自定义AES加密，返回加密后内容
    encrypt: (conetnt, key) => '...',
    // 自定义AES解密，返回解密后内容
    decrypt: (conetnt, key) => '...',
  }

  
  // 支持密钥二次加密
  keyEncrypt: true,
});

```



#### 后端控制

在web端可以引入服务器上`app/env/info.js` ，并通过平台界面配置来动态控制安全模式相关配置。

在小程序上可以根据服务返回的参数进行设置：

```javascript
const security = require('@lingxiteam/security').default;

// 与平台对接，单点后拿到 refreshAccessToken、secretKey、secretValue、accessToken、lxServerTime、lxSecurityMode
// 启动安全签名
security.httpEncryption.start({
    mode: lxSecurityMode,
    sign: {
        saltKey: secretKey,
        saltValue: secretValue,
    },
    serverTime: lxServerTime,
});
```





#### 手动模式

当执行 `httpEncryption.start()` 后，程序进行将自动处理所有异步请求，执行 `httpEncryption.stop()` 将释放控制权。如果自动模式无法满足或出现与其他程序使用不兼容，可以通过使用原始的方法自行进行处理。

```javascript
import { createHttpSignStr, RSAEncrypt, RSADecrypt, AESEncrypt, AESDecrypt, DESEncrypt, DESDecrypt } from '@lingxiteam/security';

// 生成签名
const xSign = createHttpSignStr(url, { headers, body, method, saltValue }, signMode);

// 加密
const RSAEncryptContent = RSAEncrypt(content, key);
const AESEncryptContent = AESEncrypt(content, key);
const DESEncryptContent = DESEncrypt(content, key);

// 解密
const RSADecryptContent = RSADecrypt(content, key);
const AESDecryptContent = AESDecrypt(content, key);
const DESDecryptContent = DESDecrypt(content, key);
```



#### 其他

##### 空值body

当body为空时，可能出现前后端取值不一致问题。nodejs后端一般存在空字符串、空对象、undefined 等值，这取决于框架处理和使用的中间件；java后端一般为null。为避免前后端取值差异导致签名校验失败，可以通过配置项将前端空值进行转换与后端保持一致。缺省值为空字符串。

```javascript
import { setConfig } from '@lingxiteam/security';

setConfig({ emptyBodyValue: '{}' });
```

##### webpack5下使用

使用webpack5工程出现 Module not found: Error: Can't resolve 'crypto', 此时需要调整webpack配置项

```json
{
  "resolve": {
    "fallback": {
      "crypto": false
    }
  }
}
```



### 调试

设置debug模式，控制台将输出相关日志信息

```javascript
// 配置设置
import { setConfig } from '@lingxiteam/security';

setConfig({ debug: true });

// or 
localStorage.setItem('lxDebug', 'true');
```



