WebApi系列~安全校验中的防篡改和防复用

web api越来越火,因为它的跨平台,因为它的简单,因为它支持xml,json等流行的数据协议,我们在开发基于面向服务的API时,有个问题一直在困扰着我们,那就是数据的安全,请求的安全,一般所说的安全也无非就是请求的防篡改和请求的防复用,例如,你向API发一个查询用户账户的请求,在这个过程中,你可能要传递用户ID,用户所在项目ID等,而现在拦截工具如此盛行,很容易就可以把它的请求拦截,然后篡改,再转发,这样你的API就是不安全的,而对于订单,账户模块这种糟糕的API设计更是致命的,可能引起的损失是不可预计的,是灾难性的,拍拍脑子想想就知道,你向项目提现10元,我把请求拦截把10改成100000,那么这个将是一个灾难!

防篡改

一般使用的方式就是把参数拼接,加上双方约定的“密钥”,加上你的当前项目AppKey,做一次MD5加密,这个过程生成的字符串我们一般称为密文,而对应的可见参数我们叫明文,其中明文和密文用于网络传输,而密钥存储在本地和服务器,不能进行传输!

防复用

一般请求,被重复的使用,也是正常的,就上面的方式进行加密,就无法解决防复用的问题,这时我们需要在客户端和服务端分别生成UTC的时间戳,这个UTC是防止你的客户端与服务端不在同一个时区,呵呵,然后把时间戳timestamp拼在密文里就可以了,至于防复用的有效性,我们可以自定义,当然大叔定义的是秒,即同一秒内,请求可以重复发送。

大叔API安全结构图

web api核心安全校验代码片断

代码供大家参考和学习,正式的项目可以根据自己公司的需要去设计

复制代码
 /// <summary>
    /// 功能:api数据安全性验证
    /// 校验方式:ciphertext=md5(form键的值拼接+timestamp+passkey),服务端用接收到的表单数据与时间戳和自己的passkey进行md5生成,最后比较值是否一致
    /// passkey为私钥,不用于网络传递,你可以将它与appKey进行关联,appKey用来传递,服务器根据appKey去数据库里取对应的passkey然后进行比较
    /// 功能:请求唯一性,防伪造性
    /// timestamp:UTC时间戳,不用于网络传递,在客户端调用服务器时,服务器也生成yyyyMMddhhmmss的时间戳,然后进行计算,看是否过期
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public class ApiValidateFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            #region 初始化
            var context = (HttpContextBase)actionContext.Request.Properties["MS_HttpContext"];//获取传统context
            var request = context.Request;//定义传统request对象
            var paramStr = new StringBuilder();
            var coll = new NameValueCollection();
            if (request.HttpMethod.ToLower() == "get")
                coll = request.QueryString;
            else
                coll = request.Form;
            #endregion

            #region 解析XML配置文件
            var config = CacheConfigFile.ConfigFactory.Instance.GetConfig<ApiValidateModelConfig>().ApiValidateModelList.FirstOrDefault(i => i.AppKey == coll["AppKey"]);
            if (config == null)
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden)
                {
                    Content = new StringContent("AppKey不是合并的,请先去组织生成有效的Key", Encoding.GetEncoding("UTF-8"))
                };
                base.OnActionExecuting(actionContext);
            }
            if (config.ExpireDate < DateTime.Now)
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden)
                {
                    Content = new StringContent("AppKey不是合并的,密钥已过期", Encoding.GetEncoding("UTF-8"))
                };
                base.OnActionExecuting(actionContext);
            }
            #endregion

            #region 验证算法
            var keys = new List<string>();
            foreach (string param in coll.Keys)
            {
                if (!string.IsNullOrEmpty(param))
                {
                    keys.Add(param.ToLower());
                }

            }
            keys.Sort();
            foreach (string p in keys)
            {
                if (p != "ciphertext")
                {
                    if (!string.IsNullOrEmpty(coll[p]))
                    {
                        paramStr.Append(coll[p]);
                    }

                }
            }
            paramStr.Append(DateTime.Now.ToUniversalTime().ToString("yyyyMMddHHmmss"));
            paramStr.Append(config.PassKey);
            #endregion


            if (Lind.DDD.Utils.Encryptor.Utility.EncryptString(paramStr.ToString(), Lind.DDD.Utils.Encryptor.Utility.EncryptorType.MD5)
                != request["cipherText"])
            {

                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden)
                {
                    Content = new StringContent("验证失败,请求非法", Encoding.GetEncoding("UTF-8"))
                };
            }

            base.OnActionExecuting(actionContext);
        }
    }
复制代码

在上的配置项大叔把它存储到的XML里,使用的是大叔自己封装的XML缓存组件CacheConfigFile,文件第一次访问会加载到内存,下次使用直接从内存返回,而当文件修改后,文件的最后更新时间发生变化,这时缓存过期,在生产缓存时,还是采用了单例模式,

这个在大叔框架里经常被看到,呵呵。

<think>好的,用户之前问过后端系统的安全性,现在具体问到了Nonce校验的实现方法。我需要先回忆一下Nonce在安全中的作用,通常用于止重放攻击。用户可能是在开发API或者认证机制时遇到了这个问题,想知道具体怎么实现。 首先,要解释Nonce是什么,它的作用是什么。Nonce是随机生成的一次性字符串,确保请求的唯一性。然后需要分步骤说明如何实现,比如生成、传输、验证等步骤。 接下来要考虑不同的应用场景,比如HTTP API、Web表单或者OAuth,可能需要不同的处理方式。可能需要举一些例子,比如在HTTP头中添加Nonce,或者在表单中使用隐藏字段。 用户可能还关心如何存储Nonce,比如用缓存还是数据库,以及如何止存储过载。需要提到过期时间定期清理机制。此外,安全性方面,生成足够随机的Nonce,比如使用加密安全的随机数生成器。 还要注意用户可能没有提到的细节,比如Nonce的长度、是否区分客户端,以及在高并发下的处理。可能需要提到分布式系统中如何同步Nonce,比如用Redis这样的集中存储。 最后,提醒用户结合其他安全措施,比如HTTPS签名,以及常见错误,比如重复使用Nonce或者不正确的过期处理。确保用户理解Nonce校验不是唯一的解决方案,而是整体安全策略的一部分。</think>Nonce(Number Used Once,一次性数字)校验是一种止重放攻击(Replay Attack)的常用安全机制,其核心是确保每个请求的唯一性时效性。以下从 **原理、实现步骤、代码示例** **注意事项** 展开说明: --- ### **1. Nonce 的作用原理** - **重放**:攻击者截获合法请求后无法重复使用,因 Nonce 仅一次有效。 - **抗篡改**:通常结合签名(Signature)使用,确保请求完整性。 - **时序性**:可关联时间戳,拒绝过期请求(如超过 5 分钟的请求失效)。 --- ### **2. 实现步骤** #### **2.1 生成与下发** - **客户端**:发起请求前,先向服务端申请一个 Nonce(或由客户端生成)。 - **服务端**:生成随机字符串(如 UUID 或加密随机数),存储并设置 **过期时间**(如 5 分钟)。 #### **2.2 请求携带** - 客户端在后续请求中必须携带该 Nonce,通常通过以下方式: - **HTTP Header**:`X-Nonce: {value}` - **URL 参数**:`/api?nonce=xxx&sign=yyy` - **请求体**:JSON/Form 中包含 `nonce` 字段。 #### **2.3 服务端验证** 1. **存在性检查**:请求是否包含 Nonce。 2. **唯一性检查**:查询存储(如 Redis),确认 Nonce 未被使用过。 3. **过期检查**:若关联时间戳,检查是否在有效期内。 4. **标记已用**:验证后立即标记 Nonce 失效,止重复使用。 --- ### **3. 代码示例(以 Node.js + Redis 为例)** #### **3.1 生成 Nonce** ```javascript const crypto = require('crypto'); // 生成 16 字节的随机 Hex 字符串作为 Nonce function generateNonce() { return crypto.randomBytes(16).toString('hex'); } ``` #### **3.2 存储与校验(Redis)** ```javascript const redis = require('redis'); const client = redis.createClient(); // 存储 Nonce,设置 5 分钟过期 async function saveNonce(nonce) { await client.set(nonce, 'used', 'EX', 300); // EX 表示秒级过期 } // 校验 Nonce 是否存在且未使用 async function validateNonce(nonce) { const exists = await client.exists(nonce); return exists === 1; // 存在则为 true,否则 false } ``` #### **3.3 中间件校验** ```javascript app.use(async (req, res, next) => { const nonce = req.headers['x-nonce']; if (!nonce) return res.status(400).send('Missing Nonce'); const isValid = await validateNonce(nonce); if (!isValid) return res.status(403).send('Invalid Nonce'); next(); // 验证通过 }); ``` --- ### **4. 增强安全性的实践** #### **4.1 结合时间戳** - 客户端在请求中额外携带时间戳(如 `X-Timestamp: 1620000000`)。 - 服务端校验时间戳与当前时间的差值(如拒绝超过 5 分钟的请求)。 #### **4.2 签名机制** - 客户端用 Nonce + 时间戳 + 请求参数生成签名(如 HMAC-SHA256)。 - 服务端用相同算法重新计算签名,比对一致性。 #### **4.3 限流与爆破** - 对同一客户端(IP 或 UserID)限制 Nonce 申请频率。 - 失败次数过多时临时封禁。 --- ### **5. 适用场景** - **支付接口**:止重复扣款。 - **关键操作**:如修改密码、删除数据。 - **OAuth 授权**:避免令牌被截获复用。 --- ### **6. 注意事项** - **随机性**:使用加密安全的随机数生成器(如 `crypto.randomBytes`),避免伪随机(如 `Math.random()`)。 - **存储选择**:高频场景用 Redis/Memcached,低频可存数据库。 - **分布式一致性**:集群环境下需集中存储 Nonce(如 Redis 集群)。 - **清理机制**:定期清理过期 Nonce,避免存储膨胀。 --- ### **7. 常见问题** - **Q:Nonce 需要全局唯一吗?** A:是,否则不同客户端的 Nonce 可能冲突。 - **Q:Nonce CSRF Token 的区别?** A:CSRF Token 跨站请求伪造,通常与会话绑定;Nonce 更聚焦重放,常与时间或签名绑定。 - **Q:是否必须结合 HTTPS?** A:是的,否则 Nonce 可能在传输中被截获。 --- 通过合理设计 Nonce 机制,可显著提升接口安全性,但需结合业务场景平衡安全性与性能(如高频请求需优化存储查询)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值