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

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

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

2019-11-14 14:18:01
字體:
來源:轉載
供稿:網友

之前先后總結并發表了關于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
亚洲精品日韩av| 成人信息集中地欧美| 亚洲品质视频自拍网| 久久亚洲影音av资源网| 国产精品va在线| 欧美日韩亚洲成人| 国产亚洲欧美aaaa| 亚洲欧美日韩视频一区| 久久久久中文字幕| 亚洲国产精品大全| 最近2019年手机中文字幕| 色中色综合影院手机版在线观看| 欧美国产精品人人做人人爱| 91成人在线观看国产| 久久免费国产精品1| 日韩在线视频中文字幕| 国产精品美女在线观看| www.午夜精品| 有码中文亚洲精品| 亚洲人成电影在线| 国产成人精品午夜| 中文一区二区视频| 国产日韩中文字幕在线| 久久久视频精品| 日韩成人激情在线| 国产一区二区三区视频免费| 日韩av在线网页| 久久精品国产成人| 亚洲аv电影天堂网| 亚洲欧美日韩一区二区在线| 中文字幕亚洲一区| 久久精品国产亚洲精品| 亚洲视频网站在线观看| 欧美亚洲成人xxx| 中文字幕精品在线视频| 国产精品男女猛烈高潮激情| 这里只有精品在线播放| 日韩欧美中文在线| 久久国产精品久久精品| www国产亚洲精品久久网站| 日韩欧美亚洲一二三区| 亚洲成人教育av| 中文字幕亚洲综合久久筱田步美| 九九久久综合网站| 91九色国产社区在线观看| 亚洲欧洲日韩国产| 国产一区二区三区高清在线观看| 国产不卡在线观看| 日日狠狠久久偷偷四色综合免费| 福利一区福利二区微拍刺激| 日韩中文字幕网| 91成人在线视频| 中文字幕在线日韩| 草民午夜欧美限制a级福利片| 亚洲天堂av在线免费观看| 久久久久久高潮国产精品视| 久久精品影视伊人网| 亚洲综合日韩中文字幕v在线| 亚洲国产欧美一区二区三区久久| 欧美一级淫片aaaaaaa视频| 欧美日韩国产综合新一区| 欧美激情中文字幕乱码免费| 欧美日韩激情视频8区| 国产女同一区二区| 欧美视频中文在线看| 日韩精品www| 日韩精品小视频| 亚洲激情电影中文字幕| 欧美xxxx18性欧美| 国产成人久久久精品一区| 97成人在线视频| 日韩美女写真福利在线观看| 91国内揄拍国内精品对白| 欧美乱大交做爰xxxⅹ性3| 精品毛片三在线观看| 欧美精品在线看| 亚洲国产精久久久久久| 91国内免费在线视频| 亚洲国产精品久久久久久| 欧美日韩免费在线观看| 91精品国产高清自在线| 中文字幕精品国产| 久久韩剧网电视剧| 欧美—级a级欧美特级ar全黄| www.日韩系列| 日韩成人小视频| 亚洲国产三级网| 久久免费精品日本久久中文字幕| 国产亚洲精品久久久久久777| 91九色国产社区在线观看| 日韩精品免费观看| 国产91精品高潮白浆喷水| 亚洲最新视频在线| 久久久久免费精品国产| 亚洲另类欧美自拍| 永久免费毛片在线播放不卡| 国产日韩在线播放| 欧美肥老妇视频| 久久99久久亚洲国产| 日韩av影视在线| 欧美国产视频一区二区| 日韩在线观看网站| 精品国产精品自拍| 欧美成人手机在线| 亚洲第一视频在线观看| 国模精品系列视频| 伊人青青综合网站| 一区二区欧美亚洲| 国产精品美女主播| 久久久久久国产精品久久| 久久亚洲国产精品成人av秋霞| 国产一区视频在线播放| 亚洲天堂av综合网| 欧美在线视频免费观看| 精品性高朝久久久久久久| 国产欧美日韩综合精品| 欧美在线一级视频| 国产国语刺激对白av不卡| 国产精品影院在线观看| 啊v视频在线一区二区三区| 亚洲一区二区中文字幕| 国产午夜精品美女视频明星a级| 欧美精品在线第一页| 伊人久久久久久久久久久| 在线国产精品视频| 成人a在线视频| 国产亚洲综合久久| 国产精品天天狠天天看| 亚洲欧美激情视频| 久久久av亚洲男天堂| 国产精品男女猛烈高潮激情| 亚洲一区二区免费| 久久精品2019中文字幕| 粉嫩老牛aⅴ一区二区三区| 国产美女搞久久| 亚洲欧美另类国产| 亚洲已满18点击进入在线看片| 国产午夜精品视频免费不卡69堂| 日韩大片在线观看视频| 亚洲视频在线观看视频| 亚洲欧洲日韩国产| 国产狼人综合免费视频| 不用播放器成人网| 青青草原成人在线视频| 精品久久久久久久久久国产| 2018中文字幕一区二区三区| 在线日韩中文字幕| 国产精品美女久久久久av超清| 欧美资源在线观看| 中文字幕成人精品久久不卡| 国内精品久久久久久影视8| 亚洲亚裔videos黑人hd| 色偷偷888欧美精品久久久| 国产亚洲福利一区| 最近更新的2019中文字幕| 欧美丝袜第一区| 精品国偷自产在线视频99| 亚洲一区二区三区视频| 午夜欧美不卡精品aaaaa| 亚洲国产欧美一区二区三区同亚洲| 欧美激情亚洲一区| 亚洲欧美另类国产| 欧美成人精品h版在线观看| 久久夜精品香蕉|