1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        短信驗(yàn)證碼最佳實(shí)踐

        共 15743字,需瀏覽 32分鐘

         ·

        2021-12-09 21:51

        來(lái)源:cnblogs.com/guokun/p/11042903.html

        1、背景

        年初,從外地轉(zhuǎn)移陣地到西安,轉(zhuǎn)眼已兩個(gè)多月。很久不寫業(yè)務(wù)代碼了,到了新公司,條件惡劣到前所未有,從需求,設(shè)計(jì),架構(gòu),實(shí)現(xiàn),實(shí)施,測(cè)試,bug修復(fù),項(xiàng)目計(jì)劃制定,項(xiàng)目管理,全他媽我一個(gè)人,關(guān)鍵是平臺(tái)很大,很多技術(shù)難點(diǎn),時(shí)間還又緊,要命的是,公司銷售左派盛行,連技術(shù)老大都是銷售出身,直屬領(lǐng)導(dǎo)設(shè)計(jì)出身不懂技術(shù)。。。點(diǎn)到為止,剩下的大家自行腦補(bǔ)。吐槽歸吐槽,事兒還是得干,程序猿的基本素養(yǎng)不是。于是一個(gè)多月,996式搞法,項(xiàng)目上線了,其中包括那個(gè)我半天做出來(lái)的短信驗(yàn)證碼。。。廢話大半天,終于說(shuō)到今天的重點(diǎn)了,那就言歸正傳。
        對(duì)于短信驗(yàn)證碼,前陣子,看到thoughtworks洞見分享了一篇短信驗(yàn)證碼的文章(https://insights.thoughtworks.cn/sms-authentication-login-api/),感覺可以作為一個(gè)最佳實(shí)踐了,老早就決定按照文中觀點(diǎn)實(shí)踐了,奈何那陣一直996,沒時(shí)間,直到最近,才忙里偷閑動(dòng)手整理。原文不再贅述,這里就文中對(duì)于短信驗(yàn)證碼的關(guān)鍵要點(diǎn),截圖如下:

        2.實(shí)現(xiàn)

        首先,直接上解決方案截圖:
        典型的應(yīng)用層 =》 服務(wù)層調(diào)用架構(gòu),采用接口層及IOC解耦。我們先看工具庫(kù),Captcha.Util,重點(diǎn)說(shuō)下ImageCaptchaHelper與MsgCaptchaHelper。圖形驗(yàn)證碼,這里要致敬EdiWang,圖形驗(yàn)證碼直接盜版的他的(https://edi.wang/post/2018/10/13/generate-captcha-code-aspnet-core)。整個(gè)文件中代碼太長(zhǎng),就不貼了,這里只給幾個(gè)要點(diǎn):
        (1)生成圖形驗(yàn)證碼的工程,需要標(biāo)記unsafe,如下:

        這是因?yàn)閳D形驗(yàn)證碼的生成有部分用到了指針相關(guān),熟悉C#的朋友應(yīng)該對(duì)這個(gè)背景知識(shí)不陌生:搜索公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師復(fù)“2T”,送你一份驚喜禮包。

        不用關(guān)心這是啥啥啥,照著設(shè)置unsafe就成了,我壓根兒就懶得看這段指針代碼,就是看了也不一定看得懂。。。

        (2)圖形驗(yàn)證碼的位置調(diào)整:

        void DrawCaptchaCode()
        {
            SolidBrush fontBrush = new SolidBrush(Color.Black);
            int fontSize = GetFontSize(width, captchaCode.Length);
            Font font = new Font(FontFamily.GenericSerif, fontSize, FontStyle.Bold, GraphicsUnit.Pixel);
            for (int i = 0; i < captchaCode.Length; i++)
            {
                fontBrush.Color = GetRandomDeepColor();

                int shiftPx = fontSize / 6;

                //float x = i * fontSize + rand.Next(-shiftPx, shiftPx) + rand.Next(-shiftPx, shiftPx);
                float x = i * fontSize + rand.Next(-shiftPx, shiftPx) / 2;
                //int maxY = height - fontSize;
                int maxY = height - fontSize * 2;
                if (maxY < 0)
                {
                    maxY = 0;
                }
                float y = rand.Next(0, maxY);

                graph.DrawString(captchaCode[i].ToString(), font, fontBrush, x, y);
            }
        }                                                                                                                        

        代碼中,X,Y的值,就是驗(yàn)證碼構(gòu)成字符中,各個(gè)字符的二維偏移量,越大,偏移就可能越厲害。注釋掉的是原來(lái)的,下邊一行是我調(diào)整過(guò)后的,因?yàn)閷?shí)際使用中發(fā)現(xiàn)不少情況下會(huì)出現(xiàn)字符超出邊框界限,沒法兒認(rèn)的情況。

        (3)噪音線處理

         void DrawDisorderLine()
        {
            Pen linePen = new Pen(new SolidBrush(Color.Black), 2);
            //for (int i = 0; i < rand.Next(3, 5); i++)
            for (int i = 0; i < 2; i++)
            {
                linePen.Color = GetRandomDeepColor();

                Point startPoint = new Point(rand.Next(0, width), rand.Next(0, height));
                Point endPoint = new Point(rand.Next(0, width), rand.Next(0, height));
                graph.DrawLine(linePen, startPoint, endPoint);
            }
        }

        不管是偏移也好,噪音線也好,本質(zhì)上都是為了降低OCR識(shí)別率。for循環(huán)的次數(shù),代表噪音線條數(shù),條數(shù)越多,可能就越難辨識(shí)。之所以從3到5條隨機(jī),改為固定2條,是因?yàn)閷?shí)際使用時(shí)發(fā)現(xiàn),當(dāng)噪音線隨機(jī)成5條時(shí),很多圖形驗(yàn)證碼基本人眼沒法兒辨識(shí),沒騙過(guò)機(jī)器,估計(jì)先把人眼晃瞎嘍。

        以上就是圖形驗(yàn)證碼中需要注意或者自己需要調(diào)整的幾個(gè)點(diǎn)。接下來(lái),我們看短信驗(yàn)證碼的生成:

        /// <summary>
        /// 短信驗(yàn)證碼工具類
        /// </summary>
        public static class MsgCaptchaHelper
        {
            /// <summary>
            /// 生成指定位數(shù)的隨機(jī)數(shù)字碼
            /// </summary>
            /// <param name="length"></param>
            /// <returns></returns>
            public static string CreateRandomNumber(int length)
            {
                Random random = new Random();
                StringBuilder sbMsgCode = new StringBuilder();
                for (int i = 0; i < length; i++)
                {
                    sbMsgCode.Append(random.Next(0, 9));
                }

                return sbMsgCode.ToString();
            }
        }

        簡(jiǎn)單粗暴,傳入短信驗(yàn)證碼長(zhǎng)度,是多少位,我就拼接多少個(gè)隨機(jī)生成的數(shù)字字符構(gòu)成滿足長(zhǎng)度要求的驗(yàn)證碼。

        接下來(lái),是Service層,圖形驗(yàn)證碼、短信驗(yàn)證碼的核心邏輯都在這里,整個(gè)工程就一個(gè)服務(wù)CaptchaService。首先,我們看看服務(wù)層依賴:

        #region Private Fields

        private readonly IMemoryCache _cache;
        private readonly IHostingEnvironment _hostingEnvironment;

        #endregion

        #region Constructors

        public CaptchaService(IMemoryCache cache, IHostingEnvironment hostingEnvironment)
        {
            _cache = cache;
            _hostingEnvironment = hostingEnvironment;
        }

        #endregion

        其中內(nèi)存緩存的作用,是緩存圖形驗(yàn)證碼、短信驗(yàn)證碼,供后續(xù)校驗(yàn)、過(guò)期使用,帶會(huì)讓詳述。這里為了演示核心主題,使用了內(nèi)存緩存,如果是大型生產(chǎn)環(huán)境,尤其是高并發(fā)的情況,可能需要分布式緩存,甚至還可能需要搭配消息隊(duì)列。core寄宿環(huán)境接口,目的是為了開發(fā)環(huán)境或測(cè)試環(huán)境下,直接返回短信驗(yàn)證碼的值而無(wú)需真實(shí)發(fā)送短信驗(yàn)證碼,生產(chǎn)環(huán)境再調(diào)用第三方運(yùn)行商發(fā)送短信驗(yàn)證碼。

        搜索公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師后臺(tái)回復(fù)“2T”,獲取一份驚喜禮包。

        接下來(lái),我們看圖形驗(yàn)證碼的請(qǐng)求:

        /// <summary>
        /// 獲取圖片驗(yàn)證碼
        /// </summary>
        /// <param name="imgCaptchaDto">圖形驗(yàn)證碼請(qǐng)求信息</param>
        /// <returns></returns>
        public CaptchaResult GetImageCaptcha(ImgCaptchaDto imgCaptchaDto)
        {
            var captchaCode = ImageCaptchaHelper.GenerateCaptchaCode();
            var result = ImageCaptchaHelper.GenerateCaptcha(100, 36, captchaCode);
            _cache.Set($"ImgCaptcha{imgCaptchaDto.ImgCaptchaType}{imgCaptchaDto.Mobile}", result.CaptchaCode);

            return result;
        }

        可以看見,生成隨機(jī)圖形驗(yàn)證碼之后,以圖形驗(yàn)證碼類型,手機(jī)號(hào),外加ImgCaptcha前綴拼接,作為圖形驗(yàn)證碼的key緩存圖形驗(yàn)證碼的值??刂破鲗拥奶幚砣缦拢?/span>

        /// <summary>
        /// 獲取圖片驗(yàn)證碼
        /// </summary>
        /// <param name="imgCaptchaDto">圖形驗(yàn)證碼請(qǐng)求信息</param>
        [HttpGet("img")]
        public IActionResult GetImageCaptcha([FromQuery]ImgCaptchaDto imgCaptchaDto)
        {
            var result = _captchaService.GetImageCaptcha(imgCaptchaDto);
            var stream = new MemoryStream(result.CaptchaByteData);

            return new FileStreamResult(stream, "image/png");
        }

        拿到短信驗(yàn)證碼結(jié)果之后,以圖形驗(yàn)證碼二進(jìn)制流為基礎(chǔ)構(gòu)建FileStreamResult返回。這里需要特別注意的是,MemoryStream不能按照最佳實(shí)踐用using包圍起來(lái),因?yàn)榱私釳VC或webapi請(qǐng)求處理管道的應(yīng)該知道,當(dāng)前FileStreamResult返回后并不是立即處理,而是在管道的某個(gè)階段及某個(gè)特定時(shí)候才處理控制器方法的返回結(jié)果,假如這里using包起來(lái)了,那控制器方法執(zhí)行完畢,memorystream也就釋放了,將來(lái)FileStreamResult執(zhí)行時(shí)候就會(huì)直接異常。

        圖形驗(yàn)證碼的校驗(yàn):

        /// <summary>
        /// 驗(yàn)證圖片驗(yàn)證碼
        /// </summary>
        /// <param name="imgCaptchaDto">圖形驗(yàn)證碼信息</param>
        /// <returns></returns>
        public bool ValidateImageCaptcha(ImgCaptchaDto imgCaptchaDto)
        {
            var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{imgCaptchaDto.ImgCaptchaType}{imgCaptchaDto.Mobile}");
            if (string.Equals(imgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 驗(yàn)證圖片驗(yàn)證碼
        /// </summary>
        /// <param name="imgCaptchaDto">圖形驗(yàn)證碼信息</param>
        /// <returns></returns>
        [HttpPost("img")]
        public IActionResult ValidateImageCaptcha(ImgCaptchaDto imgCaptchaDto)
        {
            bool isCaptchaValid = _captchaService.ValidateImageCaptcha(imgCaptchaDto);
            if (isCaptchaValid)
            {
                return Ok("圖形驗(yàn)證碼驗(yàn)證成功");
            }
            else
            {
                return StatusCode(StatusCodes.Status403Forbidden, "驗(yàn)證失敗,請(qǐng)輸入正確手機(jī)號(hào)及獲取到的驗(yàn)證碼");
            }
        }

        這里沒啥好說(shuō)的,就是按照同樣的構(gòu)造鍵取出圖形驗(yàn)證碼并與客戶端發(fā)送過(guò)來(lái)的比對(duì),相同就校驗(yàn)通過(guò)。

        接下來(lái),看看短信驗(yàn)證碼的請(qǐng)求:

        /// <summary>
        /// 獲取短信驗(yàn)證碼
        /// </summary>
        /// <param name="msgCaptchaDto">短信驗(yàn)證碼請(qǐng)求信息</param>
        /// <returns></returns>
        public (bool, string) GetMsgCaptcha(MsgCaptchaDto msgCaptchaDto)
        {
            if (string.IsNullOrWhiteSpace(msgCaptchaDto.ImgCaptcha))
            {
                throw new BusinessException((int)ErrorCode.BadRequest, "請(qǐng)輸入圖形驗(yàn)證碼");
            }

            var cachedImageCaptcha = _cache.Get<string>($"ImgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}");
            if (!string.Equals(msgCaptchaDto.ImgCaptcha, cachedImageCaptcha, StringComparison.OrdinalIgnoreCase))
            {
                return (false"驗(yàn)證失敗,請(qǐng)輸入正確手機(jī)號(hào)及獲取到的圖形驗(yàn)證碼");
            }

            string key = $"MsgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}";
            var cachedMsgCaptcha = _cache.Get<MsgCaptchaDto>(key);
            if (cachedMsgCaptcha != null)
            {
                var offsetSecionds = (DateTime.Now - cachedMsgCaptcha.CreateTime).Seconds;
                if (offsetSecionds < 60)
                {
                    return (false, $"短信驗(yàn)證碼獲取太頻繁,請(qǐng){60 - offsetSecionds}秒之后再獲取");
                }
            }

            var msgCaptcha = MsgCaptchaHelper.CreateRandomNumber(6);
            msgCaptchaDto.MsgCaptcha = msgCaptcha;
            msgCaptchaDto.CreateTime = DateTime.Now;
            msgCaptchaDto.ValidateCount = 0;
            _cache.Set(key, msgCaptchaDto, TimeSpan.FromMinutes(2));

            if (_hostingEnvironment.IsProduction())
            {
                //TODO:調(diào)用第三方SDK實(shí)際發(fā)送短信
                return (true"發(fā)送成功");
            }
            else        //非生產(chǎn)環(huán)境,直接將驗(yàn)證碼返給前端,便于調(diào)查跟蹤
            {
                return (true, $"發(fā)送成功,短信驗(yàn)證碼為:{msgCaptcha}");
            }
        }

        請(qǐng)求短信驗(yàn)證碼,需要把對(duì)應(yīng)的圖形驗(yàn)證碼一并隨請(qǐng)求發(fā)過(guò)來(lái)。這里額外交代一下,圖形驗(yàn)證碼類型,短信驗(yàn)證碼類型是需要一一對(duì)應(yīng)的,實(shí)際業(yè)務(wù)中,我們可能有注冊(cè)驗(yàn)證碼,找回密碼驗(yàn)證碼,修改密碼驗(yàn)證碼,各種業(yè)務(wù)驗(yàn)證碼等,每種業(yè)務(wù)驗(yàn)證碼對(duì)應(yīng)的圖形驗(yàn)證碼類型和短信驗(yàn)證碼類型應(yīng)該是對(duì)應(yīng)的,如果為了減少錯(cuò)誤,可以定義兩個(gè)枚舉,這里因?yàn)槭窍氚羊?yàn)證碼做成通用服務(wù),所以類型并未根據(jù)具體業(yè)務(wù)定義枚舉?;氐桨l(fā)送短信驗(yàn)證碼的實(shí)現(xiàn)上,可以看到,首先就校驗(yàn)圖形驗(yàn)證碼,圖形驗(yàn)證碼校驗(yàn)通過(guò)的情況下,按照與圖形驗(yàn)證碼Key類似的規(guī)則構(gòu)建短信驗(yàn)證碼緩存key,并從緩存找是否存在對(duì)應(yīng)的短信驗(yàn)證碼緩存對(duì)象。如果找到了,則說(shuō)明相同手機(jī)號(hào)的相同業(yè)務(wù)已經(jīng)獲取過(guò)短信驗(yàn)證碼且指定時(shí)間內(nèi)未失效,這種情況下,是不能獲取短信驗(yàn)證碼的,否則視為短信轟炸,直接返回。示例中,或者說(shuō)按照騷窩最佳實(shí)踐要點(diǎn)中,一分鐘之內(nèi)是只能獲取一條的, 所以我定了60s,并做時(shí)差提示。假如不存在對(duì)應(yīng)短信驗(yàn)證碼,則構(gòu)造短信驗(yàn)證碼對(duì)象,分別設(shè)置短信碼、創(chuàng)阿金時(shí)間為當(dāng)前時(shí)間、校驗(yàn)次數(shù)為0,并緩存。最后,根據(jù)當(dāng)前是開發(fā)還是生產(chǎn)環(huán)境,決定是直接返驗(yàn)證碼還是真實(shí)發(fā)送短信。

        最后,看短信驗(yàn)證碼校驗(yàn):

        /// <summary>
        /// 驗(yàn)證短信驗(yàn)證碼
        /// </summary>
        /// <param name="msgCaptchaDto">短信驗(yàn)證碼信息</param>
        /// <returns></returns>
        public (bool, string) ValidateMsgCaptcha(MsgCaptchaDto msgCaptchaDto)
        {
            var key = $"MsgCaptcha{msgCaptchaDto.MsgCaptchaType}{msgCaptchaDto.Mobile}";
            var cachedMsgCaptcha = _cache.Get<MsgCaptchaDto>(key);
            if (cachedMsgCaptcha == null)
            {
                return (false"短信驗(yàn)證碼無(wú)效,請(qǐng)重新獲取");
            }

            if (cachedMsgCaptcha.ValidateCount >= 3)
            {
                _cache.Remove(key);
                return (false"短信驗(yàn)證碼已失效,請(qǐng)重新獲取");
            }
            cachedMsgCaptcha.ValidateCount++;

            if (!string.Equals(cachedMsgCaptcha.MsgCaptcha, msgCaptchaDto.MsgCaptcha, StringComparison.OrdinalIgnoreCase))
            {
                return (false"短信驗(yàn)證碼錯(cuò)誤");
            }
            else
            {
                return (true"驗(yàn)證通過(guò)");
            }
        }

        邏輯蠻簡(jiǎn)單,首先按照指定鍵取短信驗(yàn)證碼緩存,取到了,再看該緩存對(duì)象校驗(yàn)次數(shù),如果超過(guò)3次了,則直接攔截,視為暴力攻擊。未超過(guò),則校驗(yàn)次數(shù)累加,并比對(duì),相同則視為OK。這里需要特別注意的是,進(jìn)程內(nèi)緩存,設(shè)置完校驗(yàn)次數(shù)就OK了,可以不用回寫緩存,但如果是分布式緩存,則需要回寫修改過(guò)的短信驗(yàn)證碼對(duì)象至緩存。至此,核心邏輯實(shí)現(xiàn)部分差不多了,接下來(lái)我們看實(shí)際效果。

        相關(guān)閱讀:2T架構(gòu)師學(xué)習(xí)資料干貨分享

        3.運(yùn)行效果:

        首先,請(qǐng)求圖形驗(yàn)證碼

        接下來(lái),校驗(yàn)此圖形驗(yàn)證碼。我們先用正確的校驗(yàn):

        再用錯(cuò)誤的去校驗(yàn):

        正確的校驗(yàn)成功,錯(cuò)誤的校驗(yàn)失敗,那么校驗(yàn)部分OK了。然后,我們看看,用此圖形驗(yàn)證碼去獲取短信驗(yàn)證碼,我們先用錯(cuò)誤的圖形驗(yàn)證碼去校驗(yàn):

        好,已經(jīng)失敗了,那我們換正確的試試:

        可以看到,短信驗(yàn)證碼已經(jīng)發(fā)送成功了。我們?cè)侔l(fā)送一次:

        這時(shí)候,系統(tǒng)提示,獲取太頻繁了,請(qǐng)20s后再。因?yàn)槲以诖a字,時(shí)間過(guò)去了點(diǎn)兒,所以是20s,這時(shí)間是根據(jù)當(dāng)前時(shí)間減去短信驗(yàn)證碼創(chuàng)建時(shí)間,在與60s的頻率限制求差值,來(lái)算倒計(jì)時(shí)的。好,現(xiàn)在我們拿剛才的短信驗(yàn)證碼去校驗(yàn):

        搜索公眾號(hào)互聯(lián)網(wǎng)架構(gòu)師后臺(tái)回復(fù)“2T”,獲取一份驚喜禮包。

        。。。我去,碼字的這會(huì)兒,短信驗(yàn)證碼緩存過(guò)期了。。。算了,這次哥從圖形驗(yàn)證碼開始整連貫的截圖吧,碼字先放一邊兒

        (1)獲取圖形驗(yàn)證碼:

        (2)校驗(yàn)圖形驗(yàn)證碼:

        (3)獲取短息驗(yàn)證碼:

        (4)用正確短信驗(yàn)證碼校驗(yàn)(第1次校驗(yàn)):

        (5)用錯(cuò)誤驗(yàn)證碼校驗(yàn)(第2次):

        (6)用錯(cuò)誤驗(yàn)證碼校驗(yàn)(第3次):

        (7)用正確驗(yàn)證碼校驗(yàn)(第4次):

        注意最后幾張短信驗(yàn)證碼校驗(yàn)的截圖結(jié)果,前3次,正確的驗(yàn)證碼校驗(yàn)成功,錯(cuò)誤的校驗(yàn)失敗,第4次開始,因?yàn)橐呀?jīng)達(dá)到校驗(yàn)上線3次,所以直接失效了,不管驗(yàn)證碼正確與否。

        好,廢話的這會(huì)兒,應(yīng)該又失效了,我們?cè)僦噩F(xiàn)下:

        4.源碼

        https://github.com/KINGGUOKUN/Captcha.git。整個(gè)解決方案是服務(wù)化的,可以開箱即用。

        5.總結(jié)

        我們?cè)倩剡^(guò)頭來(lái)看看騷窩的短信驗(yàn)證碼核心要點(diǎn):

        這么多要點(diǎn)中,本方案有兩個(gè)沒有實(shí)現(xiàn),如截圖所示,同一個(gè)手機(jī)號(hào)在同一時(shí)間內(nèi)可以有多個(gè)有效的短信驗(yàn)證碼以及第三方api,第三方api說(shuō)的并不明確,到底是什么,而且如果是集成第三方了,那么可能就用不上短信驗(yàn)證碼了,直接用戶名、密碼、第三方api就直接了,至于另一條,同一手機(jī)號(hào)同一時(shí)間內(nèi)可以有多個(gè)有效的短信驗(yàn)證碼,個(gè)人感覺不太實(shí)用和必要。假如要實(shí)踐的話,其實(shí)也簡(jiǎn)單,方案中短信驗(yàn)證碼模型中,并不是保存單個(gè)短信驗(yàn)證碼,而是緩存驗(yàn)證碼列表就OK了,這點(diǎn)不難。


        1、2T架構(gòu)師學(xué)習(xí)資料干貨分享

        2、985副教授工資曝光

        3、心態(tài)崩了!稅前2萬(wàn)4,到手1萬(wàn)4,年終獎(jiǎng)扣稅方式1月1日起施行~

        4、雷軍做程序員時(shí)寫的博客,很強(qiáng)大!

        5、人臉識(shí)別的時(shí)候,一定要穿上衣服??!

        6、清華大學(xué):2021 元宇宙研究報(bào)告!

        7、績(jī)效被打3.25B,員工將支付寶告上了法院,判了

        瀏覽 65
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美黄色片在线观看 | a国产在线播放 | 操免费网站| 大鸡巴操逼 | 丰满波霸爆乳一区二区 | 一边吃奶一边啪好爽受不了了 | 色秘 乱码一区二区三区网站 | 日本少妇无码精品12p顾欣怡 | 深夜激情网 | 叉逼网站|