『你的API接口安全么』之给NetCore接口返回值加密!

2023-11-13 20:40:07 浏览数 (2)

(晚来天欲雪 _ _ _ _ _ _)

大家好,天气骤降,你那里下雪了么。

1、故事背景

书接上文,上篇说到了我们可以通过前端把请求参数进行加密,然后传给后端,后端统一通过中间件或者过滤器进行解密,将参数回填到请求里,这样就能无感的将接口参数进行安全保护了,同时又不改变接口的写法。

接口参数加密其实不是很重要(当然除了登录接口),那最重要的还是返回值内容加密:

2、整体设计思路

首先,我们还是需要定义一种加密方式,也同时需要和前端商量好,肯定是需要前端也能解密的,要不然不能页面渲染,前端可以在axios的返回值拦截器里解密。这里还是用Base64来演示,大家下载BlogCore最新代码就能看到两个中间件,自己根据公司情况进行修改即可。

其次,我们还是用这个接口举例子:

http://localhost:9291/api/Login/GetJwtTokenSecret?name=blogadmin&pass=blogadmin

这是一个很简单的接口,有两个参数,分别是用户名和密码。

然后模拟登录,请求会返回token:

如果这里是用户信息,或者是其他敏感信息,直接暴露到公网是不安全的,当然你可能会说用https会安全,这也不尽然,毕竟再加密一下肯定会更安全的嘛。

第三,就是我们的重中之重,返回内容加密中间件。

我们需要统一的对接口返回值进行加密,然后将输出到前端,所以聪明的你肯定知道如何处理,而且也要放到中间件管道外层,代码是这样,当然后期会微调,大家还是看BlogCore最新更新就行了。

代码语言:javascript复制
public async Task InvokeAsync(HttpContext context)
{
    // 配置开关,过滤接口
    if (AppSettings.app("Middleware", "EncryptionResponse", "Enabled").ObjToBool())
    {
        var isAllApis = AppSettings.app("Middleware", "EncryptionResponse", "AllApis").ObjToBool();
        var needEnApis = AppSettings.app<string>("Middleware", "EncryptionResponse", "LimitApis");
        var path = context.Request.Path.Value.ToLower();
        if (isAllApis || (path.Length > 5 && needEnApis.Any(d => d.ToLower().Contains(path))))
        {
            Console.WriteLine($"{isAllApis} -- {path}");
            var responseCxt = context.Response;
            var originalBodyStream = responseCxt.Body;

            // 创建一个新的内存流用于存储加密后的数据
            using var encryptedBodyStream = new MemoryStream();
            // 用新的内存流替换 responseCxt.Body
            responseCxt.Body = encryptedBodyStream;

            // 执行下一个中间件请求管道
            await _next(context);

            //encryptedBodyStream.Seek(0, SeekOrigin.Begin);
            //encryptedBodyStream.Position = 0;

            // 可以去掉某些流接口
            if (!context.Response.ContentType.ToLower().Contains("application/json"))
                    {
                        Console.WriteLine($"非json返回格式 {context.Response.ContentType}");
                        //await encryptedBodyStream.CopyToAsync(originalBodyStream);
                        context.Response.Body = originalBodyStream;
                        return;
                    }

            // 读取加密后的数据
            //var encryptedBody = await new StreamReader(encryptedBodyStream).ReadToEndAsync();
            var encryptedBody = responseCxt.GetResponseBody();

            if (encryptedBody.IsNotEmptyOrNull())
            {
                dynamic jsonObject = JsonConvert.DeserializeObject(encryptedBody);
                string statusCont = jsonObject.status;
                var status = statusCont.ObjToInt();
                string msg = jsonObject.msg;
                string successCont = jsonObject.success;
                var success = successCont.ObjToBool();
                dynamic responseCnt = success ? jsonObject.response : "";
                string s = "1";
                // 这里换成自己的任意加密方式
                var response = responseCnt.ToString() != "" ? Convert.ToBase64String(Encoding.UTF8.GetBytes(responseCnt.ToString())) : "";
                string resJson = JsonConvert.SerializeObject(new { response, msg, status, s, success });

                context.Response.Clear();
                responseCxt.ContentType = "application/json";

                //await using var streamlriter = new StreamWriter(originalBodyStream, leaveOpen: true);
                //await streamlriter.WriteAsync(resJson);

                var encryptedData = Encoding.UTF8.GetBytes(resJson);
                responseCxt.ContentLength = encryptedData.Length;
                await originalBodyStream.WriteAsync(encryptedData, 0, encryptedData.Length);

                responseCxt.Body = originalBodyStream;
            }
        }
        else
        {
            await _next(context);
        }
    }
    else
    {
        await _next(context);
    }
}

核心的地方就是在信息加密那个节点,然后修改response对象。

最后呢,就是运行下项目,就可以看到能正常的请求到接口了,而且参数也加密了:

将返回内容,进行解密,就可以看到

到这里,我们就很完美的实现了这个需求,而且不用修改之前的任意代码,只需要一个中间件,就能实现,还可以手动进行控制,比如指定某几个接口等。

不知道你是否有更好的建议呢,欢迎评论留言哟。

0 人点赞