<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      SpringBoot實戰:Spring Boot接入Security權限認證服務

      引言

      Spring Security 是一個功能強大且高度可定制的身份驗證和訪問控制的框架,提供了完善的認證機制和方法級的授權功能,是一個非常優秀的權限管理框架。其核心是一組過濾器鏈,不同的功能經由不同的過濾器。本文將通過一個案例將 Spring Security 整合到 SpringBoot中,要實現的功能就是在認證服務器上登錄,然后獲取Token,再訪問資源服務器中的資源。

      一、基本介紹

      登錄驗證:

      通過 JWT 為每個用戶生成一個唯一且有期限的 Token,用戶每次請求都會重新生成過期時間,在規定的時間內,用戶未進行操作 Token 就會過期,當用戶再次請求時則會再次執行登錄流程,而 Token 的過期時間應根據實際的業務場景規定。

      權限認證:

      權限認證通過Spring Security框架來實現,在用戶成功登錄之后,當嘗試訪問系統資源時(即發起接口調用),服務端會根據用戶所屬的角色來判斷其是否具備相應的訪問權限。若用戶未獲得該資源的訪問權限,則服務端應當返回明確的權限不足提示信息,以確保系統的安全性與用戶體驗。

      通過如圖來講解我們的實現目標:登錄驗證權限認證
      image

      二、環境準備

      創建 auth_user 系統用戶表,并準備測試數據。

      CREATE TABLE `auth_user`
      (
      	`id`                      varchar(36) NOT NULL,
      	`username`                varchar(100) DEFAULT NULL,
      	`password`                varchar(100) DEFAULT NULL,
      	`role`                    varchar(100) DEFAULT NULL,
      	`account_non_expired`     int(11) DEFAULT '0',
      	`account_non_locked`      int(11) DEFAULT '0',
      	`credentials_non_expired` int(11) DEFAULT '0',
      	`is_enabled`              int(11) DEFAULT NULL,
      	PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf32;
      
      
      
      INSERT INTO auth_user (id, username, password, `role`, account_non_expired, account_non_locked,
      					   credentials_non_expired, is_enabled)
      VALUES ('1', 'user', '15tT+y0b+lJq2HIKUjsvvg==', 'USER', 1, 1, 1, 1),
      	   ('2', 'admin', '15tT+y0b+lJq2HIKUjsvvg==', 'ADMIN', 1, 1, 1, 1);
      

      三、登錄代碼實現

      1.為項目導入相關依賴

      pom.xml 文件中到入依賴,除了 Security 之外 還引入了 AES JWT相關依賴

      <dependencies>
      	<dependency>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-security</artifactId>
      	</dependency>
      	<!-- AES加密 -->
      	<dependency>
      		<groupId>org.apache.directory.studio</groupId>
      		<artifactId>org.apache.commons.codec</artifactId>
      		<version>1.8</version>
      	</dependency>
      	<!-- JWT -->
      	<dependency>
      		<groupId>io.jsonwebtoken</groupId>
      		<artifactId>jjwt</artifactId>
      		<version>0.9.0</version>
      	</dependency>
      </dependencies>
      

      創建項目所需實體類:

      在工程中創建一個新的實體類AuthUser,該實體類需要實現Spring SecurityUserDetails接口,并特別地,需要重寫getAuthorities()方法來從數據庫中動態讀取并設置用戶的角色權限。此外,為了確保用戶賬戶處于正常激活狀態,isAccountNonExpired()isAccountNonLocked()isCredentialsNonExpired()isEnabled()這四個方法也必須被重寫,并且應該基于數據庫查詢的結果或業務邏輯,無條件地返回true(假設在這個場景下,所有用戶賬戶都被視為有效、未過期、未鎖定且憑據未過期)。

      這樣的設計確保了AuthUser類能夠準確地反映用戶的安全狀態和權限信息,同時允許Spring Security框架利用這些信息進行訪問控制。通過從數據庫動態加載權限信息,系統能夠靈活地適應不同用戶的權限需求,提升系統的安全性和靈活性。

      public class AuthUser implements Serializable, UserDetails {
      
      	private static final long serialVersionUID = 1L;
      
      	private String id;
      
      	private String username;
      
      	private String password;
      
      	private String role;
      
      	private Integer accountNonExpired;
      
      	private Integer accountNonLocked;
      
      	private Integer credentialsNonExpired;
      
      	private Integer isEnabled;
      
      	@Override
      	public Collection<? extends GrantedAuthority> getAuthorities() {
      		// 獲取用戶所有權限
      		String[] roles = role.split(",");
      		// 遍歷 roles,取出每一個權限進行認證,添加到簡單的授予認證類
      		List<SimpleGrantedAuthority> authorities = new ArrayList<>();
      		for (String role : roles) {
      			authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
      		}
      		// 返回到已經被授予認證的權限集合, 這里面的角色所擁有的權限都已經被 spring security 所知道
      		return authorities;
      	}
      
      	@Override
      	public boolean isAccountNonExpired() {
      		return this.accountNonExpired != null && this.accountNonExpired == 1;
      	}
      
      	@Override
      	public boolean isAccountNonLocked() {
      		return this.accountNonLocked != null && this.accountNonLocked == 1;
      	}
      
      	@Override
      	public boolean isCredentialsNonExpired() {
      		return this.credentialsNonExpired != null && this.credentialsNonExpired == 1;
      	}
      
      	@Override
      	public boolean isEnabled() {
      		return this.isEnabled != null && this.isEnabled == 1;
      	}
      
      	// 略去其它 Get、Set 方法
      }
      

      創建 Service 服務

      創建名為 AuthUserService 的接口,并實現 UserDetailsService 類,重寫 loadUserByUsername() 方法( Security 認證登錄調用的接口)。

      public interface AuthUserService extends UserDetailsService {
      
      }
      
      @Service("authUserService")
      public class AuthUserServiceImpl implements AuthUserService {
      
      	@Resource
      	private AuthUserDao authUserDao;
      
      	@Override
      	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      		AuthUser authUser = authUserDao.queryByName(username);
      		if (authUser == null) {
      			throw new IllegalArgumentException("User [" + username + "] doesn't exist.");
      		}
      		return authUser;
      	}
      }
      

      AutUserDao 是用來解讀數據庫信息的類, queryByName() 是通過 usernameauth_user 數據表進行精準查詢。

      Congtroller 層方法

      創建兩個接口分別供不同角色測試。

      @RestController
      @RequestMapping("api/resource")
      public class ResourceController {
      
      	@GetMapping("user")
      	public String demo1() {
      		return "User demo.";
      	}
      
      	@GetMapping("admin")
      	public String demo2() {
      		return "Admin demo.";
      	}
      }
      

      四、工具類

      AES加密

      在前后端數據傳輸過程中明文密碼傳輸存在相當大的隱患,可以采用加密的方式,對信息進行隱藏,話不多說上代碼。

      public class AESUtil {
      
      	private final static String ALGORITHM = "AES/CBC/NoPadding";
      	private final static String DEFAULT_IV = "1234567890123456";
      	private final static String DEFAULT_KEY = "1234567890123456";
      
      	public static String encrypt(String data) throws Exception {
      		return encrypt(data, DEFAULT_KEY, DEFAULT_IV);
      	}
      
      	public static String desEncrypt(String data) throws Exception {
      		return desEncrypt(data, DEFAULT_KEY, DEFAULT_IV);
      	}
      
      	public static String encrypt(String data, String key, String iv) throws Exception {
      		Cipher cipher = Cipher.getInstance(ALGORITHM);
      		int blockSize = cipher.getBlockSize();
      		byte[] dataBytes = data.getBytes();
      		int length = dataBytes.length;
      		if (length % blockSize != 0) {
      			length = length + (blockSize - (length % blockSize));
      		}
      		byte[] plaintext = new byte[length];
      		System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
      		SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
      		IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
      		cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
      	 	byte[] encrypted = cipher.doFinal(plaintext);
      		return new Base64().encodeToString(encrypted);
      	}
      
      	public static String desEncrypt(String data, String key, String iv) throws Exception {
      		byte[] encrypted1 = new Base64().decode(data);
      		Cipher cipher = Cipher.getInstance(ALGORITHM);
      		SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
      		IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
      		cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
      		byte[] bytes = cipher.doFinal(encrypted1);
      		return new String(bytes);
      	}
      }
      

      JWT生成

      通過引入JWT(JSON Web Tokens),我們可以高效地管理用戶的登錄狀態。JWT能夠生成一串包含過期時間的Token值,該值以字符串形式存在。當Token達到其設定的過期時間時,嘗試對其進行解析將會觸發ExpiredJwtException異常。通過捕獲這個ExpiredJwtException異常,我們能夠有效地判斷用戶的登錄狀態是否已經過期。在上述描述中,createJWT()函數負責生成Token,而parseJWT()函數則負責解析Token。這樣的機制既方便了Token的生成與管理,也簡化了用戶登錄狀態的驗證過程。

      public class TokenUtil {
      
      	/**
      	 * 密鑰
      	 */
      	public static final String JWT_KEY = "ibudai";
      	/**
      	 * 過期時間
      	 */
      	public static final Long JWT_TTL = TimeUnit.MINUTES.toMillis(5);
      
      	/**
      	 * 生成 Token
      	 */
      	public static String createJWT(String data, Long ttlMillis) {
      		String uuid = UUID.randomUUID().toString().replaceAll("-", "");
      		JwtBuilder builder = getJwtBuilder(data, ttlMillis, uuid);
      		return builder.compact();
      	}
      
      	/**
      	 * 解析 Token
      	 */
      	public static Claims parseJWT(String token) {
      		SecretKey secretKey = generalKey();
      		return Jwts.parser()
      				.setSigningKey(secretKey)
      				.parseClaimsJws(token)
      				.getBody();
      	}
      
      	/**
      	 * 生成加密后的秘鑰
      	 */
      	private static SecretKey generalKey() {
      		byte[] encodedKey = Base64.getDecoder().decode(JWT_KEY);
      		return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
      	}
      
      	private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
      		SignatureAlgorithm algorithm = SignatureAlgorithm.HS256;
      		SecretKey secretKey = generalKey();
      		long nowMillis = System.currentTimeMillis();
      		Date now = new Date(nowMillis);
      		if (ttlMillis == null) {
      			ttlMillis = JWT_TTL;
      		}
      		long expMillis = nowMillis + ttlMillis;
      		Date expDate = new Date(expMillis);
      		return Jwts.builder()
      				.setId(uuid)
      				// 計算內容
      				.setSubject(subject)
      				// 簽發者
      				.setIssuer("budai")
      				// 簽發時間
      				.setIssuedAt(now)
      				// 加密算法簽名
      				.signWith(algorithm, secretKey)
      				.setExpiration(expDate);
      	}
      }
      

      五、權限配置

      接下來正式配置 Security 權限模塊。
      新建SecurityConfig類,并使其繼承自WebSecurityConfigurerAdapter,隨后在該類中重寫configure(AuthenticationManagerBuilder auth)方法。在這個方法內部,我們將利用AuthUserService(即之前創建的用于從數據庫中讀取用戶角色數據的類)來配置用戶認證信息。這樣的配置確保了Spring Security能夠基于數據庫中存儲的用戶和角色信息來執行身份驗證。

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
      	@Autowired
      	private AuthUserService authUserService;
      
      	@Override
      	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      		// 動態讀取數據庫信息
      		auth.userDetailsService(authUserService)
      				// 自定義 AES 方式加密
      				.passwordEncoder(new AESEncoder());
      	}
      }
      

      配置好上述代碼,首先來手動配置兩個角色 budia , admian 以及相應的角色權限和密碼。

      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      	// 手動配置
      	auth.inMemoryAuthentication()
      			.withUser("budai").password("123456").roles("USER")
      			.and()
      			.withUser("admin").password("123456").roles("ADMIN", "USER")
      			.and()
      			// 自定義賬號信息解析方式
      			.passwordEncoder(new AESEncoder());
      }
      

      自定義加密

      Security 中默認提供了強哈希加密方式 BCryptPasswordEncoder,但也可根據實際需求自定義加密邏輯,這通過實現 PasswordEncoder 接口并重寫其方法來完成。在自定義的 PasswordEncoder 實現中,matches 方法的 charSequence 參數實際上是用戶登錄時傳入的密碼(明文),該密碼在驗證前可能已經過解密處理(如果前端使用了AES等加密方式)。而 matches 方法的另一個參數 s(或根據具體實現可能命名為其他變量),則是從數據庫中讀取的、已經加密存儲的用戶密碼值。由于前端工程中實施了AES數據加密,因此在服務器端進行密碼驗證之前,需要先對接收到的加密密碼進行解密操作。

      public class AESEncoder implements PasswordEncoder {
      
      	@Override
      	public String encode(CharSequence charSequence) {
      		String str = charSequence.toString();
      		try {
      			String plain;
      			if (!Objects.equals(str, "userNotFoundPassword")) {
      				plain = AESUtil.desEncrypt(str);
      			} else {
      				plain = str;
      			}
      			return AESUtil.encrypt(plain);
      		} catch (Exception e) {
      			throw new RuntimeException(e);
      		}
      	}
      
      	@Override
      	public boolean matches(CharSequence charSequence, String s) {
      		try {
      			String plain = AESUtil.desEncrypt(charSequence.toString());
      			String result = AESUtil.encrypt(plain);
      			return Objects.equals(result, s);
      		} catch (Exception e) {
      			throw new RuntimeException(e);
      		}
      	}
      }
      

      權限分配

      完成用戶角色的創建之后,接下來的步驟是為不同的角色分配相應的資源權限。這通常在SecurityConfig類中通過重寫configure(HttpSecurity http)方法來實現。在該方法中,可以配置哪些接口(如freeAPIuserAPIadminAPI)可以被特定用戶角色訪問。這些接口的配置信息可以存儲在yml文件中,并通過Spring的注解機制動態獲取。

      當未認證用戶嘗試訪問受保護的資源時,Spring Security會自動將請求重定向到登錄頁面,但在這里,我們通過formLogin().loginProcessingUrl("/api/auth/verify")指定了一個自定義的登錄接口地址/api/auth/verify,以支持通過API請求方式進行用戶認證。用戶提交登錄請求后,AuthUserService中的loadUserByUsername()方法將被調用,以驗證用戶的用戶名和密碼,并確定其角色。

      對于認證成功、認證失敗以及無權限訪問的情況,我們采用了匿名函數(或Lambda表達式,具體取決于實現方式)來處理這些事件的邏輯。這些處理邏輯可能包括重定向到特定頁面、返回錯誤信息或執行其他自定義操作。

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
      	/**
      	 * 免認證資源
      	 */
      	@Value("${auth.api.free}")
      	private String freeAPI;
      
      	/**
      	 * 普通用戶資源
      	 */
      	@Value("${auth.api.user}")
      	private String userAPI;
      
      	/**
      	 * 超級用戶資源
      	 */
      	@Value("${auth.api.admin}")
      	private String adminAPI;
      
      	@Override
      	protected void configure(HttpSecurity http) throws Exception {
      		String[] freeResource = freeAPI.trim().split(",");
      		String[] userResource = userAPI.trim().split(",");
      		String[] adminResource = adminAPI.trim().split(",");
      		http.authorizeRequests()
      				// 設置免認證資源
      				.antMatchers(freeResource).permitAll()
      				// 為不同權限分配不同資源
      				.antMatchers(userResource).hasRole("USER")
      				.antMatchers(adminResource).hasRole("ADMIN")
      				// 默認無定義資源都需認證
      				.anyRequest().authenticated()
      				// 自定義認證訪問資源
      				.and().formLogin().loginProcessingUrl("/api/auth/verify")
      				// 認證成功邏輯
      				.successHandler(this::successHandle)
      				// 認證失敗邏輯
      				.failureHandler(this::failureHandle)
      				// 未認證訪問受限資源邏輯
      				.and().exceptionHandling().authenticationEntryPoint(this::unAuthHandle)
      				.and().httpBasic()
      				// 允許跨域
      				.and().cors()
      				// 關閉跨站攻擊
      				.and().csrf().disable();
      	}
      }
      

      六、邏輯處理

      成功處理

      用戶成功通過認證后,系統會執行兩個關鍵步驟來管理登錄狀態和權限控制。首先,會生成一個JWT(JSON Web Token)Token值,該Token用于后續請求的登錄狀態管理。JWT是基于登錄用戶的用戶名、密碼(通常是密碼的哈希值,而非明文)及角色信息序列化后的JSON數據計算得出的,確保了數據的安全性和可驗證性。其次,用戶的角色信息會被封裝成一個Authentication認證碼,該認證碼是username:password(注意:這里的password部分應替換為更安全的信息,如用戶ID或角色的哈希值,因為直接包含密碼是不安全的)經過Base64編碼后的值,用于后續的權限過濾。

      這兩個認證信息——JWT TokenAuthentication認證碼——都會通過HTTP響應的請求頭返回給前端。前端接收到這些信息后,會將其存儲起來,并在后續發出的所有請求中,在請求頭中攜帶這兩個參數。后端則通過配置過濾器與Spring Security框架,實現對這些請求頭的解析,從而驗證用戶的登錄狀態和訪問權限,完成登錄狀態的管理與權限訪問控制。

      失敗處理

      用戶未通過 Security 認證時,需要通過驗證碼狀態等信息來響應給前端, 在這里我們通過新建的返回類? 來返回結果給前端。

      private void failureHandle(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
      	String msg;
      	if (exception instanceof LockedException) {
      		msg = "Account has been locked, please contact the administrator.";
      	} else if (exception instanceof BadCredentialsException) {
      		msg = "Account credential error, please recheck.";
      	} else {
      		msg = "Account doesn't exist, please recheck.";
      	}
      	response.setContentType("application/json;charset=UTF-8");
      	response.setStatus(203);
      	ResultData<Object> result = new ResultData<>(203, msg, null);
      	response.getWriter().write(objectMapper.writeValueAsString(result));
      }
      

      無權攔截

      在用戶沒有經過 權限認證的情況下訪問資源,則需要進行攔截并返回響應的狀態信息。

      private void unAuthHandle(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
      	String msg = "Please login and try again.";
      	response.setContentType("application/json;charset=UTF-8");
      	response.setStatus(203);
      	ResultData<Object> result = new ResultData<>(203, msg, null);
      	response.getWriter().write(objectMapper.writeValueAsString(result));
      }
      

      七、Filter配置

      Bean注入

      @Configuration
      public class FilterConfig {
      
      	/**
      	 * 設置放行資源
      	 *
      	 * 例:/api/auth/verify
      	 */
      	@Value("${auth.api.verify}")
      	private String verifyAPI;
      
      	@Bean
      	public FilterRegistrationBean<AuthFilter> orderFilter1() {
      		FilterRegistrationBean<AuthFilter> filter = new FilterRegistrationBean<>();
      		filter.setName("auth-filter");
      		// Set effect url
      		filter.setUrlPatterns(Collections.singleton("/**"));
      		// Set ignore url, when multiply the value spilt with ","
      		filter.addInitParameter("excludedUris", verifyAPI);
      		filter.setOrder(-1);
      		filter.setFilter(new AuthFilter());
      		return filter;
      	}
      }
      

      攔截邏輯

      我們新建一個名為AuthFilter的自定義過濾器類并實現Filter接口時,我們需要重點關注doFilter()方法的實現。如之前所述,一旦用戶通過登錄認證成功,系統會將JWT TokenAuthentication認證信息寫入HTTP響應的請求頭中,并返回給前端。之后,前端在發起任何需要認證或權限驗證的請求時,都應在請求頭中包含這兩個參數。

      在請求到達后端時,首先會觸發Spring Security的認證流程。Spring Security會使用請求頭中的Authentication認證信息(盡管通常不直接使用username:password格式的Base64編碼,而是可能使用更安全的認證令牌,如預共享密鑰生成的Token或基于HTTP頭部的認證方式)進行初步的身份驗證。這一部分是Spring Security內部自動處理的,我們無需直接操作。

      一旦通過Spring Security的身份驗證,請求將繼續流向我們配置的AuthFilter。在AuthFilterdoFilter()方法中,我們需要編寫邏輯來解析請求頭中的JWT Token。這個Token包含了用戶的會話信息,如用戶名、角色以及Token的簽發和過期時間等。我們將驗證這個Token是否有效(比如檢查它是否未過期),如果Token已過期,我們需要構造一個包含相應錯誤信息的響應,并通過HTTP狀態碼(如401 Unauthorized)返回給前端。前端接收到這個響應后,可以根據需要重定向用戶到登錄頁面。

      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      	HttpServletRequest req = (HttpServletRequest) servletRequest;
      	HttpServletResponse response = (HttpServletResponse) servletResponse;
      	int status;
      	String msg;
      	String token = req.getHeader("Token");
      	if (StringUtils.isNotBlank(token)) {
      		boolean isExpired = false;
      		try {
      			TokenUtil.parseJWT(token);
      		} catch (ExpiredJwtException e) {
      			isExpired = true;
      		}
      		if (!isExpired) {
      			filterChain.doFilter(req, servletResponse);
      			return;
      		} else {
      			status = 203;
      			msg = "Login expired.";
      		}
      	} else {
      		status = 203;
      		msg = "Please login and try again.";
      	}
      	response.setContentType("application/json;charset=UTF-8");
      	response.setStatus(status);
      	ResultData<Object> result = new ResultData<>(status, msg, null);
      	response.getWriter().write(objectMapper.writeValueAsString(result));
      }
      

      八、跨域處理

      在工程中新建 CorsConfig 類實現 WebMvcConfigurer 接口并重寫 addCorsMappings() 方法配置跨域信息

      @Configuration
      public class CorsConfig implements WebMvcConfigurer {

      	/**
      	 * 設置跨域訪問地址,逗號分隔
      	 *
      	 * 例:http://localhost:8080,http://127.0.0.1:8080
      	 */
      	@Value("${auth.host.cors}")
      	private String hosts;
      
      	@Override
      	public void addCorsMappings(CorsRegistry registry) {
      		String[] crosHost = hosts.trim().split(",");
      		// 設置允許跨域的路徑
      		registry.addMapping("/**")
      				// 設置允許跨域請求的域名
      				.allowedOriginPatterns(crosHost)
      				// 是否允許cookie
      				.allowCredentials(true)
      				// 設置允許的請求方式
      				.allowedMethods("GET", "POST", "DELETE", "PUT")
      				// 設置允許的header屬性
      				.allowedHeaders("*")
      				// 跨域允許時間
      				.maxAge(TimeUnit.SECONDS.toMillis(5));
      	}
      }
      
      posted @ 2024-07-23 17:54  二價亞鐵  閱讀(3019)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 亚洲中文字幕第一页在线| 无码人妻丝袜在线视频| 熟女精品国产一区二区三区| 无码国产偷倩在线播放老年人| 成全影视大全在线观看| 国产精品成人观看视频国产奇米| 亚洲综合久久精品国产高清| 精品一卡2卡三卡4卡乱码精品视频| 91中文字幕在线一区| 亚洲国产欧美一区二区好看电影| 欧美亚洲国产日韩一区二区| 成全影视大全在线观看| 人人妻人人澡人人爽人人精品电影| 偷拍美女厕所尿尿嘘嘘小便| 国产福利酱国产一区二区| 无码一级视频在线| 成年男女免费视频网站| 2019亚洲午夜无码天堂| 久久综合亚洲鲁鲁九月天| 成人无码视频97免费| 国产精品一区二区传媒蜜臀| 欧美成人精品一级在线观看| 成人性无码专区免费视频| 凌海市| 四虎国产精品永久在线下载| 国产一区二区日韩经典| 日韩精品中文字幕人妻| 亚洲乱码国产乱码精品精| 久久国产乱子伦免费精品无码| 国产免费高清69式视频在线观看| 欧美午夜精品久久久久久浪潮| 人妻系列中文字幕精品| 亚洲精品麻豆一区二区| 国产成人精品中文字幕| 久热这里有精品视频在线| 永久国产盗摄一区二区色欲| 在线观看成人av天堂不卡| 精精国产xxxx视频在线| 久久精品国产88精品久久| 亚洲真人无码永久在线| 国产亚洲AV电影院之毛片|