<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          公司新來了一個同事,把權限系統設計的爐火純青!

          共 31457字,需瀏覽 63分鐘

           ·

          2022-10-14 21:22

          推薦閱讀:掌握這些 Spring Boot 啟動擴展點,已經超過 90% 的人了

          思維導圖如下

          RBAC權限分析

          RBAC 全稱為基于角色的權限控制,本段將會從什么是RBAC,模型分類,什么是權限,用戶組的使用,實例分析等幾個方面闡述RBAC

          思維導圖

          繪制思維導圖如下

          什么是RBAC

          RBAC 全稱為用戶角色權限控制,通過角色關聯用戶,角色關聯權限,這種方式,間階的賦予用戶的權限,如下圖所示


          對于通常的系統而言,存在多個用戶具有相同的權限,在分配的時候,要為指定的用戶分配相關的權限,修改的時候也要依次的對這幾個用戶的權限進行修改,有了角色這個權限,在修改權限的時候,只需要對角色進行修改,就可以實現相關的權限的修改。這樣做增加了效率,減少了權限漏洞的發(fā)生。

          模型分類

          對于RBAC模型來說,分為以下幾個模型 分別是RBAC0,RBAC1,RBAC2,RBAC3,這四個模型,這段將會依次介紹這四個模型,其中最常用的模型有RBAC0.

          RBAC0

          RBAC0是最簡單的RBAC模型,這里面包含了兩種。

          用戶和角色是多對一的關系,即一個用戶只充當一種角色,一個角色可以有多個角色的擔當。用戶和角色是多對多的關系,即,一個用戶可以同時充當多個角色,一個角色可以有多個用戶。 

          此系統功能單一,人員較少,這里舉個栗子,張三既是行政,也負責財務,此時張三就有倆個權限,分別是行政權限,和財務權限兩個部分。

          RBAC1

          相對于RBAC0模型來說,增加了子角色,引入了繼承的概念。

          RBAC2 模型

          這里RBAC2模型,在RBAC0模型的基礎上,增加了一些功能,以及限制

          角色互斥

          即,同一個用戶不能擁有兩個互斥的角色,舉個例子,在財務系統中,一個用戶不能擁有會計員和審計這兩種角色。

          基數約束

          即,用一個角色,所擁有的成員是固定的,例如對于CEO這種角色,同一個角色,也只能有一個用戶。

          先決條件

          即,對于該角色來說,如果想要獲得更高的角色,需要先獲取低一級別的角色。舉個栗子,對于副總經理和經理這兩個權限來說,需要先有副總經理權限,才能擁有經理權限,其中副總經理權限是經理權限的先決條件。

          運行時互斥

          即,一個用戶可以擁有兩個角色,但是這倆個角色不能同時使用,需要切換角色才能進入另外一個角色。舉個栗子,對于總經理和專員這兩個角色,系統只能在一段時間,擁有其一個角色,不能同時對這兩種角色進行操作。

          RBAC3模型

          即,RBAC1,RBAC2,兩者模型全部累計,稱為統一模型。

          什么是權限

          權限是資源的集合,這里的資源指的是軟件中的所有的內容,即,對頁面的操作權限,對頁面的訪問權限,對數據的增刪查改的權限。舉個栗子。對于下圖中的系統而言,

          擁有,計劃管理,客戶管理,合同管理,出入庫通知單管理,糧食安全追溯,糧食統計查詢,設備管理這幾個頁面,對這幾個頁面的訪問,以及是否能夠訪問到菜單,都屬于權限。

          用戶組的使用

          對于用戶組來說,是把眾多的用戶劃分為一組,進行批量授予角色,即,批量授予權限。舉個栗子,對于部門來說,一個部門擁有一萬多個員工,這些員工都擁有相同的角色,如果沒有用戶組,可能需要一個個的授予相關的角色,在擁有了用戶組以后,只需要,把這些用戶全部劃分為一組,然后對該組設置授予角色,就等同于對這些用戶授予角色。

          優(yōu)點:減少工作量,便于理解,增加多級管理,等。

          SpringSecurity 簡單使用

          首先添加依賴

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-security</artifactId>
          </dependency>

          然后添加相關的訪問接口

          package com.example.demo.web;

          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.RestController;

          @RestController
          @RequestMapping("/test")
          public class Test {
              @RequestMapping("/test")
              public String test(){
                  return "test";
              }
          }

          最后啟動項目,在日志中查看相關的密碼

          訪問接口,可以看到相關的登錄界面

          輸入用戶名和相關的密碼
          用戶名:user
          密碼 984cccf2-ba82-468e-a404-7d32123d0f9c

          登錄成功

          增加用戶名和密碼

          在配置文件中,書寫相關的登錄和密碼

          spring:
          security:
          user:
          name: ming
          password: 123456
          roles: admin

          在登錄頁面,輸入用戶名和密碼,即可正常登錄。另外,Spring 系列面試題和答案全部整理好了,微信搜索互聯網架構師,在后臺發(fā)送:面試,可以在線閱讀。

          基于內存的認證

          需要自定義類繼承 WebSecurityConfigurerAdapter 代碼如下

          package com.example.demo.config;

          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.crypto.password.NoOpPasswordEncoder;
          import org.springframework.security.crypto.password.PasswordEncoder;

          @Configuration
          public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
              @Bean
              PasswordEncoder passwordEncoder(){
                  return NoOpPasswordEncoder.getInstance();
              }

              @Override
              protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                  auth.inMemoryAuthentication()
                          .withUser("admin").password("123").roles("admin");
              }
          }

          即,配置的用戶名為admin,密碼為123,角色為admin

          HttpSecurity

          這里對一些方法進行攔截

          package com.ming.demo.interceptor;

          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.http.HttpMethod;
          import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
          import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
          import org.springframework.security.config.annotation.web.builders.HttpSecurity;
          import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
          import org.springframework.security.crypto.password.PasswordEncoder;
          import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;

          @Configuration
          @EnableWebSecurity
          public class SecurityConfig extends WebSecurityConfigurerAdapter {
          //基于內存的用戶存儲
          @Override
          public void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth.inMemoryAuthentication()
          .withUser("itguang").password("123456").roles("USER").and()
          .withUser("admin").password("{noop}" + "123456").roles("ADMIN");
          }

          //請求攔截
          @Override
          protected void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests()
          .anyRequest().permitAll()
          .and()
          .formLogin()
          .permitAll()
          .and()
          .logout()
          .permitAll();
          }

          }
          即,這里完成了對所有的方法訪問的攔截。

          SpringSecurity 集成JWT

          這是一個小demo,目的,登錄以后返回jwt生成的token

          導入依賴

          添加web依賴

          導入JWT和Security依賴

           <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
                  <dependency>
                      <groupId>io.jsonwebtoken</groupId>
                      <artifactId>jjwt</artifactId>
                      <version>0.9.1</version>
                  </dependency>
                  <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-security</artifactId>
                      <version>2.3.1.RELEASE</version>
                  </dependency>

          創(chuàng)建一個JwtUser實現UserDetails

          創(chuàng)建 一個相關的JavaBean

          package com.example.demo;

          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.core.userdetails.UserDetails;

          import java.util.Collection;

          public class JwtUser implements UserDetails {
              private String username;
              private String password;
              private Integer state;
              private Collection<? extends GrantedAuthority> authorities;
              public JwtUser(){

              }

              public JwtUser(String username, String password, Integer state,  Collection<? extends GrantedAuthority> authorities){
                  this.username = username;
                  this.password = password;
                  this.state = state;
                  this.authorities = authorities;
              }

              @Override
              public Collection<? extends GrantedAuthority> getAuthorities() {
                  return authorities;
              }

              @Override
              public String getPassword() {
                  return this.password;
              }

              @Override
              public String getUsername() {
                  return this.username;
              }

              @Override
              public boolean isAccountNonExpired() {
                  return true;
              }

              @Override
              public boolean isAccountNonLocked() {
                  return true;
              }

              @Override
              public boolean isCredentialsNonExpired() {
                  return true;
              }

              @Override
              public boolean isEnabled() {
                  return true;
              }
          }

          編寫工具類生成令牌

          編寫工具類,用來生成token,以及刷新token,以及驗證token

          package com.example.demo;

          import io.jsonwebtoken.Claims;
          import io.jsonwebtoken.Jwts;
          import io.jsonwebtoken.SignatureAlgorithm;
          import org.springframework.security.core.userdetails.UserDetails;

          import java.io.Serializable;
          import java.util.Date;
          import java.util.HashMap;
          import java.util.Map;

          public class JwtTokenUtil implements Serializable {
              private String secret;
              private Long expiration;
              private String header;

              private String generateToken(Map<StringObject> claims) {
                  Date expirationDate = new Date(System.currentTimeMillis() + expiration);
                  return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
              }

              private Claims getClaimsFromToken(String token) {
                  Claims claims;
                  try {
                      claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

                  } catch (Exception e) {
                      claims = null;
                  }
                  return claims;
              }

              public String generateToken(UserDetails userDetails) {
                  Map<StringObject> claims = new HashMap<>(2);
                  claims.put("sub", userDetails.getUsername());
                  claims.put("created"new Date());
                  return generateToken(claims);

              }

              public String getUsernameFromToken(String token) {
                  String username;
                  try {
                      Claims claims = getClaimsFromToken(token);
                      username = claims.getSubject();

                  } catch (Exception e) {
                      username = null;

                  }
                  return username;

              }

              public Boolean isTokenExpired(String token) {
                  try {
                      Claims claims = getClaimsFromToken(token);
                      Date expiration = claims.getExpiration();
                      return expiration.before(new Date());
                  } catch (Exception e) {
                      return false;
                  }
              }

              public String refreshToken(String token) {
                  String refreshedToken;
                  try {
                      Claims claims = getClaimsFromToken(token);
                      claims.put("created"new Date());
                      refreshedToken = generateToken(claims);

                  } catch (Exception e) {
                      refreshedToken = null;

                  }
                  return refreshedToken;
              }

              public Boolean validateToken(String token, UserDetails userDetails) {
                  JwtUser user = (JwtUser) userDetails;
                  String username = getUsernameFromToken(token);
                  return (username.equals(user.getUsername()) && !isTokenExpired(token));

              }

          }

          編寫攔截器

          編寫Filter 用來檢測JWT

          package com.example.demo;

          import org.apache.commons.lang.StringUtils;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
          import org.springframework.security.core.context.SecurityContextHolder;
          import org.springframework.security.core.userdetails.UserDetails;
          import org.springframework.security.core.userdetails.UserDetailsService;
          import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
          import org.springframework.stereotype.Component;
          import org.springframework.web.filter.OncePerRequestFilter;

          import javax.servlet.FilterChain;
          import javax.servlet.ServletException;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          import java.io.IOException;

          @Component
          public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
          @Autowired
          private UserDetailsService userDetailsService;
          @Autowired
          private JwtTokenUtil jwtTokenUtil;

          @Override
          protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
          String authHeader = httpServletRequest.getHeader(jwtTokenUtil.getHeader());
          if (authHeader != null && StringUtils.isNotEmpty(authHeader)) {
          String username = jwtTokenUtil.getUsernameFromToken(authHeader);
          if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
          UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
          if (jwtTokenUtil.validateToken(authHeader, userDetails)) {
          UsernamePasswordAuthenticationToken authentication =
          new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
          authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
          SecurityContextHolder.getContext().setAuthentication(authentication);

          }
          }
          }
          filterChain.doFilter(httpServletRequest, httpServletResponse);

          }
          }

          編寫userDetailsService的實現類

          在上方代碼中,編寫userDetailsService,類,實現其驗證過程

          package com.example.demo;

          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.security.core.authority.SimpleGrantedAuthority;
          import org.springframework.security.core.userdetails.User;
          import org.springframework.security.core.userdetails.UserDetails;
          import org.springframework.security.core.userdetails.UserDetailsService;
          import org.springframework.security.core.userdetails.UsernameNotFoundException;
          import org.springframework.stereotype.Service;

          import javax.management.relation.Role;
          import java.util.List;

          @Service
          public class JwtUserDetailsServiceImpl implements UserDetailsService {
          @Autowired
          private UserMapper userMapper;

          @Override
          public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
          User user = userMapper.selectByUserName(s);
          if (user == null) {
          throw new UsernameNotFoundException(String.format("'%s'.這個用戶不存在", s));

          }
          List<SimpleGrantedAuthority> collect = user.getRoles().stream().map(Role::getRolename).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
          return new JwtUser(user.getUsername(), user.getPassword(), user.getState(), collect);

          }
          }

          編寫登錄

          編寫登錄業(yè)務的實現類 其login方法會返回一個JWTUtils 的token

          @Service
          public class UserServiceImpl  implements UserService {
              @Autowired
              private UserMapper userMapper;

              @Autowired
              private AuthenticationManager authenticationManager;

              @Autowired
              private UserDetailsService userDetailsService;

              @Autowired
              private JwtTokenUtil jwtTokenUtil;

              public User findByUsername(String username) {
                  User user = userMapper.selectByUserName(username);
                  return user;

              }

              public RetResult login(String username, String password) throws AuthenticationException {
                  UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
                  final Authentication authentication = authenticationManager.authenticate(upToken);
                  SecurityContextHolder.getContext().setAuthentication(authentication);
                  UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                  return new RetResult(RetCode.SUCCESS.getCode(),jwtTokenUtil.generateToken(userDetails));

              }
          }

          最后配置Config

          @EnableGlobalMethodSecurity(prePostEnabled = true)
          @EnableWebSecurity
          public class WebSecurity extends WebSecurityConfigurerAdapter {
          @Autowired
          private UserDetailsService userDetailsService;
          @Autowired
          private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

          @Autowired
          public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
          authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());

          }

          @Bean(name = BeanIds.AUTHENTICATION_MANAGER)

          @Override
          public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
          }

          @Bean
          public PasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder();

          }

          @Override
          protected void configure(HttpSecurity http) throws Exception {
          http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
          .and().authorizeRequests()
          .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
          .antMatchers("/auth/**").permitAll()
          .anyRequest().authenticated()
          .and().headers().cacheControl();

          http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

          ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();

          registry.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();

          }

          @Bean
          public CorsFilter corsFilter() {
          final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
          final CorsConfiguration cors = new CorsConfiguration();
          cors.setAllowCredentials(true);
          cors.addAllowedOrigin("*");
          cors.addAllowedHeader("*");
          cors.addAllowedMethod("*");
          urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", cors);
          return new CorsFilter(urlBasedCorsConfigurationSource);

          }
          }

          運行,返回token

          運行,返回結果為token

          SpringSecurity JSON登錄

          這里配置SpringSecurity之JSON登錄

          這里需要重寫UsernamePasswordAnthenticationFilter類,以及配置SpringSecurity

          重寫UsernamePasswordAnthenticationFilter

          public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

              @Override
              public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

                  //attempt Authentication when Content-Type is json
                  if(request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
                          ||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){

                      //use jackson to deserialize json
                      ObjectMapper mapper = new ObjectMapper();
                      UsernamePasswordAuthenticationToken authRequest = null;
                      try (InputStream is = request.getInputStream()){
                          AuthenticationBean authenticationBean = mapper.readValue(is,AuthenticationBean.class);
                          authRequest = new UsernamePasswordAuthenticationToken(
                                  authenticationBean.getUsername(), authenticationBean.getPassword());
                      }catch (IOException e) {
                          e.printStackTrace();
                          authRequest = new UsernamePasswordAuthenticationToken(
                                  """");
                      }finally {
                          setDetails(request, authRequest);
                          return this.getAuthenticationManager().authenticate(authRequest);
                      }
                  }

                  //transmit it to UsernamePasswordAuthenticationFilter
                  else {
                      return super.attemptAuthentication(request, response);
                  }
              }
          }

          配置SecurityConfig

          @Override
          protected void configure(HttpSecurity http) throws Exception {
          http
          .cors().and()
          .antMatcher("/**").authorizeRequests()
          .antMatchers("/", "/login**").permitAll()
          .anyRequest().authenticated()
          //這里必須要寫formLogin(),不然原有的UsernamePasswordAuthenticationFilter不會出現,也就無法配置我們重新的UsernamePasswordAuthenticationFilter
          .and().formLogin().loginPage("/")
          .and().csrf().disable();

          //用重寫的Filter替換掉原有的UsernamePasswordAuthenticationFilter
          http.addFilterAt(customAuthenticationFilter(),
          UsernamePasswordAuthenticationFilter.class);
          }

          //注冊自定義的UsernamePasswordAuthenticationFilter
          @Bean
          CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
          CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
          filter.setAuthenticationSuccessHandler(new SuccessHandler());
          filter.setAuthenticationFailureHandler(new FailureHandler());
          filter.setFilterProcessesUrl("/login/self");

          //這句很關鍵,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己組裝AuthenticationManager
          filter.setAuthenticationManager(authenticationManagerBean());
          return filter;
          }

          這樣就完成使用json登錄SpringSecurity。

          Spring Security 密碼加密方式

          需要在Config 類中配置如下內容

           /**
               * 密碼加密
               */

              @Bean
              public BCryptPasswordEncoder passwordEncoder(){
                  return new BCryptPasswordEncoder();
              }

          即,使用此方法,對密碼進行加密, 在業(yè)務層的時候,使用此加密的方法

          @Service
          @Transactional
          public class UserServiceImpl implements UserService {

              @Resource
              private UserRepository userRepository;

              @Resource
              private BCryptPasswordEncoder bCryptPasswordEncoder;  //注入bcryct加密
              @Override
              public User add(User user) {
                  user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); //對密碼進行加密
                  User user2 = userRepository.save(user);
                  return user2;
              }
              @Override
              public ResultInfo login(User user) {
                  ResultInfo resultInfo=new ResultInfo();
                  User user2 = userRepository.findByName(user.getName());
                  if (user2==null) {
                      resultInfo.setCode("-1");
                      resultInfo.setMessage("用戶名不存在");
                      return resultInfo;
                  }

                  //判斷密碼是否正確
                  if (!bCryptPasswordEncoder.matches(user.getPassword(),user2.getPassword())) {
                      resultInfo.setCode("-1");
                      resultInfo.setMessage("密碼不正確");
                      return resultInfo;
                  }
                  resultInfo.setMessage("登錄成功");
                  return resultInfo;
              }
          }
          即,使用BCryptPasswordEncoder 對密碼進行加密,保存數據庫

          使用數據庫認證

          這里使用數據庫認證SpringSecurity

          設計數據表

          這里設計數據表

          著重配置SpringConfig

          @Configurable
          public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
              @Autowired
              private UserService userService;    // service 層注入

              @Bean
              PasswordEncoder passwordEncoder(){
                  return new BCryptPasswordEncoder();
              }

              @Override
              protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                  // 參數傳入Service,進行驗證
                  auth.userDetailsService(userService);
              }

              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.authorizeRequests()
                          .antMatchers("/admin/**").hasRole("admin")
                          .anyRequest().authenticated()
                          .and()
                          .formLogin()
                          .loginProcessingUrl("/login").permitAll()
                          .and()
                          .csrf().disable();
              }
          }

          這里著重配置SpringConfig

          小結

          著重講解了RBAC的權限配置,以及簡單的使用SpringSecurity,以及使用SpringSecurity + JWT 完成前后端的分離,以及配置json登錄,和密碼加密方式。


          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  豆花视频在线观 | 免费国产黄色片 | 国内久久婷婷 | 就去色五月婷婷 | 欧美色站导航 |