亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

關于WEBService&WCF&WebApi實現身份驗證之WebApi篇

2019-11-14 14:28:15
字體:
來源:轉載
供稿:網友

之前先后總結并發表了關于WEB Service、WCF身份驗證相關文章,如下:

關于WEB Service&WCF&WebApi實現身份驗證之WEB Service篇、

關于WEB Service&WCF&WebApi實現身份驗證之WCF篇(1)、關于WEB Service&WCF&WebApi實現身份驗證之WCF篇(2)

今天再來總結關于如何實現WebApi的身份驗證,以完成該系列所有文章,WebApi常見的實現方式有:FORM身份驗證、集成WINDOWS驗證、Basic基礎認證、Digest摘要認證

 第一種:FORM身份驗證(若在asp.net應用程序使用,則該驗證方式不支持跨域,因為cookie無法跨域訪問)

1.定義一個FormAuthenticationFilterAttribute,該類繼承自AuthorizationFilterAttribute,并重寫其OnAuthorization,在該方法中添加從請求頭中獲取有無登錄的Cookie,若有則表示登錄成功,否則失敗,代碼如下:

using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Http;using System.Web.Http.Filters;using System.Web.Security;using System.Net.Http;using System.Collections.ObjectModel;using System.Net.Http.Headers;using System.Threading;using System.Security.PRincipal;using System.Net;using System.Text;namespace Webapplication1.Models{    public class FormAuthenticationFilterAttribute : AuthorizationFilterAttribute    {        private const string UnauthorizedMessage = "請求未授權,拒絕訪問。";        public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)        {            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0)            {                base.OnAuthorization(actionContext);                return;            }            if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated)            {                base.OnAuthorization(actionContext);                return;            }            var cookies = actionContext.Request.Headers.GetCookies();            if (cookies == null || cookies.Count < 1)            {                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage, Encoding.UTF8) };                return;            }            FormsAuthenticationTicket ticket = GetTicket(cookies);            if (ticket == null)            {                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { Content = new StringContent(UnauthorizedMessage, Encoding.UTF8) };                return;            }            //這里可以對FormsAuthenticationTicket對象進行進一步驗證            var principal = new GenericPrincipal(new FormsIdentity(ticket), null);            HttpContext.Current.User = principal;            Thread.CurrentPrincipal = principal;            base.OnAuthorization(actionContext);        }        private FormsAuthenticationTicket GetTicket(Collection<CookieHeaderValue> cookies)        {            FormsAuthenticationTicket ticket = null;            foreach (var item in cookies)            {                var cookie = item.Cookies.SingleOrDefault(c => c.Name == FormsAuthentication.FormsCookieName);                if (cookie != null)                {                    ticket = FormsAuthentication.Decrypt(cookie.Value);                    break;                }            }            return ticket;        }    }}

  

2.在需要認證授權后才能訪問的Controller中類或ACTION方法上添加上述授權過濾器FormAuthenticationFilterAttribute,也可在global文件中將該類添加到全局過濾器中,同時定義一個登錄ACTION,用于登錄入口,示例代碼如下:

using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Http;using System.Web;using System.Web.Http;using System.Web.Security;using WebApplication1.Models;namespace WebApplication1.Controllers{    [FormAuthenticationFilter]    public class TestController : ApiController    {        [AllowAnonymous]        [AcceptVerbs("Get")]        [Route("Api/Test/Login")]        public HttpResponseMessage Login(string uname, string pwd)        {            if ("admin".Equals(uname, StringComparison.OrdinalIgnoreCase) && "api.admin".Equals(pwd))            {                //創建票據                FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, uname, DateTime.Now, DateTime.Now.AddMinutes(30), false, string.Empty);                //加密票據                string authTicket = FormsAuthentication.Encrypt(ticket);                //存儲為cookie                HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, authTicket);                cookie.Path = FormsAuthentication.FormsCookiePath;                HttpContext.Current.Response.AppendCookie(cookie);                //或者                //FormsAuthentication.SetAuthCookie(uname, false, "/");                return Request.CreateResponse(HttpStatusCode.OK, "登錄成功!");            }            else            {                HttpContext.Current.Response.AppendCookie(new HttpCookie(FormsAuthentication.FormsCookieName) { Expires = DateTime.Now.AddDays(-10) });//測試用:當登錄失敗時,清除可能存在的身份驗證Cookie                return Request.CreateErrorResponse(HttpStatusCode.NotFound, "登錄失敗,無效的用戶名或密碼!");            }        }        // GET api/test        public IEnumerable<string> GetValues()        {            return new string[] { "value1", "value2" };        }        // GET api/test/5        public string GetValue(int id)        {            return "value";        }    }}

測試用法一:可直接在瀏覽器中訪問需要授權的方法(即:Login除外),如:http://localhost:11099/api/test/,響應結果如下:

請求頭信息如下:

若成功調用Login方法后(http://localhost:11099/api/test/login?uname=admin&pwd=api.admin),再調用上述方法,則可以獲得正常的結果,如下圖示:

看一下請求時附帶的Cookie,如下圖示:

測試用法二:采用HttpClient來調用Api的相關方法,示例代碼如下:

        public async static void TestLoginApi()        {            HttpClientHandler handler = new HttpClientHandler();            handler.UseCookies = true;//因為采用Form驗證,所以需要使用Cookie來記錄身份登錄信息            HttpClient client = new HttpClient(handler);            Console.WriteLine("Login>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");            var response = await client.GetAsync("http://localhost:11099/api/test/login/?uname=admin&pwd=api.admin");            var r = await response.Content.ReadAsAsync<dynamic>();            Console.WriteLine("StatusCode:{0}", response.StatusCode);            if (!response.IsSuccessStatusCode)            {                Console.WriteLine("Msg:{1}", response.StatusCode, r.Message);                return;            }            Console.WriteLine("Msg:{1}", response.StatusCode, r);            var getCookies = handler.CookieContainer.GetCookies(new Uri("http://localhost:11099/"));            Console.WriteLine("獲取到的cookie數量:" + getCookies.Count);            Console.WriteLine("獲取到的cookie:");            for (int i = 0; i < getCookies.Count; i++)            {                Console.WriteLine(getCookies[i].Name + ":" + getCookies[i].Value);            }            Console.WriteLine("GetValues>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");            response = await client.GetAsync("http://localhost:11099/api/test/");            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();            foreach (string item in r2)            {                Console.WriteLine("GetValues - Item Value:{0}", item);            }            Console.WriteLine("GetValue>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");            response = await client.GetAsync("http://localhost:11099/api/test/8");            var r3 = await response.Content.ReadAsAsync<string>();            Console.WriteLine("GetValue - Item Value:{0}", r3);        }

結果如下圖示:

 如果Web Api作為ASP.NET 或MVC的一部份使用,那么完全可以采用基于默認的FORM身份驗證授權特性(Authorize),或采用web.config中配置,這個很簡單,就不作說明了,大家可以網上參考關于ASP.NET 或ASP.NET MVC的FORM身份驗證。

第二種:集成WINDOWS驗證

首先在WEB.CONFIG文件中,增加如下配置,以開啟WINDOWS身份驗證,配置如下:

    <authentication mode="Windows">    </authentication>

然后在需要認證授權后才能訪問的Controller中類或ACTION方法上添加Authorize特性,Controller與上文相同不再貼出,當然也可以在WEB.CONFIG中配置:

    <authorization>      <deny users="?"/>    </authorization>

最后將WEB API寄宿到(或者說發布到)IIS,且需要在IIS中啟用WINDOWS身份驗證,如下圖示:

這樣就完成了該身份驗證模式(理論上WEB服務、WCF若都以IIS為宿主,都可以采用集成WINDOWS身份驗證模式),測試方法很簡單,第一種直接在瀏覽器中訪問,第二種采用HttpClient來調用WEB API,示例代碼如下:

        public async static void TestLoginApi2()        {            HttpClientHandler handler = new HttpClientHandler();            handler.ClientCertificateOptions = ClientCertificateOption.Manual;            handler.Credentials = new NetworkCredential("admin", "www.zuowenjun.cn");            HttpClient client = new HttpClient(handler);            var response = await client.GetAsync("http://localhost:8010/api/test/");            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();            foreach (string item in r2)            {                Console.WriteLine("GetValues - Item Value:{0}", item);            }            response = await client.GetAsync("http://localhost:8010/api/test/8");            var r3 = await response.Content.ReadAsAsync<string>();            Console.WriteLine("GetValue - Item Value:{0}", r3);        }

第三種:Basic基礎認證

1.定義一個繼承自AuthorizationFilterAttribute的HttpBasicAuthenticationFilter類,用于實現Basic基礎認證,實現代碼如下:

using System;using System.Net;using System.Text;using System.Web;using System.Web.Http.Controllers;using System.Web.Http.Filters;using System.Net.Http;using System.Web.Http;using System.Security.Principal;using System.Threading;using System.Net.Http.Headers;namespace WebApplication1.Models{    public class HttpBasicAuthenticationFilter : AuthorizationFilterAttribute    {        public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)        {            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0)            {                base.OnAuthorization(actionContext);                return;            }            if (Thread.CurrentPrincipal != null && Thread.CurrentPrincipal.Identity.IsAuthenticated)            {                base.OnAuthorization(actionContext);                return;            }            string authParameter = null;            var authValue = actionContext.Request.Headers.Authorization;            if (authValue != null && authValue.Scheme == "Basic")            {                authParameter = authValue.Parameter;  //authparameter:獲取請求中經過Base64編碼的(用戶:密碼)            }            if (string.IsNullOrEmpty(authParameter))            {                Challenge(actionContext);                return;            }            authParameter = Encoding.Default.GetString(Convert.FromBase64String(authParameter));            var authToken = authParameter.Split(':');            if (authToken.Length < 2)            {                Challenge(actionContext);                return;            }            if (!ValidateUser(authToken[0], authToken[1]))            {                Challenge(actionContext);                return;            }            var principal = new GenericPrincipal(new GenericIdentity(authToken[0]), null);            Thread.CurrentPrincipal = principal;            if (HttpContext.Current != null)            {                HttpContext.Current.User = principal;            }            base.OnAuthorization(actionContext);        }        private void Challenge(HttpActionContext actionContext)        {            var host = actionContext.Request.RequestUri.DnsSafeHost;            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "請求未授權,拒絕訪問。");            //actionContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=/"{0}/"", host));//可以使用如下語句            actionContext.Response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", string.Format("realm=/"{0}/"", host)));        }        protected virtual bool ValidateUser(string userName, string passWord)        {            if (userName.Equals("admin", StringComparison.OrdinalIgnoreCase) && password.Equals("api.admin")) //判斷用戶名及密碼,實際可從數據庫查詢驗證,可重寫            {                return true;            }            return false;        }    }}

  

 2.在需要認證授權后才能訪問的Controller中類或ACTION方法上添加上述定義的類HttpBasicAuthenticationFilter,也可在global文件中將該類添加到全局過濾器中,即可

測試方法很簡單,第一種直接在瀏覽器中訪問(同上),第二種采用HttpClient來調用WEB API,示例代碼如下:

        public async static void TestLoginApi3()        {            HttpClient client = new HttpClient();            client.DefaultRequestHeaders.Authorization = CreateBasicHeader("admin", "api.admin");            var response = await client.GetAsync("http://localhost:11099/api/test/");            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();            foreach (string item in r2)            {                Console.WriteLine("GetValues - Item Value:{0}", item);            }            response = await client.GetAsync("http://localhost:11099/api/test/8");            var r3 = await response.Content.ReadAsAsync<string>();            Console.WriteLine("GetValue - Item Value:{0}", r3);        }        public static AuthenticationHeaderValue CreateBasicHeader(string username, string password)        {            return new AuthenticationHeaderValue("Basic",                    Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", username, password))));        }

實現Basic基礎認證,除了通過繼承自AuthorizationFilterAttribute來實現自定義的驗證授權過濾器外,還可以通過繼承自DelegatingHandler來實現自定義的消息處理管道類,具體的實現方式可參見園子里的這篇文章:

http://www.49028c.com/CreateMyself/p/4857799.html

 第四種:Digest摘要認證

 1.定義一個繼承自DelegatingHandler的HttpDigestAuthenticationHandler類,用于實現在消息管道中實現Digest摘要認證,同時定義該類所需關聯或依賴的其它類,源代碼如下:

using System;using System.Collections.Concurrent;using System.Net;using System.Net.Http;using System.Net.Http.Headers;using System.Security.Cryptography;using System.Security.Principal;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Web;namespace WebApplication1.Models{    public class HttpDigestAuthenticationHandler : DelegatingHandler    {        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)        {            try            {                HttpRequestHeaders headers = request.Headers;                if (headers.Authorization != null)                {                    Header header = new Header(request.Headers.Authorization.Parameter, request.Method.Method);                    if (Nonce.IsValid(header.Nonce, header.NounceCounter))                    {                        string password = "www.zuowenjun.cn";//默認值                        //根據用戶名獲取正確的密碼,實際情況應該從數據庫查詢                        if (header.UserName.Equals("admin", StringComparison.OrdinalIgnoreCase))                        {                            password = "api.admin";//這里模擬獲取到的正確的密碼                        }                        #region 計算正確的可授權的Hash值                        string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).Tomd5Hash();                        string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash();                        string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",                                            ha1, header.Nonce, header.NounceCounter, header.Cnonce, "auth", ha2).ToMD5Hash();                        #endregion                        if (String.CompareOrdinal(header.Response, computedResponse) == 0) //比較請求的Hash值與正確的可授權的Hash值是否相同,相則則表示驗證通過,否則失敗                        {                            // digest computed matches the value sent by client in the response field.                            // Looks like an authentic client! Create a principal.                            //    var claims = new List<Claim>                            //{                            //                new Claim(ClaimTypes.Name, header.UserName),                            //                new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)                            //};                            //    ClaimsPrincipal principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });                            //    Thread.CurrentPrincipal = principal;                            //    if (HttpContext.Current != null)                            //        HttpContext.Current.User = principal;                            var principal = new GenericPrincipal(new GenericIdentity(header.UserName), null);                            Thread.CurrentPrincipal = principal;                            if (HttpContext.Current != null)                            {                                HttpContext.Current.User = principal;                            }                        }                    }                }                HttpResponseMessage response = await base.SendAsync(request, cancellationToken);                if (response.StatusCode == HttpStatusCode.Unauthorized)                {                    response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", Header.GetUnauthorizedResponseHeader(request).ToString()));                }                return response;            }            catch (Exception)            {                var response = request.CreateResponse(HttpStatusCode.Unauthorized);                response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", Header.GetUnauthorizedResponseHeader(request).ToString()));                return response;            }        }    }    public class Header    {        public Header() { }        public Header(string header, string method)        {            string keyValuePairs = header.Replace("/"", String.Empty);            foreach (string keyValuePair in keyValuePairs.Split(','))            {                int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);                string key = keyValuePair.Substring(0, index).Trim();                string value = keyValuePair.Substring(index + 1).Trim();                switch (key)                {                    case "username": this.UserName = value; break;                    case "realm": this.Realm = value; break;                    case "nonce": this.Nonce = value; break;                    case "uri": this.Uri = value; break;                    case "nc": this.NounceCounter = value; break;                    case "cnonce": this.Cnonce = value; break;                    case "response": this.Response = value; break;                    case "method": this.Method = value; break;                }            }            if (String.IsNullOrEmpty(this.Method))                this.Method = method;        }        public string Cnonce { get; private set; }        public string Nonce { get; private set; }        public string Realm { get; private set; }        public string UserName { get; private set; }        public string Uri { get; private set; }        public string Response { get; private set; }        public string Method { get; private set; }        public string NounceCounter { get; private set; }        // This property is used by the handler to generate a        // nonce and get it ready to be packaged in the        // WWW-Authenticate header, as part of 401 response        public static Header GetUnauthorizedResponseHeader(HttpRequestMessage request)        {            var host = request.RequestUri.DnsSafeHost;            return new Header()            {                Realm = host,                Nonce = WebApplication1.Models.Nonce.Generate()            };        }        public override string ToString()        {            StringBuilder header = new StringBuilder();            header.AppendFormat("realm=/"{0}/"", Realm);            header.AppendFormat(",nonce=/"{0}/"", Nonce);            header.AppendFormat(",qop=/"{0}/"", "auth");            return header.ToString();        }    }    public class Nonce    {        private static ConcurrentDictionary<string, Tuple<int, DateTime>>        nonces = new ConcurrentDictionary<string, Tuple<int, DateTime>>();        public static string Generate()        {            byte[] bytes = new byte[16];            using (var rngProvider = new RNGCryptoServiceProvider())            {                rngProvider.GetBytes(bytes);            }            string nonce = bytes.ToMD5Hash();            nonces.TryAdd(nonce, new Tuple<int, DateTime>(0, DateTime.Now.AddMinutes(10)));            return nonce;        }        public static bool IsValid(string nonce, string nonceCount)        {            Tuple<int, DateTime> cachedNonce = null;            //nonces.TryGetValue(nonce, out cachedNonce);            nonces.TryRemove(nonce, out cachedNonce);//每個nonce只允許使用一次            if (cachedNonce != null) // nonce is found            {                // nonce count is greater than the one in record                if (Int32.Parse(nonceCount) > cachedNonce.Item1)                {                    // nonce has not expired yet                    if (cachedNonce.Item2 > DateTime.Now)                    {                        // update the dictionary to reflect the nonce count just received in this request                        //nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount), cachedNonce.Item2);                        // Every thing looks ok - server nonce is fresh and nonce count seems to be                         // incremented. Does not look like replay.                        return true;                    }                }            }            return false;        }    }}

 

using System.Linq;using System.Security.Cryptography;using System.Text;namespace WebApplication1.Models{    public static class HashHelper    {        public static string ToMD5Hash(this byte[] bytes)        {            StringBuilder hash = new StringBuilder();            MD5 md5 = MD5.Create();            md5.ComputeHash(bytes)                  .ToList()                  .ForEach(b => hash.AppendFormat("{0:x2}", b));            return hash.ToString();        }        public static string ToMD5Hash(this string inputString)        {            return Encoding.UTF8.GetBytes(inputString).ToMD5Hash();        }    }}

2.將上述自定義的HttpDigestAuthenticationHandler類添加到全局消息處理管道中,代碼如下:

    public static class WebApiConfig    {        public static void Register(HttpConfiguration config)        {            config.MapHttpAttributeRoutes();            config.Routes.MapHttpRoute(                name: "DefaultApi",                routeTemplate: "api/{controller}/{id}",                defaults: new { id = RouteParameter.Optional }            );            config.MessageHandlers.Add(new HttpDigestAuthenticationHandler());//添加到消息處理管道中        }    }

3.在需要認證授權后才能訪問的Controller中類或ACTION方法上添加Authorize特性即可。

測試方法很簡單,第一種直接在瀏覽器中訪問(同上),第二種采用HttpClient來調用WEB API,示例代碼如下:

        public async static void TestLoginApi4()        {            HttpClientHandler handler = new HttpClientHandler();            handler.ClientCertificateOptions = ClientCertificateOption.Manual;            handler.Credentials = new NetworkCredential("admin", "api.admin");            HttpClient client = new HttpClient(handler);            var response = await client.GetAsync("http://localhost:11099/api/test/");            var r2 = await response.Content.ReadAsAsync<IEnumerable<string>>();            foreach (string item in r2)            {                Console.WriteLine("GetValues - Item Value:{0}", item);            }            response = await client.GetAsync("http://localhost:11099/api/test/8");            var r3 = await response.Content.ReadAsAsync<string>();            Console.WriteLine("GetValue - Item Value:{0}", r3);        }

該實現方法,參考了該篇文章:http://zrj-software.VEvb.com/blog/2163487

實現Digest摘要認證,除了上述通過繼承自DelegatingHandler來實現自定義的消息處理管道類外,也可以通過繼承自AuthorizationFilterAttribute來實現自定義的驗證授權過濾器,Basic基礎認證與Digest摘要認證流程基本相同,區別在于:Basic是將密碼直接base64編碼(明文),而Digest是用MD5進行加密后傳輸,所以兩者實現認證方式上,也基本相同。

最后說明一下,WEB SERVICE、WCF、WEB API實現身份驗證的方法有很多,每種方法都有他所適用的場景,我這個系列文章僅是列舉一些常見的實見身份驗證的方法,一是給自己復習并備忘,二是給大家以參考,文中可能有不足之處,若發現問題,可以在下面評論指出,謝謝!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美专区国产专区| 北条麻妃99精品青青久久| 欧美乱大交xxxxx| 77777少妇光屁股久久一区| 综合av色偷偷网| 日韩av最新在线观看| 久久免费视频在线观看| 亚洲激情在线观看视频免费| 4438全国成人免费| 日韩精品在线影院| 91久久精品国产91久久性色| 不用播放器成人网| 色悠悠久久久久| 亚洲天堂av在线免费观看| 午夜伦理精品一区| 日韩成人在线视频| 中文字幕日韩高清| 这里只有视频精品| www.亚洲人.com| 隔壁老王国产在线精品| 亚洲最新av在线| 欧美日韩视频免费播放| 日韩免费在线观看视频| 国产在线拍揄自揄视频不卡99| 亚洲欧美在线播放| 亚洲美女喷白浆| 欧美性xxxxxxx| 国产精品自在线| 国产欧美一区二区三区久久人妖| 国产精品99久久久久久www| 久久av资源网站| 欧美日韩中文字幕日韩欧美| 精品久久久av| 国产精品入口尤物| 久久久久久噜噜噜久久久精品| 日韩精品在线免费| 欧美精品aaa| 欧美国产日韩二区| 久久亚洲精品成人| 亚洲国产成人在线播放| 国产精品亚洲美女av网站| 欧美日韩免费区域视频在线观看| 蜜臀久久99精品久久久无需会员| 欧美大成色www永久网站婷| 欧美一级大片在线观看| 亚洲国产成人久久综合| 中文字幕最新精品| 日韩av手机在线观看| 日韩欧美在线中文字幕| 亚洲人成电影在线播放| 欧美日韩国产成人在线| 亚洲第一在线视频| 欧美日韩爱爱视频| 国产精品亚洲视频在线观看| 亚洲欧美在线看| 一区二区中文字幕| 欧美精品电影在线| 亚洲国产精品va在线看黑人动漫| 久久久国产一区二区三区| 色播久久人人爽人人爽人人片视av| 欧美日韩爱爱视频| 久久99国产精品久久久久久久久| www.xxxx欧美| 国产精品第三页| 国产精品永久免费视频| 国产免费一区视频观看免费| 性欧美亚洲xxxx乳在线观看| 亚洲精品国产精品自产a区红杏吧| 成人观看高清在线观看免费| 欧美日韩精品在线观看| 成人精品网站在线观看| 日韩在线观看成人| 日韩欧美精品网址| 欧美精品少妇videofree| 一本大道香蕉久在线播放29| 国产精品日韩一区| 日本高清视频精品| 日韩高清av一区二区三区| 亚洲欧美国内爽妇网| 亚洲免费影视第一页| 日韩中文理论片| 久久偷看各类女兵18女厕嘘嘘| 国产精品久久一区主播| 51久久精品夜色国产麻豆| 91精品国产沙发| 精品国产鲁一鲁一区二区张丽| 欧美成人高清视频| 亚洲午夜av电影| 最近2019年好看中文字幕视频| 狠狠色香婷婷久久亚洲精品| 欧美激情亚洲另类| 日韩久久精品电影| 国产日韩欧美视频| 永久免费精品影视网站| 国模视频一区二区三区| 亚洲人成亚洲人成在线观看| 欧美电影免费观看高清完整| 欧美精品久久久久久久久| 2019亚洲日韩新视频| 在线观看亚洲视频| 欧美体内谢she精2性欧美| 亚洲性无码av在线| 久久久亚洲精选| 日韩亚洲第一页| 91国内在线视频| 国产精品专区第二| 国产亚洲xxx| xxx欧美精品| 91极品女神在线| 国产亚洲精品久久久久动| 精品国产拍在线观看| 欧美日本中文字幕| 亚洲国产高潮在线观看| 国产一区二区三区在线视频| 久久网福利资源网站| 日韩欧美大尺度| 欧美午夜无遮挡| 欧美人成在线视频| 日韩最新免费不卡| 久久久999国产精品| 日韩av免费在线| 国产91久久婷婷一区二区| 亚洲欧美制服综合另类| 欧美激情一区二区三区在线视频观看| 精品香蕉在线观看视频一| 中文字幕国内精品| 久久国产精彩视频| 亚洲欧美资源在线| 欧美理论电影网| 久久久久久亚洲精品不卡| 国产不卡一区二区在线播放| 亚洲欧美国产精品专区久久| 亚洲第一区中文99精品| 久久久久久久国产精品| 黑人巨大精品欧美一区二区三区| 国产伦精品一区二区三区精品视频| 色一区av在线| 欧美极品少妇xxxxⅹ喷水| 亚洲激情免费观看| 亚洲国产欧美一区二区三区久久| 免费91在线视频| 欧美激情欧美激情在线五月| 精品久久久av| 欧美一级黄色网| 欧美国产日韩精品| 欧美视频专区一二在线观看| 欧美在线精品免播放器视频| 国产精品高清在线| 久久精品国产91精品亚洲| 国产精品狠色婷| 国内外成人免费激情在线视频网站| 日韩亚洲成人av在线| 国产精品久久久久久久久久久久久| 成人性生交大片免费看视频直播| 国产日韩欧美中文| 日韩欧美aⅴ综合网站发布| 国产亚洲精品综合一区91| 国产精品户外野外| 亚洲精品日韩久久久| 国产成人av网址| 国产中文字幕日韩| 992tv在线成人免费观看| 亚洲国模精品一区| 91久久久久久|