<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆 - 5  文章 - 17  trackbacks - 0
    <2007年9月>
    2627282930311
    2345678
    9101112131415
    16171819202122
    23242526272829
    30123456

    常用鏈接

    留言簿(3)

    隨筆分類

    隨筆檔案

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    摘要

    Acegi提供了多種身份驗證方式(表單驗證,CAS等),但只允許一種用戶登錄,而就個人了解,有一些系統(tǒng)是需要多種用戶登錄的。比如企業(yè)的員工需要登錄并使用系統(tǒng),企業(yè)也允許客戶登錄系統(tǒng)并使用有限的功能。以下嘗試剖析Acegi的表單驗證過程,并給出一種允許多種用戶登錄的方案。本方案基本達(dá)到“能用”的目的,但不一定是最佳方案。希望這篇文章能起到拋磚引玉的作用,給各位朋友一點參考,也希望各位提出有益的建議。

    Acegi的表單驗證方式簡要分析

    一個使用Acegi的表單驗證的登錄頁面通常需要在表單提交時request的j_username和j_password參數(shù)賦值,即用戶名和密碼,而表單則提交到Acegi設(shè)定到驗證地址。例如:

    <form method="post" id="loginForm" action="<c:url value='/j_security_check'/>" >
            
    <input type="text" name="j_username" id="j_username" />

            
    <input type="password" name="j_password" id="j_password" />

            
    <input type="submit" name="login" value="Login" />
    </form>

    服務(wù)器的Servlet容器收到請求后會傳遞給Acegi的FilterToBeanProxy,這需要在web.xml中進(jìn)行配置。例如:

    <filter>
        
    <filter-name>securityFilter</filter-name>
        
    <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
        
    <init-param>
            
    <param-name>targetClass</param-name>
            
    <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
        
    </init-param>
    </filter>
    <filter-mapping>
        
    <filter-name>securityFilter</filter-name>
        
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    FilterToBeanProxy基本上只起到調(diào)用轉(zhuǎn)發(fā)的作用。在它的doFilter方法中會找到類型為FilterChainProxy的bean,調(diào)用后者的doFilter方法,同時把request、response會chain參數(shù)都傳遞過去。代碼如下:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        
    throws IOException, ServletException {
        
    if (!initialized) {
            doInit();
        }

        delegate.doFilter(request, response, chain);
    }

    上面的代碼中的delegate就是找到的類型FilterChainProxy的bean。FilterChainProxy的典型配置如下:

    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        
    <property name="filterInvocationDefinitionSource">
            
    <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,
            
    </value>
        
    </property>
    </bean>

    對于上面的配置,引用一段Acegi聯(lián)機(jī)幫助中的說明來幫助理解:

    Internally Acegi Security will use a PropertyEditor to convert the string presented in the above XML fragment into a FilterInvocationDefinitionSource object. What's important to note at this stage is that a series of filters will be run - in the order specified by the declaration - and each of those filters are actually the <bean id> of another bean inside the application context.

    實際上,F(xiàn)ilterChainProxy的doFilter方法會執(zhí)行如下處理:
    1.讀取配置,如果配置為空,則直接調(diào)用chain.doFilter,返回
    2.如果配置不為空,則根據(jù)配置找到各個bean,放入Filter數(shù)組中。如果配置中沒有配置任何bean,則直接調(diào)用chain.doFilter,返回
    3.FilterChainProxy創(chuàng)建一個VirtualFilterChain對象,并將chain封裝為一個FilterInvocation對象,將它和Filter數(shù)組一起傳遞給VirtualFilterChain的構(gòu)造函數(shù)。VirtualFilterChain的構(gòu)造函數(shù)初始化了一個指針currentPosition,指向Filter數(shù)組的第一個元素additionalFilters[0]
    4.FilterChainProxy調(diào)用VirtualFilterChain的doFilter方法,在該方法中將指針currentPosition前移,調(diào)用additionalFilters[0]的doFilter方法。注意這里VirtualFilterChain把自身作為參數(shù)傳遞給additionalFilters[0]的doFilter方法,這樣additionalFilters[0]的doFilter方法最后會調(diào)用VirtualFilterChain的doFilter方法,這樣控制就又回到了VirtualFilterChain!于是VirtualFilterChain又將currentPosition前移,調(diào)用additionalFilters[1]的doFilter方法......
    5.當(dāng)additionalFilters中所有元素的doFilter都執(zhí)行完畢,VirtualFilterChain執(zhí)行fi.getChain().doFilter,而fi.getChain()的值就是FilterChainProxy的doFilter方法中的參數(shù)chain的值。這樣我們就理解了FilterChainProxy是怎樣讓調(diào)用兜了個圈,又傳遞出去的。

    重新回到FilterChainProxy的配置,看到它調(diào)用了authenticationProcessingFilter這個Filter。讓我們看看它的配置:

    <bean id="authenticationProcessingFilter"
        class
    ="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
        
    <property name="authenticationManager" ref="authenticationManager"/>
        
    <property name="authenticationFailureUrl" value="/login.jsp?error=true"/>
        
    <property name="defaultTargetUrl" value="/"/>
        
    <property name="filterProcessesUrl" value="/j_security_check"/>
        
    <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

    authenticationProcessingFilter的其中一個作用就是獲取客戶端提交的用戶名和密碼,將它們封裝為一個Token,傳遞給authenticationManager的authenticate方法,由后者負(fù)責(zé)驗證。

    看看authenticationManager的配置:

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        
    <property name="providers">
            
    <list>
                
    <ref local="daoAuthenticationProvider"/>
                
    <ref local="anonymousAuthenticationProvider"/>
                
    <ref local="rememberMeAuthenticationProvider"/>
            
    </list>
        
    </property>
    </bean>

    authenticationManager依次調(diào)用每個provider的authenticate方法。如果某個provider驗證成功則返回;如果所有的驗證都不成功,則拋出異常。

    讓我們看看daoAuthenticationProvider的配置:

    <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
         
    <property name="userDetailsService" ref="userDao"/>
         
    <property name="passwordEncoder" ref="passwordEncoder"/>
    </bean>

    daoAuthenticationProvider在authenticate方法中調(diào)用retrieveUser方法取得用戶信息,執(zhí)行基本的驗證,然后調(diào)用additionalAuthenticationChecks執(zhí)行附加的驗證(比如驗證密碼是否正確)。在retrieveUser方法中調(diào)用userDetailsService的loadUserByUsername方法取得用戶信息,而userDetailsService是一個名為userDao的bean。讓我們看看userDao的配置:

    <bean id="userDao" class="cn.net.cogent.summer.extension.appfuse.dao.hibernate.EmployeeDaoHibernate">
        
    <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    userDao實現(xiàn)了Acegi的UserDetailsService接口,該接口只有l(wèi)oadUserByUsername方法。loadUserByUsername方法根據(jù)傳入的username取得相應(yīng)的Employee對象(Employee實現(xiàn)了UserDetails接口),該對象返回給daoAuthenticationProvider,由它和authenticationManager聯(lián)合完成驗證的任務(wù)。

    以上對Acegi對表單驗證過程進(jìn)行了簡單對分析,限于篇幅,無法深入分析源碼。但從配置可以畫出驗證過程的對象圖如下:



    從圖中可以看出,盡管Acegi調(diào)用了多個Filter來完成驗證過程,關(guān)鍵點卻在三處:
    1.在客戶端輸入身份驗證信息,包括用戶名和密碼
    2.AuthenticationProcessingFilter取出用戶名和密碼,封裝為一個Token往后傳遞
    3.DaoAuthenticationProvider從系統(tǒng)中找出用戶資料,并和ProviderManager一起執(zhí)行驗證

    實現(xiàn)多種用戶登錄

    很明顯,要讓系統(tǒng)識別不同種類的用戶,必須設(shè)立一個用戶類型標(biāo)志。問題就轉(zhuǎn)化為:
    1.用戶在客戶端輸入身份信息時系統(tǒng)就必須設(shè)立相應(yīng)的標(biāo)志
    2.該標(biāo)志如何傳遞到DaoAuthenticationProvider
    3.DaoAuthenticationProvider如何識別該標(biāo)志,并從相應(yīng)類型的用戶中找到指定用戶

    我不打算改動Acegi的源碼,只打算擴(kuò)展出我需要的功能。

    首先在登錄頁面中加入用戶類型標(biāo)志j_userkind。在登錄頁面中加入如下代碼:

    <input type="hidden" name="j_userkind" id="j_userkind" value="0">

    其中0代碼員工,1代碼客戶。可以考慮在登錄頁面中增加一個選項,如果用戶要以員工身份登錄,則把j_userkind置為0;如果用戶要以客戶身份登錄,則把j_userkind置為1。也可以提供兩個登錄頁面,其中一個員工專用(j_userkind被強(qiáng)制置為0),另一個客戶專用(j_userkind被強(qiáng)制置為1)

    系統(tǒng)如何根據(jù)收到的用戶類型標(biāo)志去讀取指定的用戶呢?如果在代碼中寫死(比如當(dāng)用戶類型標(biāo)志=0時,讀取員工;當(dāng)用戶類型標(biāo)志=1時,讀取客戶)非常不好,還是通過配置來確定比較靈活。首先編寫UserKindComparisonAware接口:

    package cn.net.cogent.summer.extension.acegisecurity.providers;

    public
     interface UserKindComparisonAware {

        
    public void setExpectedUserKind(String expectedUserKind);
          
    public void setCurrentUserKind(String currentUserKind);

    }

    該接口說明實現(xiàn)類需要實現(xiàn)兩個方法,setExpectedUserKind用于接受一個期望的用戶類型標(biāo)志(通常該標(biāo)志通過配置來設(shè)置),setCurrentUserKind用于接受當(dāng)前登錄用戶的用戶類型標(biāo)志(系統(tǒng)在運(yùn)行時捕獲,并傳遞給實現(xiàn)類)

    編寫MKUDaoAuthenticationProvider類:

    package cn.net.cogent.summer.extension.acegisecurity.providers.dao;

    import cn.net.cogent.summer.extension.acegisecurity.BadUserKindException;
    import cn.net.cogent.summer.extension.acegisecurity.providers.UserKindComparisonAware;

    import org.acegisecurity.AuthenticationException;
    import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
    import org.acegisecurity.providers.dao.DaoAuthenticationProvider;
    import org.acegisecurity.userdetails.UserDetails;

    import cn.net.cogent.summer.util.LoggerUtil;

    public class MKUDaoAuthenticationProvider extends DaoAuthenticationProvider implements
        UserKindComparisonAware {

        
    private String expectedUserKind;
          
    private String currentUserKind;

        
    public String getExpectedUserKind() {
              
    return expectedUserKind;
        }
        
    public void setExpectedUserKind(String expectedUserKind) {
              
    this.expectedUserKind = expectedUserKind;
        }

          
    public String getCurrentUserKind() {
                
    return currentUserKind;
          }
          
    public void setCurrentUserKind(String currentUserKind) {
                
    this.currentUserKind = currentUserKind;
          }

        
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                UsernamePasswordAuthenticationToken authentication) 
    throws AuthenticationException {
            LoggerUtil.getLogger().debug(
    "expectedUserKind = '" + expectedUserKind + "', currentUserKind = '" + currentUserKind + "'");
            
    if (currentUserKind.equals(expectedUserKind))
                
    super.additionalAuthenticationChecks(userDetails, authentication);
            
    else
                
    throw new BadUserKindException(
                    
    "Flag UserKind does not match");
        }
    }

    該類繼承自DaoAuthenticationProvider并實現(xiàn)UserKindComparisonAware接口,在additionalAuthenticationChecks方法中判斷當(dāng)前登錄用戶的用戶類型標(biāo)志與期望的用戶類型標(biāo)志是否一致,如果一致則執(zhí)行父類的additionalAuthenticationChecks,完成驗證;否則拋出一個BadUserKindException異常,表明驗證失敗。BadUserKindException繼承自org.acegisecurity.AuthenticationException,具體的代碼略

    在applicationContext.xml中刪除daoAuthenticationProvider相關(guān)的配置,增加如下配置:

    <bean id="customerDaoAuthenticationProvider" class="cn.net.cogent.summer.extension.acegisecurity.providers.dao.MKUDaoAuthenticationProvider">
         
    <property name="userDetailsService" ref="customerDao"/>
         
    <property name="passwordEncoder" ref="passwordEncoder"/>
         
    <property name="expectedUserKind" value="1"/>
    </bean>

    <bean id="userDaoAuthenticationProvider" class="cn.net.cogent.summer.extension.acegisecurity.providers.dao.MKUDaoAuthenticationProvider">
         
    <property name="userDetailsService" ref="userDao"/>
         
    <property name="passwordEncoder" ref="passwordEncoder"/>
         
    <property name="expectedUserKind" value="0"/>
    </bean>

    可以看出customerDaoAuthenticationProvider僅用于驗證客戶(其expectedUserKind被指定為1),而userDaoAuthenticationProvider僅用于驗證員工(其expectedUserKind被指定為0)。customerDao的配置如下:

    <bean id="customerDao" class="cn.net.cogent.summer.extension.appfuse.dao.hibernate.CustomerDaoHibernate">
        
    <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    CustomerDaoHibernate的代碼如下:

    package cn.net.cogent.summer.extension.appfuse.dao.hibernate;

    import org.acegisecurity.userdetails.UserDetails;
    import org.acegisecurity.userdetails.UserDetailsService;
    import org.acegisecurity.userdetails.UsernameNotFoundException;

    import cn.net.cogent.summer.model.Customer;
    import org.appfuse.dao.hibernate.GenericDaoHibernate;
    import org.springframework.dao.DataAccessException;

    import java.util.List;

    public class CustomerDaoHibernate extends GenericDaoHibernate<Customer, Long> implements UserDetailsService {

        
    public CustomerDaoHibernate() {
            
    super(Customer.class);
        }

        
    public UserDetails loadUserByUsername(String username)
            
    throws UsernameNotFoundException, DataAccessException {
            List
    <Customer> users = getHibernateTemplate().find("from Customer where username=?", username);
            
    if (users == null || users.isEmpty()) {
                
    throw new UsernameNotFoundException("Customer '" + username + "' not found");
            } 
    else {
                
    return (UserDetails) users.get(0);
            }
        }
    }


    可以看出CustomerDaoHibernate是取得一個Customer對象(實現(xiàn)了UserDetails接口),而不是Employee。

    修改authenticationManager的配置如下:

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        
    <property name="providers">
            
    <list>
                
    <ref local="customerDaoAuthenticationProvider"/>
                
    <ref local="userDaoAuthenticationProvider"/>
                
    <ref local="anonymousAuthenticationProvider"/>
                
    <ref local="rememberMeAuthenticationProvider"/>
            
    </list>
        
    </property>
    </bean>

    在哪里捕獲當(dāng)前登錄用戶的用戶類型標(biāo)志,并傳遞給MKUDaoAuthenticationProvider呢?我決定增加一個名為PreAuthenticationProcessingFilter的Filter,放在AuthenticationProcessingFilter之前,代碼如下:

    package cn.net.cogent.summer.extension.acegisecurity.ui.webapp;

    import cn.net.cogent.summer.extension.acegisecurity.providers.UserKindComparisonAware;

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactoryUtils;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;

    import java.io.IOException;

    import java.util.Iterator;
    import java.util.Map;

    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;

    public class PreAuthenticationProcessingFilter implements Filter, ApplicationContextAware {

        
    public static final String ACEGI_SECURITY_FORM_USERKIND = "j_userkind";

        
    private FilterConfig filterConfig;
        
    private boolean initialized = false;
        
    private Map targetBeans;
        
    private String targetClass;
        
    private ApplicationContext applicationContext;

        
    public String getTargetClass() {
            
    return targetClass;
        }
        
    public void setTargetClass(String targetClass) {
            
    this.targetClass = targetClass;
        }

        
    public void setApplicationContext(ApplicationContext applicationContext) {
            
    this.applicationContext = applicationContext;
        }

        
    public void destroy() {
        }

        
    public void init(FilterConfig filterConfig) throws ServletException {
            
    this.filterConfig = filterConfig;
        }

        
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
                ServletException {
            
    if (!(request instanceof HttpServletRequest)) {
                
    throw new ServletException("Can only process HttpServletRequest");
            }

            
    if (!initialized) {
                doInit();
            }

            String userKind 
    = obtainUserKind((HttpServletRequest)request);
            
    for (Iterator it = targetBeans.values().iterator(); it.hasNext();) {
                 UserKindComparisonAware comparison 
    = (UserKindComparisonAware)it.next();
                 comparison.setCurrentUserKind(userKind);
            }

            chain.doFilter(request, response);
        }

        
    private synchronized void doInit() throws ServletException {
            
    if ((targetClass == null|| "".equals(targetClass)) {
                
    throw new ServletException("targetClass must be specified");
            }

            Class _targetClass;

            
    try {
                _targetClass 
    = Thread.currentThread().getContextClassLoader().loadClass(targetClass);
            } 
    catch (ClassNotFoundException ex) {
                
    throw new ServletException("Class of type " + targetClass + " not found in classloader");
            }

            targetBeans 
    = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, _targetClass, truetrue);

            
    if (targetBeans.size() == 0) {
                
    throw new ServletException("Bean context must contain at least one bean of type " + targetClass);
            }

            
    for (Iterator it = targetBeans.entrySet().iterator(); it.hasNext();) {
                  Map.Entry entry 
    = (Map.Entry)it.next();
                    
    if (!(entry.getValue() instanceof UserKindComparisonAware)) {
                        
    throw new ServletException("Bean '" + entry.getKey() +
                            
    "' does not implement cn.net.cogent.summer.extension.acegisecurity.providers.UserKindComparisonAware");
                    }
            }

            
    // Set initialized to true at the end of the synchronized method, so
            
    // that invocations of doFilter() before this method has completed will not
            
    // cause NullPointerException
            initialized = true;
        }

        
    protected String obtainUserKind(HttpServletRequest request) {
            
    return request.getParameter(ACEGI_SECURITY_FORM_USERKIND);
        }
    }

    PreAuthenticationProcessingFilter需要在初始化參數(shù)中指定targetClass,該參數(shù)的值是一個類,該類實現(xiàn)了UserKindComparisonAware接口。PreAuthenticationProcessingFilter找到容器中所有該類的實例,并把捕獲的當(dāng)前登錄用戶的用戶類型標(biāo)志賦值給它們。PreAuthenticationProcessingFilter的配置如下:

    <bean id="preAuthenticationProcessingFilter"
        class
    ="cn.net.cogent.summer.extension.acegisecurity.ui.webapp.PreAuthenticationProcessingFilter">
        
    <property name="targetClass"
            value
    ="cn.net.cogent.summer.extension.acegisecurity.providers.dao.MKUDaoAuthenticationProvider"/>
    </bean>

    還需要把preAuthenticationProcessingFilter加入到filterChainProxy的配置中:

    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        
    <property name="filterInvocationDefinitionSource">
            
    <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /**=,preAuthenticationProcessingFilter,authenticationProcessingFilter,
            
    </value>
        
    </property>
    </bean>


    注意把它放在authenticationProcessingFilter的前面

    至此我們初步實現(xiàn)了使用Acegi實現(xiàn)多種用戶登錄
    posted on 2007-09-18 22:19 雨奏 閱讀(5210) 評論(8)  編輯  收藏

    FeedBack:
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2007-09-19 12:02 千里冰封
    就為了一個登錄,這樣配置有點復(fù)雜了吧:)  回復(fù)  更多評論
      
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2007-09-19 13:25 雨奏
    @千里冰封
    請問怎樣配置會更好呢?能簡要說說你的辦法嗎?  回復(fù)  更多評論
      
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2007-09-19 16:39 西濱
    實現(xiàn)多種用戶登錄倒不難,難的是有了多種用戶(像本文的員工和客戶)之后,怎么處理不同用戶的角色、權(quán)限?  回復(fù)  更多評論
      
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2007-09-19 21:39 雨奏
    @西濱
    我倒是覺得處理角色和權(quán)限不難。原本系統(tǒng)中員工的角色、權(quán)限是如何授予的,客戶的角色、權(quán)限可以用類似的方法處理  回復(fù)  更多評論
      
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2007-09-20 11:30 Java初心
    acegi的dao驗證本來就支持USERROLE的吧

    <bean id="jdbcDaoImpl"
    class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
    <property name="dataSource">
    <ref bean="dataSource" />
    </property>
    <property name="usersByUsernameQuery">
    <value>
    SELECT USERID, PASSWORD,1 FROM T_USER_ROLE
    WHERE USERID=?
    </value>
    </property>
    <property name="authoritiesByUsernameQuery">
    <value>
    SELECT USERID,USERROLE FROM T_USER_ROLE WHERE
    USERID=?
    </value>
    </property>
    </bean>  回復(fù)  更多評論
      
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2007-11-05 09:38 Aspen
    多種用戶登錄,用角色來區(qū)分吧  回復(fù)  更多評論
      
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2008-10-06 17:52 汪洋
    好復(fù)雜啊,有沒有簡單一點的  回復(fù)  更多評論
      
    # re: 拋磚引玉-使用Acegi實現(xiàn)多種用戶登錄的一種方案 2009-08-11 11:16 天好冷
    看的出,樓主是leader級別的人物。

    深表欽佩  回復(fù)  更多評論
      

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 国产色无码精品视频免费| 波多野结衣在线免费观看| 亚洲AV无码1区2区久久| 免费黄色福利视频| 黄页视频在线观看免费| 亚洲一区影音先锋色资源| 国产一精品一AV一免费孕妇 | 免费无遮挡无码视频在线观看| 亚洲无线码一区二区三区| 999在线视频精品免费播放观看| 亚洲AV女人18毛片水真多| 亚洲日韩一页精品发布| 男人的好看免费观看在线视频 | 国产一级在线免费观看| 亚洲国产日产无码精品| 亚洲一区精品伊人久久伊人| 18以下岁毛片在免费播放| 日韩在线观看免费| 亚洲免费视频网址| 国产亚洲免费的视频看 | 亚洲国产成人精品无码区在线秒播 | 亚洲人成网站看在线播放| 久久亚洲av无码精品浪潮| 成人超污免费网站在线看| 97无码人妻福利免费公开在线视频| 亚洲精品动漫免费二区| 亚洲精品中文字幕麻豆| 国产AV无码专区亚洲AV手机麻豆| 全免费A级毛片免费看网站| 18禁无遮挡无码国产免费网站| WWW国产成人免费观看视频| 亚洲中文字幕久久精品蜜桃| 亚洲一区二区三区高清| 国产亚洲美日韩AV中文字幕无码成人| 在线免费观看一区二区三区| 亚洲精品在线免费观看视频| 无码精品人妻一区二区三区免费看 | 中文字幕视频免费在线观看| 国产AV无码专区亚洲AV蜜芽| 亚洲av永久无码嘿嘿嘿| 久久国产亚洲高清观看|