国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

SpringBoot整合OAuth2實現單點登錄

開發 前端
客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式并不屬于OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。

關于OAuth2不做介紹了,網絡太多了。

環境:2.4.12 + OAuth2 + Redis

redis用來實現token的存儲。

  • pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security.oauth.boot</groupId>
  <artifactId>spring-security-oauth2-autoconfigure</artifactId>
  <version>2.2.11.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
  <groupId>net.sourceforge.nekohtml</groupId>
  <artifactId>nekohtml</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • application.yml
server:
  port: 8208
---
spring:
  application:
    name: oauth-server
---
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 1
    lettuce:
      pool:
        maxActive: 8
        maxIdle: 100
        minIdle: 10
        maxWait: -1
---
spring:
  resources:
    staticLocations: classpath:/static/,classpath:/templates/,classpath:/pages/
  mvc:
    staticPathPattern: /resources/**
---
spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
    username: root
    password: 123456
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 10
      maximumPoolSize: 200
      autoCommit: true
      idleTimeout: 30000
      poolName: MasterDatabookHikariCP
      maxLifetime: 1800000
      connectionTimeout: 30000
      connectionTestQuery: SELECT 1    
  jpa:
    hibernate:
      ddlAuto: update
    showSql: true
    openInView: true #Open EntityManager in View
---
spring:
  thymeleaf:
    servlet:
      contentType: text/html; charset=utf-8 
    cache: false
    mode: LEGACYHTML5
    encoding: UTF-8
    enabled: true
    prefix: classpath:/pages/
    suffix: .html
---
spring:
  main:
    allow-bean-definition-overriding: true
  • 實體
@Entity
@Table(name = "T_APP")
public class App implements Serializable {


  private static final long serialVersionUID = 1L ;
  @Id
  @GeneratedValue(generator = "system-uuid")
  @GenericGenerator(name = "system-uuid", strategy = "uuid")
  private String id ;
  /**
   * 客戶端ID
   */
  private String clientId ;
  /**
   * 客戶端密鑰
   */
  private String clientSecret ;
  /**
   * 跳轉地址
   */
  private String redirectUri ;
}
// 該實體用來存在每個應用的信息。
@Entity
@Table(name = "T_USERS")
public class Users implements UserDetails, Serializable {


  private static final long serialVersionUID = 1L;


  @Id
  @GeneratedValue(generator = "system-uuid")
  @GenericGenerator(name = "system-uuid", strategy = "uuid")
  private String id ;
  private String username ;
  private String password ;
}
// 該實體是用戶登錄信息。
  • DAO類
// 提供了一個方法,根據clientId獲取客戶端信息。
public interface AppRepository extends JpaRepository<App, String>, JpaSpecificationExecutor<App> {




  App findByClientId(String clientId) ;
  
}
public interface UsersRepository extends JpaRepository<Users, String>, JpaSpecificationExecutor<Users> {


  Users findByUsernameAndPassword(String username, String password) ;
  
}
  • 核心配置類

重要代碼已經加了注釋說明

@Configuration
@EnableAuthorizationServer
public class OAuthAuthorizationConfig extends AuthorizationServerConfigurerAdapter {


  @Resource
  private AppRepository appRepository ;
  @Resource
  private RedisConnectionFactory redisConnectionFactory ;
  @Resource
  private AuthenticationManager authenticationManager;
  
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(clientDetailsService());
  }
  
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security)
      throws Exception {
   security.tokenKeyAccess("permitAll()") // isAuthenticated()
     .checkTokenAccess("permitAll()") // 允許訪問 /oauth/check_token 接口
     .allowFormAuthenticationForClients() ;
  }
  
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints)
      throws Exception {
    // 自定義CODE
    endpoints.authorizationCodeServices(new InMemoryAuthorizationCodeServices() {
    @Override
    public String createAuthorizationCode(OAuth2Authentication authentication) {
      String code = UUID.randomUUID().toString().replaceAll("-", "") ;
      store(code, authentication) ;
      return code;
    }
  }) ;
    endpoints.exceptionTranslator(new DefaultWebResponseExceptionTranslator() {
      @SuppressWarnings({ "unchecked", "rawtypes" })
      @Override
      public ResponseEntity translate(Exception e) throws Exception {
        ResponseEntity<OAuth2Exception> responseEntity = super.translate(e) ;
        ResponseEntity<Map<String, Object>> customEntity = exceptionProcess(responseEntity);
        return customEntity ;
      }
    }) ;
    // 要想使用密碼模式這個步驟不能少,否則默認情況下的只支持除密碼模式外的其它4中模式
    endpoints.authenticationManager(authenticationManager) ;
    /**
     * 如果重新定義了TokenServices 那么token有效期等信息需要重新定義
     * 這時候在ClientDetailsServiceConfigurer中設置的有效期將會無效
     */
    endpoints.tokenServices(tokenService()) ; // 生成token的服務
    endpoints.allowedTokenEndpointRequestMethods(HttpMethod.values()) ; // 獲取token 時 允許所有的方法類型
    endpoints.accessTokenConverter(defaultTokenConvert()); // token生成方式
    endpoints.tokenStore(tokenStore()) ;
    endpoints.pathMapping("/oauth/error", "/oauth/customerror") ;
    // endpoints.addInterceptor(new XXXX()) ; // 在這里可以配置攔截器
    endpoints.requestValidator(new OAuth2RequestValidator() {
      @Override
      public void validateScope(AuthorizationRequest authorizationRequest, ClientDetails client)
          throws InvalidScopeException {
        //logger.info("放行...") ;
      }
      @Override
      public void validateScope(TokenRequest tokenRequest, ClientDetails client)
          throws InvalidScopeException {
        //logger.info("放行...") ;
      }
          
    }) ;
    endpoints.approvalStore(new InMemoryApprovalStore()) ;
  }
  
  @Bean
  public ClientDetailsService clientDetailsService() {
    return (clientId) -> {
      if (clientId == null) {
        throw new ClientRegistrationException("未知的客戶端: " + clientId) ;
      }
      App app = appRepository.findByClientId(clientId) ;
      if (app == null) {
        throw new ClientRegistrationException("未知的客戶端: " + clientId) ;
      }
      // 因為每一個客戶端都可以對應多個認證授權類型,跳轉URI等信息,這里為了簡單就為每一個客戶端固定了這些信息
      OAuthClientDetails clientDetails = new OAuthClientDetails() ;
      clientDetails.setClientId(clientId) ;
      clientDetails.setClientSecret(app.getClientSecret()) ;
      Set<String> registeredRedirectUri = new HashSet<>() ;
      registeredRedirectUri.add(app.getRedirectUri()) ;
      clientDetails.setRegisteredRedirectUri(registeredRedirectUri);
      clientDetails.setScoped(false) ;
      clientDetails.setSecretRequired(true) ;
      clientDetails.setScope(new HashSet<String>());
      Set<String> authorizedGrantTypes = new HashSet<>() ;
      authorizedGrantTypes.add("authorization_code") ;
      authorizedGrantTypes.add("implicit") ;
      authorizedGrantTypes.add("password") ;
      authorizedGrantTypes.add("refresh_token") ;
      authorizedGrantTypes.add("client_credentials") ;
      clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);
      Collection<GrantedAuthority> authorities = new ArrayList<>() ;
      clientDetails.setAuthorities(authorities) ;
      return clientDetails ;
    } ;
  }
    
  // 如下Bean可用來增加獲取Token時返回信息(需要在TokenServices中增加)
  @Bean
  public TokenEnhancer tokenEnhancer(){
    return new TokenEnhancer() {
      @Override
      public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        System.out.println(authentication) ;
        if (accessToken instanceof DefaultOAuth2AccessToken){
          DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
          Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
          additionalInformation.put("username", ((Users)authentication.getPrincipal()).getUsername());
          additionalInformation.put("create_time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
          token.setAdditionalInformation(additionalInformation);
        }
        return accessToken;
      }
    };
  }
    
  @Bean
  @Primary
  public AuthorizationServerTokenServices tokenService() {
    DefaultTokenServices tokenService = new DefaultTokenServices() ;
    tokenService.setSupportRefreshToken(true) ; // 如果不設置返回的token 將不包含refresh_token
    tokenService.setReuseRefreshToken(true) ;
    tokenService.setTokenEnhancer(tokenEnhancer()); // 在這里設置JWT才會生效
    tokenService.setTokenStore(tokenStore()) ;
    tokenService.setAccessTokenValiditySeconds(60 * 60 * 24 * 3) ; // token有效期
    tokenService.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7) ; // 30 * 24 * 60 * 60;刷新token (必須在token沒有過期前使用)
    return tokenService ;
  }
    
    
  @Bean
  public TokenStore tokenStore() {
    TokenStore tokenStore = null ;
    tokenStore = new RedisTokenStore(redisConnectionFactory) ;
    return tokenStore ;
  }


  @Bean 
  public DefaultAccessTokenConverter defaultTokenConvert() {
    DefaultAccessTokenConverter defaultTokenConvert = new DefaultAccessTokenConverter() ;
    return defaultTokenConvert ;
  }
  
  private static ResponseEntity<Map<String, Object>> exceptionProcess(
        ResponseEntity<OAuth2Exception> responseEntity) {
    Map<String, Object> body = new HashMap<>() ;
    body.put("code", -1) ;
    OAuth2Exception excep = responseEntity.getBody() ;
    String errorMessage = excep.getMessage();
    if (errorMessage != null) {
      errorMessage = "認證失敗,非法用戶" ;
      body.put("message", errorMessage) ;
    } else {
      String error = excep.getOAuth2ErrorCode();
      if (error != null) {
        body.put("message", error) ;
      } else {
        body.put("message", "認證服務異常,未知錯誤") ;
      }
    }
    body.put("data", null) ;
    ResponseEntity<Map<String, Object>> customEntity = new ResponseEntity<>(body, 
    responseEntity.getHeaders(), responseEntity.getStatusCode()) ;
    return customEntity;
  }  
    
}
  • 暴露一個AuthenticationManager類

密碼模式必須設置對應的AuthenticationManager,所以這里必須暴露出來,否則系統找不到。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
  @Override
  @Bean
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }
}
  • 自定義ClientDetails

該類主要是用在配置類中定義 ClientDetailsService是為了簡化使用的。如下圖:

圖片圖片

這里就是為了獲取當前客戶端的所有信息使用。

public class OAuthClientDetails implements ClientDetails,Serializable {


  private static final long serialVersionUID = 1L;


  private String id ;
  
  private String clientId ;
  
  private boolean secretRequired ;
  
  private String clientSecret ;
  
  private boolean scoped ;
  
  private Set<String> resourceIds ;
  
  private Set<String> scope = new HashSet<>();
  
  private Set<String> authorizedGrantTypes = new HashSet<>();
  
  private Set<String> registeredRedirectUri = new HashSet<>();
  
  private Collection<GrantedAuthority> authorities ;
  
  private boolean autoApprove ;
  
  private Integer accessTokenValiditySeconds ;
  
  private Integer refreshTokenValiditySeconds ;
}
  • 登錄認證類
@Component
public class LoginAuthenticationProvider implements AuthenticationProvider {
  
  @Resource
  private UsersRepository usersRepository ;


  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    // 登錄用戶名
    String username = authentication.getName() ;
    // 憑證(密碼)
    Object credentials = authentication.getCredentials() ;
    Users user = null ;
    try {
      user = usersRepository.findByUsernameAndPassword(username, (String) credentials) ;
      if (user == null) {
        String errorMsg = "錯誤的用戶名或密碼" ;
        throw new BadCredentialsException(errorMsg) ;
      }
    } catch (Exception e) {
      throw e ;
    }
    UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
      user, authentication.getCredentials(), Arrays.asList(
          new SimpleGrantedAuthority("ROLE_USERS"),
          new SimpleGrantedAuthority("ROLE_ACTUATOR")));
          result.setDetails(authentication.getDetails());
          return result;
      }
  
    @Override
    public boolean supports(Class<?> authentication) {
      return (UsernamePasswordAuthenticationToken.class
        .isAssignableFrom(authentication));
    }
  
}
  • 密碼驗證
@Component
public class LoginPasswordEncoder implements PasswordEncoder {
  
  @Override
  public String encode(CharSequence rawPassword) {
    return rawPassword.toString() ;
  }


  @Override
  public boolean matches(CharSequence rawPassword, String encodedPassword) {
    return this.encode(rawPassword).equals(encodedPassword) ;
  }


}

注意:

Users實體類為啥要實現UserDetails?

應該我們在存儲token相關信息到redis時需要有對應key的生成方式。

RedisTokenStore.java中有個默認的key生成方式:

圖片圖片


圖片圖片

進入上面的方法中:

圖片圖片

進入getName方法中:

圖片圖片

最終它會調用紅色框中的代碼,這樣就出現一個問題,你每次獲取token的時候都會生成一個新的token。所以這里我們的Users實體實現了UserDetails接口。

圖片圖片

這里是通過debug查看

到此整合完畢了!

測試:

先造兩條數據:

圖片圖片


圖片圖片

  • 授權碼模式

授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后臺服務器,與"服務提供商"的認證服務器進行互動。

請求地址請求地址


圖片圖片

訪問上面地址后跳轉到了登錄頁面

輸入正確的用戶名密碼后:

圖片圖片

成功后跳到了我們配置的跳轉地址,這時候我們就可以根據地址欄的code獲取token了:

圖片圖片

注意:這里的code是一次性的,也就是說如果使用過了就會自動失效。

  • 密碼模式

密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己的用戶名和密碼。客戶端使用這些信息,向"服務商提供商"索要授權。

在這種模式中,用戶必須把自己的密碼給客戶端,但是客戶端不得儲存密碼。這通常用在用戶對客戶端高度信任的情況下,比如客戶端是操作系統的一部分,或者由一個著名公司出品。而認證服務器只有在其他授權模式無法執行的情況下,才能考慮使用這種模式。

請求地址

圖片圖片

  • 客戶端模式

客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式并不屬于OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。

圖片圖片

  • 簡化模式

簡化模式(implicit grant type)不通過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,跳過了"授權碼"這個步驟,因此得名。所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認證。

圖片圖片

簡化模式的流程,這樣有助于理解

(A)客戶端將用戶導向認證服務器。

(B)用戶決定是否給予客戶端授權。

(C)假設用戶給予授權,認證服務器將用戶導向客戶端指定的"重定向URI",并在URI的Hash部分包含了訪問令牌。

(D)瀏覽器向資源服務器發出請求,其中不包括上一步收到的Hash值。

(E)資源服務器返回一個網頁,其中包含的代碼可以獲取Hash值中的令牌。

(F)瀏覽器執行上一步獲得的腳本,提取出令牌。

(G)瀏覽器將令牌發給客戶端。

  • 刷新令牌

如果用戶訪問的時候,客戶端的"訪問令牌"過期前,可以申請一個新的訪問令牌。

圖片圖片

這里的refresh_token就是在獲取token的時候返回的。

責任編輯:武曉燕 來源: Spring全家桶實戰案例源碼
相關推薦

2023-08-31 08:34:07

Users對象序列化

2022-04-11 07:34:46

OAuth2UAA節點

2022-05-12 07:37:51

單點登錄微服務開源

2024-06-20 08:20:27

2025-06-26 04:11:00

SpringSecurityOAuth2

2013-05-02 14:13:44

Android開發OAuth2服務認證

2017-08-04 18:10:09

2025-05-06 00:58:00

ServerlessOAuth2Token

2021-08-02 12:50:45

sessiontokenJava

2025-04-29 09:07:21

2021-08-29 23:33:44

OAuth2服務器Keycloak

2025-01-13 08:04:24

2025-04-16 08:00:00

FastAPIJWT用戶認證

2021-11-15 13:58:00

服務器配置授權

2025-08-12 07:24:54

2020-12-28 05:52:27

SSO登錄單點

2016-12-26 18:05:00

單點登錄原理簡單實現

2021-07-02 10:45:53

SpringBootCAS登錄

2022-08-15 08:34:08

OauthCAS登錄

2024-06-21 09:28:43

點贊
收藏

51CTO技術棧公眾號

国产日产欧美精品一区二区三区| 久热成人在线视频| 亚洲午夜国产成人av电影男同| 制服丝袜在线播放| 精品一区二区三区中文字幕 | 欧美午夜电影在线观看| 欧美这里只有精品| 婷婷丁香久久五月婷婷| 台湾佬成人网| 97视频中文字幕| 久久一区二区视频| 天天色天天射天天综合网| 国内精品视频一区| 久久97超碰国产精品超碰| 青青草娱乐在线| 欧美激情亚洲视频| 久久午夜影视| 性欧美精品孕妇| 久精品免费视频| 激情综合色综合久久| 国产高清av在线| 欧美性受xxxx黑人猛交| 成人av一区二区三区| 久热国产在线| 91精品在线看| 亚洲欧洲韩国日本视频 | 国产精品视频资源| 久久一区二区三区国产精品| av剧情在线观看| 91手机在线视频| 免费男女羞羞的视频网站主页在线观看 | 成人免费视频网站| 亚洲精品中文在线影院| 97久久精品人人做人人爽50路| 在线看视频你懂得| 午夜精品三级视频福利| 成人性生交大片免费看中文网站| av电影高清在线观看| 不卡一区二区三区视频| 亚洲一级不卡视频| 网友自拍一区| 亚洲福利精品视频| 欧美人在线视频| 99国产欧美另类久久久精品| 美女100%一区| 中文字幕一区二区三区有限公司 | 激情久久av一区av二区av三区| 老司机精品在线| 国产三区在线视频| 中文字幕不卡在线视频极品| 久久精品999| 国产黄色大片在线观看| 日韩久久不卡| 精品国产精品网麻豆系列| 国产精品嫩草99av在线| 久cao在线| 久久久福利视频| 91精品久久久久久久91蜜桃| 亚洲欧美日韩视频二区| а√天堂官网中文在线| 日本一区网站| 日韩国产高清视频在线| 7m第一福利500精品视频| 在线免费观看不卡av| 成人午夜电影小说| 亚洲精品国产精品粉嫩| 可以在线观看的av| 麻豆av福利av久久av| 久热精品视频在线| 欧美精品v国产精品v日韩精品| 日韩福利视频网| 校园春色欧美| 91精品网站| 欧美日韩一级片网站| 亚洲欧美日韩国产一区| av中文字幕在线看| 国产 国语对白 露脸| 日韩美女一区二区三区在线观看| 99精品中文字幕在线不卡| 国产欧美日韩亚州综合| 成人高清免费观看mv| 97人洗澡人人免费公开视频碰碰碰| 99免费精品在线观看| 欧美天堂一区| 黄色a级片免费| 欧美激情高清视频| 亚洲私人影院在线观看| 久久爱www成人| 色视频网站在线| 国产免费一区二区三区在线能观看 | 午夜影院欧美| 国产精品一区二区三区av| 特黄特黄的视频| 亚洲精品永久www嫩草| 国产精品美女久久久久久2018| 影视先锋久久| 国产精品原创视频| 亚洲精品在线免费| 久久精品国内一区二区三区| 男人av在线播放| 欧美日韩性生活片| 人体精品一二三区| 欧美性一区二区| 国内精品久久久久影院色| 国产精品一级在线观看| 中文字幕中文字幕在线中文字幕三区| 国产一区二区三区奇米久涩| 亚洲性日韩精品一区二区| 中文字幕中文乱码欧美一区二区| 欧美精品91| 欧洲av一区二区| 国产美女在线播放| 亚洲一区二区三区精品动漫| 国模私拍视频一区| 欧美区在线观看| 99久久99精品久久久久久| 久久国产影院| 性爽视频在线| 丝袜美女写真福利视频| 天堂社区 天堂综合网 天堂资源最新版 | 国产精品免费久久久久| 欧美.日韩.国产.一区.二区| 自拍视频在线看| 97影院在线观看| 一个色的综合| 国产精品青青在线观看爽香蕉| 欧美www视频| 在线欧美成人| 八戒八戒神马在线电影| 丰满的护士2在线观看高清| 婷婷视频在线| 国产激情视频在线看| 精品网站在线| 哺乳挤奶一区二区三区免费看| 亚洲bt欧美bt精品777| 亚洲欧美大片| 影音先锋日韩有码| 精品久久香蕉国产线看观看亚洲 | 在线视频中文亚洲| 欧美日韩精品在线视频| 懂色av一区二区夜夜嗨| 97视频精品| 99视频这里有精品| 久久综合伊人77777麻豆| 欧美高清一区二区| 热久久免费视频精品| 97免费资源站| 精品久久久久久久久久久aⅴ| 欧美13一16娇小xxxx| 免费99热在线观看| 一区二区日本伦理| 国产色婷婷国产综合在线理论片a| 中文字幕日韩欧美在线视频| 色欧美88888久久久久久影院| 国产香蕉久久精品综合网| 丝袜亚洲精品中文字幕一区| 成人精品影院| 996久久国产精品线观看| 黄a在线观看| 麻豆传媒在线视频| 欧美日韩在线成人| 国产精品波多野结衣| 91青青草免费观看| 91a在线视频| 久久精品人人做人人爽| 亚洲国产精品中文| 国产aⅴ精品一区二区四区| 蜜臀精品久久久久久蜜臀| 色一情一乱一乱一91av| 久久精品视频播放| 日韩av高清| 情趣网站视频在线观看| 成人午夜888| 国产一二精品视频| 欧美日韩一区中文字幕| 国产欧美精品va在线观看| 99精品欧美一区二区三区综合在线| 在线观看视频免费一区二区三区| 国产探花一区| aaa国产精品| 一区二区网站| 国产精久久久| 亚洲狼人综合| 亚洲精品自拍| 国产专区精品| 经典三级久久| 亚洲一二av| 欧美视频精品全部免费观看| 日本免费在线一区| 992tv国产精品成人影院| 久久野战av| 在线天堂资源| 四虎4545www精品视频| 韩国精品主播一区二区在线观看| 在线看片福利| 日本高清不卡一区二区三区视频| 新版的欧美在线视频| 欧美调教sm| 色综合一本到久久亚洲91| 国模一区二区|