国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

SpringBoot+SpringSecurity前后端分離+Jwt的權(quán)限認(rèn)證(改造記錄)

共 45124字,需瀏覽 91分鐘

 ·

2021-09-13 20:39

來源:blog.csdn.net/zzzgd_666/article/details/96444829

前言

一般來說,我們用SpringSecurity默認(rèn)的話是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity還自帶login登錄頁,還讓你配置登出頁,錯誤頁。

但是現(xiàn)在前后端分離才是正道,前后端分離的話,那就需要將返回的頁面換成Json格式交給前端處理了

SpringSecurity默認(rèn)的是采用Session來判斷請求的用戶是否登錄的,但是不方便分布式的擴展,雖然SpringSecurity也支持采用SpringSession來管理分布式下的用戶狀態(tài),不過現(xiàn)在分布式的還是無狀態(tài)的Jwt比較主流。

所以下面說下怎么讓SpringSecurity變成前后端分離,采用Jwt來做認(rèn)證的

一、五個handler一個filter兩個User

5個handler,分別是

  • 實現(xiàn)AuthenticationEntryPoint接口,當(dāng)匿名請求需要登錄的接口時,攔截處理
  • 實現(xiàn)AuthenticationSuccessHandler接口,當(dāng)?shù)卿洺晒?該處理類的方法被調(diào)用
  • 實現(xiàn)AuthenticationFailureHandler接口,當(dāng)?shù)卿浭『?該處理類的方法被調(diào)用
  • 實現(xiàn)AccessDeniedHandler接口,當(dāng)?shù)卿浐?訪問接口沒有權(quán)限的時候,該處理類的方法被調(diào)用
  • 實現(xiàn)LogoutSuccessHandler接口,注銷的時候調(diào)用

1.1 AuthenticationEntryPoint

匿名未登錄的時候訪問,遇到需要登錄認(rèn)證的時候被調(diào)用

/**
 * 匿名未登錄的時候訪問,需要登錄的資源的調(diào)用類
 * @author zzzgd
 */

@Component
public class CustomerAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
     //設(shè)置response狀態(tài)碼,返回錯誤信息等
     ...
        ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.REQUIRED_LOGIN_ERROR));
    }
}

1.2 AuthenticationSuccessHandler

這里是我們輸入的用戶名和密碼登錄成功后,調(diào)用的方法

簡單的說就是獲取用戶信息,使用JWT生成token,然后返回token

/**
 * 登錄成功處理類,登錄成功后會調(diào)用里面的方法
 * @author Exrickx
 */

@Slf4j
@Component
public class CustomerAuthenticationSuccessHandler implements AuthenticationSuccessHandler {


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
     //簡單的說就是獲取當(dāng)前用戶,拿到用戶名或者userId,創(chuàng)建token,返回
        log.info("登陸成功...");
        CustomerUserDetails principal = (CustomerUserDetails) authentication.getPrincipal();
        //頒發(fā)token
        Map<String,Object> emptyMap = new HashMap<>(4);
        emptyMap.put(UserConstants.USER_ID,principal.getId());
        String token = JwtTokenUtil.generateToken(principal.getUsername(), emptyMap);
        ResponseUtil.out(ResultUtil.success(token));
    }
}

1.3 AuthenticationFailureHandler

有登陸成功就有登錄失敗

登錄失敗的時候調(diào)用這個方法,可以在其中做登錄錯誤限制或者其他操作,我這里直接就是設(shè)置響應(yīng)頭的狀態(tài)碼為401,返回

/**
 * 登錄賬號密碼錯誤等情況下,會調(diào)用的處理類
 * @author Exrickx
 */

@Slf4j
@Component
public class CustomerAuthenticationFailHandler implements AuthenticationFailureHandler {


    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
    //設(shè)置response狀態(tài)碼,返回錯誤信息等
     ....
        ResponseUtil.out(401, ResultUtil.failure(ErrorCodeConstants.LOGIN_UNMATCH_ERROR));
    }

}

1.4 LogoutSuccessHandler

登出注銷的時候調(diào)用,Jwt有個缺點就是無法主動控制失效,可以采用Jwt+session的方式,比如刪除存儲在Redis的token

這里需要注意,如果將SpringSecurity的session配置為無狀態(tài),或者不保存session,這里authentication為null!!,注意空指針問題。(詳情見下面的配置WebSecurityConfigurerAdapter)

/**
 * 登出成功的調(diào)用類
 * @author zzzgd
 */

@Component
public class CustomerLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        ResponseUtil.out(ResultUtil.success("Logout Success!"));
    }
}

1.5 AccessDeniedHandler

登錄后,訪問缺失權(quán)限的資源會調(diào)用。

/**
 * 沒有權(quán)限,被拒絕訪問時的調(diào)用類
 * @author Exrickx
 */

@Component
@Slf4j
public class CustomerRestAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
        ResponseUtil.out(403, ResultUtil.failure(ErrorCodeConstants.PERMISSION_DENY));
    }

}

1.6 一個過濾器OncePerRequestFilter

這里算是一個小重點。

上面我們在登錄成功后,返回了一個token,那怎么使用這個token呢?

前端發(fā)起請求的時候?qū)oken放在請求頭中,在過濾器中對請求頭進(jìn)行解析。

  • 如果有accessToken的請求頭(可以自已定義名字),取出token,解析token,解析成功說明token正確,將解析出來的用戶信息放到SpringSecurity的上下文中
  • 如果有accessToken的請求頭,解析token失敗(無效token,或者過期失效),取不到用戶信息,放行
  • 沒有accessToken的請求頭,放行

這里可能有人會疑惑,為什么token失效都要放行呢?

這是因為SpringSecurity會自己去做登錄的認(rèn)證和權(quán)限的校驗,靠的就是我們放在SpringSecurity上下文中的SecurityContextHolder.getContext().setAuthentication(authentication);,沒有拿到authentication,放行了,SpringSecurity還是會走到認(rèn)證和校驗,這個時候就會發(fā)現(xiàn)沒有登錄沒有權(quán)限。

舊版本, 最新在底部

package com.zgd.shop.web.config.auth.filter;

import com.zgd.shop.common.constants.SecurityConstants;
import com.zgd.shop.common.util.jwt.JwtTokenUtil;
import com.zgd.shop.web.config.auth.user.CustomerUserDetailService;
import lombok.extern.slf4j.Slf4j;
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.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;

/**
 * 過濾器,在請求過來的時候,解析請求頭中的token,再解析token得到用戶信息,再存到SecurityContextHolder中
 * @author zzzgd
 */

@Component
@Slf4j
public class CustomerJwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    CustomerUserDetailService customerUserDetailService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        
     //請求頭為 accessToken
     //請求體為 Bearer token

     String authHeader = request.getHeader(SecurityConstants.HEADER);

        if (authHeader != null && authHeader.startsWith(SecurityConstants.TOKEN_SPLIT)) {

            final String authToken = authHeader.substring(SecurityConstants.TOKEN_SPLIT.length());
            String username = JwtTokenUtil.parseTokenGetUsername(authToken);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = customerUserDetailService.loadUserByUsername(username);
                if (userDetails != null) {
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

1.7 實現(xiàn)UserDetails擴充字段

這個接口表示的用戶信息,SpringSecurity默認(rèn)實現(xiàn)了一個User,不過字段寥寥無幾,只有username,password這些,而且后面獲取用戶信息的時候也是獲取的UserDetail。學(xué)習(xí)資料:Java進(jìn)階視頻資源

于是我們將自己的數(shù)據(jù)庫的User作為拓展,自己實現(xiàn)這個接口。繼承的是數(shù)據(jù)庫對應(yīng)的User,而不是SpringSecurity的User

package com.zgd.shop.web.config.auth.user;

import com.zgd.shop.common.constants.UserConstants;
import com.zgd.shop.dao.entity.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/**
 * CustomerUserDetails
 *
 * @author zgd
 * @date 2019/7/17 15:29
 */

public class CustomerUserDetails extends User implements UserDetails {

  private Collection<? extends GrantedAuthority> authorities;

  public CustomerUserDetails(User user){
    this.setId(user.getId());
    this.setUsername(user.getUsername());
    this.setPassword(user.getPassword());
    this.setRoles(user.getRoles());
    this.setStatus(user.getStatus());
  }

  public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
    this.authorities = authorities;
  }

  /**
   * 添加用戶擁有的權(quán)限和角色
   * @return
   */

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

  /**
   * 賬戶是否過期
   * @return
   */

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

  /**
   * 是否禁用
   * @return
   */

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

  /**
   * 密碼是否過期
   * @return
   */

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

  /**
   * 是否啟用
   * @return
   */

  @Override
  public boolean isEnabled() {
    return UserConstants.USER_STATUS_NORMAL.equals(this.getStatus());
  }
}

1.8 實現(xiàn)UserDetailsService

SpringSecurity在登錄的時候,回去數(shù)據(jù)庫(或其他來源),根據(jù)username獲取正確的user信息,就會根據(jù)這個service類,拿到用戶的信息和權(quán)限。我們自己實現(xiàn)

package com.zgd.shop.web.config.auth.user;

import com.alibaba.fastjson.JSON;
import com.zgd.shop.dao.entity.model.User;
import com.zgd.shop.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 java.util.ArrayList;
import java.util.List;

/**
 * @author zgd
 * @date 2019/1/16 16:27
 * @description 自己實現(xiàn)UserDetailService,用與SpringSecurity獲取用戶信息
 */

@Service
@Slf4j
public class CustomerUserDetailService implements UserDetailsService {

  @Autowired
  private IUserService userService;

  /**
   * 獲取用戶信息,然后交給spring去校驗權(quán)限
   * @param username
   * @return
   * @throws UsernameNotFoundException
   */

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //獲取用戶信息
    User user = userService.getUserRoleByUserName(username);
    if(user == null){
      throw new UsernameNotFoundException("用戶名不存在");
    }
    CustomerUserDetails customerUserDetails = new CustomerUserDetails(user);

    List<SimpleGrantedAuthority> authorities = new ArrayList<>();
    //用于添加用戶的權(quán)限。只要把用戶權(quán)限添加到authorities 就萬事大吉。
    if (CollectionUtils.isNotEmpty(user.getRoles())){
      user.getRoles().forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_"+r.getRoleName())));
    }
    customerUserDetails.setAuthorities(authorities);
    log.info("authorities:{}", JSON.toJSONString(authorities));
    
    //這里返回的是我們自己定義的UserDetail
    return customerUserDetails;
  }
}

二、配置WebSecurityConfigurerAdapter

我們需要將上面定義的handler和filter,注冊到SpringSecurity。同時配置一些放行的url

這里有一點需要注意:如果配置了下面的SessionCreationPolicy.STATELESS,則SpringSecurity不會保存session會話,在/logout登出的時候會拿不到用戶實體對象。

http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

如果登出注銷不依賴SpringSecurity,并且session交給redis的token來管理的話,可以按上面的配置。

package com.zgd.shop.web.config;

import com.zgd.shop.web.config.auth.encoder.MyAesPasswordEncoder;
import com.zgd.shop.web.config.auth.encoder.MyEmptyPasswordEncoder;
import com.zgd.shop.web.config.auth.handler.*;
import com.zgd.shop.web.config.auth.filter.CustomerJwtAuthenticationTokenFilter;
import com.zgd.shop.web.config.auth.user.CustomerUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @Author: zgd
 * @Date: 2019/1/15 17:42
 * @Description:
 */

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)// 控制@Secured權(quán)限注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  /**
   * 這里需要交給spring注入,而不是直接new
   */

  @Autowired
  private PasswordEncoder passwordEncoder;
  @Autowired
  private CustomerUserDetailService customerUserDetailService;
  @Autowired
  private CustomerAuthenticationFailHandler customerAuthenticationFailHandler;
  @Autowired
  private CustomerAuthenticationSuccessHandler customerAuthenticationSuccessHandler;
  @Autowired
  private CustomerJwtAuthenticationTokenFilter customerJwtAuthenticationTokenFilter;
  @Autowired
  private CustomerRestAccessDeniedHandler customerRestAccessDeniedHandler;
  @Autowired
  private CustomerLogoutSuccessHandler customerLogoutSuccessHandler;
  @Autowired
  private CustomerAuthenticationEntryPoint customerAuthenticationEntryPoint;


 
  /**
   * 該方法定義認(rèn)證用戶信息獲取的來源、密碼校驗的規(guī)則
   *
   * @param auth
   * @throws Exception
   */

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //auth.authenticationProvider(myauthenticationProvider)  自定義密碼校驗的規(guī)則

    //如果需要改變認(rèn)證的用戶信息來源,我們可以實現(xiàn)UserDetailsService
    auth.userDetailsService(customerUserDetailService).passwordEncoder(passwordEncoder);
  }


  @Override
  protected void configure(HttpSecurity http) throws Exception {
    /**
     * antMatchers: ant的通配符規(guī)則
     * ? 匹配任何單字符
     * * 匹配0或者任意數(shù)量的字符,不包含"/"
     * ** 匹配0或者更多的目錄,包含"/"
     */

    http
            .headers()
            .frameOptions().disable();

    http
            //登錄后,訪問沒有權(quán)限處理類
            .exceptionHandling().accessDeniedHandler(customerRestAccessDeniedHandler)
            //匿名訪問,沒有權(quán)限的處理類
            .authenticationEntryPoint(customerAuthenticationEntryPoint)
    ;

    //使用jwt的Authentication,來解析過來的請求是否有token
    http
            .addFilterBefore(customerJwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);


    http
            .authorizeRequests()
            //這里表示"/any"和"/ignore"不需要權(quán)限校驗
            .antMatchers("/ignore/**""/login""/**/register/**").permitAll()
            .anyRequest().authenticated()
            // 這里表示任何請求都需要校驗認(rèn)證(上面配置的放行)


            .and()
            //配置登錄,檢測到用戶未登錄時跳轉(zhuǎn)的url地址,登錄放行
            .formLogin()
            //需要跟前端表單的action地址一致
            .loginProcessingUrl("/login")
            .successHandler(customerAuthenticationSuccessHandler)
            .failureHandler(customerAuthenticationFailHandler)
            .permitAll()

            //配置取消session管理,又Jwt來獲取用戶狀態(tài),否則即使token無效,也會有session信息,依舊判斷用戶為登錄狀態(tài)
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

            //配置登出,登出放行
            .and()
            .logout()
            .logoutSuccessHandler(customerLogoutSuccessHandler)
            .permitAll()
            
            .and()
            .csrf().disable()
    ;
  }


}

三、其他

大概到這就差不多了,啟動,localhost:8080/login,使用postman,采用form-data,post提交,參數(shù)是username和password,調(diào)用,返回token。

將token放在header中,請求接口。學(xué)習(xí)資料:Java進(jìn)階視頻資源

3.1 不足之處

上面是最簡單的處理,還有很多優(yōu)化的地方。比如

  • 控制token銷毀?

使用redis+token組合,不僅解析token,還判斷redis是否有這個token。注銷和主動失效token:刪除redis的key

  • 控制token過期時間?如果用戶在token過期前1秒還在操作,下1秒就需要重新登錄,肯定不好

1、考慮加入refreshToken,過期時間比token長,前端在拿到token的同時獲取過期時間,在過期前一分鐘用refreshToken調(diào)用refresh接口,重新獲取新的token。

2、 將返回的jwtToken設(shè)置短一點的過期時間,redis再存這個token,過期時間設(shè)置長一點。如果請求過來token過期,查詢redis,如果redis還存在,返回新的token。(為什么redis的過期時間大于token的?因為redis的過期是可控的,手動可刪除,以redis的為準(zhǔn))

  • 每次請求都會被OncePerRequestFilter 攔截,每次都會被UserDetailService中的獲取用戶數(shù)據(jù)請求數(shù)據(jù)庫

可以考慮做緩存,還是用redis或者直接保存內(nèi)存中

3.2 解決

這是針對上面的2.2說的,也就是redis時間久一點,jwt過期后如果redis沒過期,頒發(fā)新的jwt。

不過更推薦的是前端判斷過期時間,在過期之前調(diào)用refresh接口拿到新的jwt。

為什么這樣?

如果redis過期時間是一周,jwt是一個小時,那么一個小時后,拿著這個過期的jwt去調(diào),就可以想創(chuàng)建多少個新的jwt就創(chuàng)建,只要沒過redis的過期時間。當(dāng)然這是在沒對過期的jwt做限制的情況下,如果要考慮做限制,比如對redis的value加一個字段,保存當(dāng)前jwt,刷新后就用新的jwt覆蓋,refresh接口判斷當(dāng)前的過期jwt是不是和redis這個一樣。

總之還需要判斷刷新token的時候,過期jwt是否合法的問題。總不能去年的過期token也拿來刷新吧。

而在過期前去刷新token的話,至少不會發(fā)生這種事情

不過我這里自己寫demo,采用的還是2.2的方式,也就是過期后給個新的,思路如下:

  • 登錄后頒發(fā)token,token有個時間戳,同時以username拼裝作為key,保存這個時間戳到緩存(redis,cache)
  • 請求來了,過濾器解析token,沒過期的話,還需要比較緩存中的時間戳和token的時間戳是不是一樣 ,如果時間戳不一樣,說明該token不能刷新。無視
  • 注銷,清除緩存數(shù)據(jù)

這樣就可以避免token過期后,我還能拿到這個token無限制的refresh。

不過這個還是有細(xì)節(jié)方面問題,并發(fā)下同時刷新token這些并沒有考慮,部分代碼如下

舊版本, 最新在底部

package com.zgd.shop.web.auth.filter;

import com.zgd.shop.common.constants.SecurityConstants;
import com.zgd.shop.common.util.jwt.JwtTokenUtil;
import com.zgd.shop.web.auth.user.CustomerUserDetailService;
import com.zgd.shop.web.auth.user.CustomerUserDetails;
import com.zgd.shop.web.auth.user.UserSessionService;
import com.zgd.shop.web.auth.user.UserTokenManager;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
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;

/**
 * 過濾器,在請求過來的時候,解析請求頭中的token,再解析token得到用戶信息,再存到SecurityContextHolder中
 * @author zzzgd
 */

@Component
@Slf4j
public class CustomerJwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    CustomerUserDetailService customerUserDetailService;
    @Autowired
    UserSessionService userSessionService;
    @Autowired
    UserTokenManager userTokenManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        
     //請求頭為 accessToken
     //請求體為 Bearer token

     String authHeader = request.getHeader(SecurityConstants.HEADER);

        if (authHeader != null && authHeader.startsWith(SecurityConstants.TOKEN_SPLIT)) {

            final String authToken = authHeader.substring(SecurityConstants.TOKEN_SPLIT.length());

            String username;
            Claims claims;
            try {
                claims = JwtTokenUtil.parseToken(authToken);
                username = claims.getSubject();
            } catch (ExpiredJwtException e) {
                //token過期
                claims = e.getClaims();
                username = claims.getSubject();
                CustomerUserDetails userDetails = userSessionService.getSessionByUsername(username);
                if (userDetails != null){
                    //session未過期,比對時間戳是否一致,是則重新頒發(fā)token
                    if (isSameTimestampToken(username,e.getClaims())){
                        userTokenManager.awardAccessToken(userDetails,true);
                    }
                }
            }
            //避免每次請求都請求數(shù)據(jù)庫查詢用戶信息,從緩存中查詢
            CustomerUserDetails userDetails = userSessionService.getSessionByUsername(username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//                UserDetails userDetails = customerUserDetailService.loadUserByUsername(username);
                if (userDetails != null) {
                    if(isSameTimestampToken(username,claims)){
                        //必須token解析的時間戳和session保存的一致
                        UsernamePasswordAuthenticationToken authentication =
                                new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
        }
        chain.doFilter(request, response);
    }

    /**
     * 判斷是否同一個時間戳
     * @param username 
     * @param claims
     * @return
     */

    private boolean isSameTimestampToken(String username, Claims claims){
        Long timestamp = userSessionService.getTokenTimestamp(username);
        Long jwtTimestamp = (Long) claims.get(SecurityConstants.TIME_STAMP);
        return timestamp.equals(jwtTimestamp);
    }
}
package com.zgd.shop.web.auth.user;

import com.google.common.collect.Maps;
import com.zgd.shop.common.constants.SecurityConstants;
import com.zgd.shop.common.constants.UserConstants;
import com.zgd.shop.common.util.ResponseUtil;
import com.zgd.shop.common.util.jwt.JwtTokenUtil;
import com.zgd.shop.core.result.ResultUtil;
import com.zgd.shop.web.config.auth.UserAuthProperties;
import org.apache.commons.collections.MapUtils;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * UserTokenManager
 * token管理
 *
 * @author zgd
 * @date 2019/7/19 15:25
 */

@Component
public class UserTokenManager {

  @Autowired
  private UserAuthProperties userAuthProperties;
  @Autowired
  private UserSessionService userSessionService;

  /**
   * 頒發(fā)token
   * @param principal
   * @author zgd
   * @date 2019/7/19 15:34
   * @return void
   */

  public void awardAccessToken(CustomerUserDetails principal,boolean isRefresh) {
    //頒發(fā)token 確定時間戳,保存在session中和token中
    long mill = System.currentTimeMillis();
    userSessionService.saveSession(principal);
    userSessionService.saveTokenTimestamp(principal.getUsername(),mill);

    Map<String,Object> param = new HashMap<>(4);
    param.put(UserConstants.USER_ID,principal.getId());
    param.put(SecurityConstants.TIME_STAMP,mill);

    String token = JwtTokenUtil.generateToken(principal.getUsername(), param,userAuthProperties.getJwtExpirationTime());
    HashMap<String, String> map = Maps.newHashMapWithExpectedSize(1);
    map.put(SecurityConstants.HEADER,token);
    int code = isRefresh ? 201 : 200;
    ResponseUtil.outWithHeader(code,ResultUtil.success(),map);
  }
}

針對token解析的過濾器做了優(yōu)化:

  • 如果redis的session沒過期, 但是請求頭的token過期了, 判斷時間戳一致后, 頒發(fā)新token并返回
  • 如果redis的session沒過期, 但是請求頭的token過期了, 時間戳不一致, 說明當(dāng)前請求的token無法刷新token, 設(shè)置響應(yīng)碼為401返回
  • 如果請求頭的token過期了, 但是redis的session失效或未找到, 直接放行, 交給后面的權(quán)限校驗處理(也就是沒有給上下文SecurityContextHolder設(shè)置登錄信息, 后面如果判斷這個請求缺少權(quán)限會自行處理)
package com.zgd.shop.web.auth.filter;

import com.zgd.shop.common.constants.SecurityConstants;
import com.zgd.shop.common.util.ResponseUtil;
import com.zgd.shop.common.util.jwt.JwtTokenUtil;
import com.zgd.shop.core.error.ErrorCodeConstants;
import com.zgd.shop.core.result.ResultUtil;
import com.zgd.shop.web.auth.user.CustomerUserDetailService;
import com.zgd.shop.web.auth.user.CustomerUserDetails;
import com.zgd.shop.web.auth.user.UserSessionService;
import com.zgd.shop.web.auth.user.UserTokenManager;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
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;

/**
 * 過濾器,在請求過來的時候,解析請求頭中的token,再解析token得到用戶信息,再存到SecurityContextHolder中
 * @author zzzgd
 */

@Component
@Slf4j
public class CustomerJwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    CustomerUserDetailService customerUserDetailService;
    @Autowired
    UserSessionService userSessionService;
    @Autowired
    UserTokenManager userTokenManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        
     //請求頭為 accessToken
     //請求體為 Bearer token

     String authHeader = request.getHeader(SecurityConstants.HEADER);

        if (authHeader != null && authHeader.startsWith(SecurityConstants.TOKEN_SPLIT)) {
            //請求頭有token
            final String authToken = authHeader.substring(SecurityConstants.TOKEN_SPLIT.length());

            String username;
            Claims claims;
            try {
                claims = JwtTokenUtil.parseToken(authToken);
                username = claims.getSubject();
            } catch (ExpiredJwtException e) {
                //token過期
                claims = e.getClaims();
                username = claims.getSubject();
                CustomerUserDetails userDetails = userSessionService.getSessionByUsername(username);
                if (userDetails != null){
                    //session未過期,比對時間戳是否一致,是則重新頒發(fā)token
                    if (isSameTimestampToken(username,e.getClaims())){
                        userTokenManager.awardAccessToken(userDetails,true);
                        //直接設(shè)置響應(yīng)碼為201,直接返回
                        return;
                    }else{
                        //時間戳不一致.無效token,無法刷新token,響應(yīng)碼401,前端跳轉(zhuǎn)登錄頁
                        ResponseUtil.out(HttpStatus.UNAUTHORIZED.value(),ResultUtil.failure(ErrorCodeConstants.REQUIRED_LOGIN_ERROR));
                        return;
                    }
                }else{
                    //直接放行,交給后面的handler處理,如果當(dāng)前請求是需要訪問權(quán)限,則會由CustomerRestAccessDeniedHandler處理
                    chain.doFilter(request, response);
                    return;
                }
            }

            //避免每次請求都請求數(shù)據(jù)庫查詢用戶信息,從緩存中查詢
            CustomerUserDetails userDetails = userSessionService.getSessionByUsername(username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//                UserDetails userDetails = customerUserDetailService.loadUserByUsername(username);
                if (userDetails != null) {
                    if(isSameTimestampToken(username,claims)){
                        //必須token解析的時間戳和session保存的一致
                        UsernamePasswordAuthenticationToken authentication =
                                new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
        }
        chain.doFilter(request, response);
    }

    /**
     * 判斷是否同一個時間戳
     * @param username
     * @param claims
     * @return
     */

    private boolean isSameTimestampToken(String username, Claims claims){
        Long timestamp = userSessionService.getTokenTimestamp(username);
        Long jwtTimestamp = (Long) claims.get(SecurityConstants.TIME_STAMP);
        return timestamp.equals(jwtTimestamp);
    }
}

瀏覽 46
點贊
評論
收藏
分享

手機掃一掃分享

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

手機掃一掃分享

分享
舉報

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 欧美一级黄色片| 亚洲香蕉视频网站| 最近中文字幕在线视频| 亚洲一级无码| 自拍偷拍视频网址| 亚洲成人综合在线| 五月天婷婷丁香综合视频| 久草视频福利在线| 少妇一级婬片内射视频| 国产精品黑人ThePorn| 波多野吉衣视频| 国产在线激情| 中文字幕一区二区三区的重点问题| www.黄色片| AV小说在线观看| 9久9久9久9久女女女女| AV在线导航| 六月婷婷在线| 好爽~要尿了~要喷了~同桌| 日本操鸡小视频| 三级成人网站| 日本无码嫩草一区二区| 国产成人免费在线观看| 97在线观看免费| 国产棈品久久久久久久久久九秃| 一起草在线视频| 91久久人澡人妻人人做人人爽97| 亚洲婷婷综合网| 国产精品人妻无码一区牛牛影视| 久久燥| 狠狠色AV| 欧美黄色大香蕉| www.啪啪| 免费A片在线观看| 91婷婷五月天| 青青青草视频| 国精产品一二四区黑人| 91久久精品一区二区三| 东北老女人操逼视频| 欧美色视频一区二区三区在线观看| 91亚洲国产成人久久精品网站| 精品一区二区三区四区学生| 影音先锋婷婷| 成人免费高清| 伊人99在线| 国产欧美在线免费观看| 伊人婷婷久久| 国产美女久久久| 成人无码视频在线| 欧美午夜精品久久久| 91香蕉视频在线| 日韩免费视频在线观看| 久久久九九九| 国产高清精品软件丝瓜软件 | 久久久穴| 青青青草视频在线观看| 日本操逼片| 七六十路の高齢熟妇无码| 天天日,天天干,天天操| 亚洲色色色| 刘玥精品国产一区二区三区| 中文字字幕在线| 天天操天天日天天射| 成人免费视频一区二区三区| 大鸡吧操视频| 久久久久久大香蕉| 在线黄色视频网站| 黄色亚洲网站| 成人做爰黄A片免费看| 国产字幕| 五月停亭六月,六月停亭的英语| 国产麻豆一区二区三区| 亚洲无码一区在线| 国产香蕉在线视频| 免费在线观看视频黄| 国产乱子伦一区二区三区在线观看 | 制服.丝袜.亚洲.中文.豆花| 好看的中文字幕av| 激情综合网五月婷婷| 高潮91PORN蝌蚪九色| 亚洲中文字幕免费观看视频| 一级免费爱爱视频| 中文字幕在线观看网| 国产成人自拍视频在线| 性爱av在线观看| 性性性性性XXXXX| 51嘿嘿嘿国产精品伦理| AV电影在线免费观看| 9一区二区三区| 成人你懂的| 国产成人精品a视频一区| 自拍三级| 国产成人777777精品综合| 国产一级a毛一级a毛观看视频网站www.jn | 人妻少妇精品| 先锋影音资源站| 狠狠久久| 日韩AV在线直播| 97这里只有精品| 午夜福利电影无码| 久久露脸国语精品国产91| 亚洲在线无码播放| 久久久久久久久黄色| www黄色片| 久久久久久久网| 免费69视频看片| 麻豆乱伦视频| 日韩啪| AV在线免费观看网址| 狠狠躁夜夜躁人爽| 亚洲一区二区在线免费观看| 日韩视频免费在线观看| 亚洲AV在线观看| 成人小视频观看| 熟女人妻一区二区三区| 欧美喷水视频| 91国内精品| 日韩一区二区高清无码| 夜夜福利| 男女日皮的视频| 亚洲无码在线免费| 日韩极品视频| 日韩黄色电影网站| 美女网站黄| 黄色777| 亚洲色情在线播放| 日韩一级免费观看| 视频一区二| 一级黄色视频免费观看| 影音先锋色先锋| 妻子互换被高潮了三次| 精品人妻一区二区三区-国产精品 无码人妻av黄色一区二区三区 | 最新AV在线播放| 中文字幕成人av| 国产精品在线免费| 久天堂| 水蜜桃网站在线观看| 日韩黄片免费看| 91在线看片| 亚洲日韩国产AV无码无码精品| 亚洲中文无码在线| 一级黄色大毛片| 国产高清精品无码| 国产最新福利| 精品国产va久久久久久久| 欧美成人免费A级在线观看| 1插菊花综合| 日韩欧美成人电影| 波多野结衣久久| 肏逼在线观看| 黄片在线网站| 成人午夜精品无码区| 无码一区二区三区四区五区六区| 97人人草| 青青青操| 亚洲日韩免费在线观看| a片网站在线观看| 亚洲AV黄色| JULIA超乳JULIA无码| 日逼综合| 亚洲成人无码在线| 久草A片| 午夜国产在线视频| 黄色在线视频观看| 色综合天天综合网国产成人网| 在线看v| 亚洲一区二区在线| www.伊人大香蕉| 国产中文字字幕乱码无限| 日韩一区二区不卡| 2025天天干| 亚洲色图图片| 情趣视频网站| 国产欧美综合三级伦| 在线观看亚洲一区| 日韩操逼网站| 99综合| 亚洲无码黄色电影| 996热re视频精品视频| 性爱日韩| 爆草美女| 操人妻视频| 国产欧美日韩成人| 久草在在线| 人成免费在线视频| 蜜臀久久久久久999| 免费观看毛片| 欧美精品一区二区三区蜜臀| 欧美午夜性爱视频| 波多野结衣无码一区二区| 女人久久久| 全国最大成人网站| 国产成人久久777777黄蓉| 人人妻人人操人人爱| 欧产日产国产swag| 狠狠躁日日躁夜夜躁2022麻豆 | 亚洲日韩成人电影| 亚洲AV无码专区一级婬片毛片 | 亚州加勒比无码| 女邻居的B好大| 大香蕉免费在线观看| 香蕉网址| 欧美黄色一级| 国产精品久久77777| 欧性猛交ⅩXXX乱大交| 玖玖成人电影| 亚洲av电影在线观看| 欧美成人中文字幕| 色图15p| 天天色天天日天天干| 水蜜桃视频网站| 久久g热| 在线观看日韩视频| 欧美成人免费网站| 国产熟妇码视频户外直播| 大香蕉伊人手机在线| 国产成人无码Av片在线公司 | 日韩一区二区三区四区| 中文字幕H| 日本边摸边吻奶边做爰| 一区高清无码| 成人抽插视频| 国产成人无码精免费视频| 日本无码在线视频| 欧美成人精品AAA| 免费av一区二区| 欧美色图另类图片| 日韩欧美高清第一期| 亚洲成人中文字幕| 久久国产精彩视频| 人人看人人摸人人搞| 精久久久| 五月婷婷狠狠爱| 开心激情网五月天| 国产成人AV网站| 日都一级A片| 国产小黄片在线| 欧美精品第一页| 西西444WWW大胆无| 中文在线不卡| 国产精品免费人成人网站酒店| 成人在线视频网站| 91久久久久久久久久久| 欧美成年人网站| 丰满欧美熟妇免费视频| 天天综合字幕一区二区| 四川BBB搡BBB爽爽爽电影| 中文字幕熟女人妻| 亚洲精品国产成人无码区在线| 狠狠插网站| 亚洲精品国产av| 思思久久高颜值| 欧美激情国产精品| 丁香色色网| 强奸校花到高潮| 99精品视频免费在线观看| 久久久久无码| www一个人免费观看视频www| 久久九一| 啊啊啊国产| 欧美性BBwBBwBBwHD| AAA日韩| 亚洲夜夜撸| 久久无码影视| 成人免费视频国产在线观看| 青青草无码视频| 乱伦91视频| 蜜桃毛片| 毛片网| 欧美特级黄| www| 成人午夜精品福利免费| 国产精品一色哟哟哟| 怡春院av| 麻豆精品在线播放| 婷婷五月天基地| 影音先锋一区二区三区| 91成人区| 久久99精品久久久水蜜桃| 婷婷激情丁香五月天| 自拍视频国产| 免费福利在线观看| 五月丁香中文| 91亚洲精品乱码久久久久久蜜桃 | www.re99| 强辱丰满人妻HD中文字幕| 91av电影网| 午夜福利视频网| 亚洲天堂一区| 91无码精品国产AⅤ| 一区二区视频在线观看| 国产一级a毛片| 中国黄色学生妹一级片| 8050午夜| 在线视频福利导航| 国产免费小视频| 亚洲成人电影天堂| 日韩无| 一本到在线观看午夜剧场| 欧美日韩在线视频一区| 日韩黄色网址| 久久精品婷婷| 亚洲男人天堂av| 亚洲人一级电影| 欧美XXX视频| 中文字幕在线观看福利视频| 色五月激情网| 免费国产视频| 影音先锋男人网| 亚洲天堂无码视频| 欧美精品网| 日韩成人无码电影| 午夜美女视频| 青草福利在线| 五月丁香欧美性爱| 国产午夜成人免费看片无遮挡| 99大香蕉| 精品视频日韩| 国产三级在线播放| 日韩精品无码一区二区三区| 69成人精品国产| 日本欧美中文字幕| 亚洲精品一二三区| 国产女人18毛片水18精品软件| 日韩激情网站| 国产精品久久久久久久久A| 欧美日韩大片| 中文字幕浅井香舞被黑人俘虏| 粉嫩小泬BBBBBB免费看| 黄片免费大全| 一区二区在线免费观看| 欧美手机在线| 狠狠狠狠狠狠干| 在线观看黄色小视频| AV资源在线免费观看| 国产精品福利视频| 欧美成人精品激情在线观看| 俺也去在线视频| 欧美成人性爱影院| 一二三四在线视频| 爱爱亚洲| 亚洲国产中文字幕| 欧美日韩一级二级三级| 婷婷五月香| 色香蕉在线视频| 无码人妻丰满熟妇| 精品一区二区三区四区五区六区七区八区九区| 久久精品成人电影| 黄色一级大片在线免费看国产| www.91久久| 日韩欧美三级在线| 亚洲性网| 青青青亚州视频在线| 欧美一区二区三区在线观看| 天堂在线9| 五月婷婷在线观看| 久久91精品| 亚洲综合伊人| 婷婷精品免费久久| 精品乱码一区| 美女操逼网站| 麻豆疯狂做受XXXX高潮视频| 日韩中文在线视频| 五月天视频网| eeuss一区| 内射视频网| 国产人成一区二区三区影院| 丁香花五月天| 日韩av在线免费观看| 国产噜噜噜噜久久久久久久久| 国产操B| 亚洲成人视频免费在线观看| 少妇性视频| 国产精品成人影视| 黄色美女毛片| 骚逼av| 在线看v片| 久草资源在线| 国产精品无码中文在线| 亚洲国产熟妇综合色专区| 午夜福利aaa| 97黄色| 国产福利视频导航| 人人爽人人操人人爱| 色婷婷色| 成人毛片AV无码| 中文字幕的色| 丰满人妻一区二区| 国产av影音| 夜夜操免费视频| 午夜三区| 亚洲欧洲有码在线| 亚洲色综合久久五月| 久久九九国产精品怡红院| 国产精品无码专区| 亚洲黄色视频免费观看| 色墦五月丁香| 国产精品国产三级国产AⅤ原创| 又粗又硬又爽18级A片| 日韩少妇视频| 琪琪久久| 欧美成人一区二区| 在线观看的av网站| 开心色播五月| 久草手机在线视频| 国产成人精品久久| 韩国中文字幕HD久久| 国产精品成人在线| 日韩美女性爱| 亚洲成人免费| 大香蕉免费在线观看| 国产成人精品视频免费看| 高清日韩无码视频| 午夜专区| 日批视频| A片视频免费| 无码人妻中文| 少妇一级| 日韩亚洲天堂| 日韩欧美性爱网站| 一区二区三区电影高清电影免费观看 | 日韩中文字幕一区二区| 亚洲一级av| 中文字幕日本精品5| 日无码在线| 91丨人妻丨偷拍| 夸克看成人片一级A片| 欧美黄色激情视频网站| 69国产精品| 啪啪视频在线观看| adn日韩av| 口爆吞精在线| 大肉大捧视频免费观看| 自拍偷拍网| 中文字幕av久久爽Av| 婷婷五月色播| 安徽妇搡BBBB搡BBBB小说| 免费观看成人| 亚洲高清福利视频| 欧美在线观看视频| 国产熟妇毛多久久久久一区| 婷婷久久久久久| 日本操逼网| 色色大香蕉| 人人爱人人操人人干| 你懂的在线视频| 久久久久逼| 夜间福利视频| 伊人五月在线| 成人午夜福利电影| 亚洲中文字幕在线视频播放| 懂色一区二区三区免费| 亚洲成人免费网站| 停停五月天| 无码人妻丰满熟妇区毛片蜜桃麻豆| 乱伦三级| 九九九无码| 手机在线一区| 亚洲免费视频观看| 天天综合干| 丁香婷婷五月基地| A片观看视频| 人人操碰| 国产成人亚洲综合AV婷婷| 一级操逼大片| 中文字幕乱妇无码Av在线| 91AV成人| 猛男大粗猛爽h男人味| 亚洲男人综合| 中文无码在线视频| 中文字幕一区二区三区在线观看| 亚洲小电影在线观看| 亚洲A√| 青草视频在线免费观看| 长腿女神打扫偷懒被主人猛操惩罚 | 逼逼AV| 国产精品怡红院有限公司| 亚洲精品在线观看视频| 一级二级三级视频| 无码视频一二三区| 大香蕉75| 婷婷五月天无码| 色色色欧美| 亚洲日韩成人AV| 亚洲女人天堂| 成人无码视频在线观看| 国产一级a一片成人AV| 国产女人18| 91天天综合| 免费在线观看a| 久久er99| 91香蕉网站| 特级西西人体444WWw高清大胆| 久久99精品久久久水蜜桃| 9久9久9久9久女女女女| 久久婷婷热| 国产激情视频| 亚洲在线视频播放| 天堂资源中文在线| 俺也要操| 色五月婷婷丁香五月| 亚洲一区视频| av资源网站| 七十路の高齢熟女千代子下载| 亚洲精品18在线观看| 人人草人人| 国产—a毛—a毛A免费| 热99在线| 中国操逼毛片| 欧美老妇日韩| 欧美精品99久久久| 国产黄色小电影| 蝌蚪久久| 天天逼网| 乱伦a片| 大香蕉福利视频| 欧美V| 自拍偷拍影音先锋| 在线无码视频观看| 婷婷丁香人妻天天爽| 欧美成人网站在线观看| 久久精品国产99精品国产亚洲性色| 熟女老阿V8888AV| 国产精品嫩草久久久久yw193 | 99热这里只有精品1| 少妇激情av| 亚洲在线观看免费| 欧美东京热视频| 91在线亚洲| 天天肏屄| 久久污| 91人人妻人人操| 日韩色图在线观看| 日韩肏屄视频在线观看| 天天爽日日澡AAAA片| 亚洲无码精品在线观看| 影音先锋女人av噜噜色| 国产理论在线| 久久艹综合网| 欧美精品成人免费片| 嫩草在线精品| 黄片免费视频在线观看| 亚韩AV| 宅男噜噜噜66一区二区| 日韩AⅤ视频| 午夜无码鲁丝片午夜精品一区二区| 大香蕉中文在线| 日韩无码三级| 久久午夜福利电影| 日本亲子乱婬一级A片| 国产精品毛片VA一区二区三区| 阿拉伯三级片| 91无码人妻| 中日韩一级片| 操日本美女| 日韩h视频| 极品少妇av| 国产有码在线观看| 91丨露脸丨熟女精品| 水蜜桃网站| 日本无码中文字幕| 黄色成人网站免费在线观看| 超碰碰碰碰| 91蜜桃传媒在线观看| 国产精品国产三级囯产普通话2| 四川少妇搡BBw搡BBBB搡| 成人AV婷婷| 国产精品自拍视频| 亚洲AV成人片无码网站网蜜柚| 亚洲熟女视频| 国产一级黄| 日本中文字幕中文翻译歌词| 无码专区亚洲| 国产超级无码高清在线视频观看| 影视先锋久久| 欧美五月在线网址| 99中文字幕| 日本黄色视频免费观看| 免费网站观看www在线观看| 国产精品高潮无套内谢| 一本一本久久a久久精品牛牛影视 91无码人妻精品一区二区蜜桃 | 男女国产网站| 99热精品国产| 天天综合在线观看| 无码专区亚洲| 在线日韩国产| 肉色超薄丝袜脚交一区二区| 天天色小说| 欧美自拍性爱视频| 日韩一级黄色电影| 婷婷色在线| 精精品人妻一区二区三区| 天天操天天插| 亚洲欧美日本在线观看| 91精品在线免费观看| 北条麻妃JUX-869无码播放| av网站免费在线观看| 中文字幕东京热| 成人中文字幕网站| 麻豆传媒av| 精品欧美片在线观看步骤| 麻豆传媒视频观看| 免费看操逼| 麻豆国产91在线播放| 亚洲卡一卡二| 北条麻妃人妻中文无码| 91青青草视频| 欧美插逼视频| 亚洲成人无码一区| 在线观看日韩av| 久久久五月| 老师搡BBBB搡BBB| 嫩BBB槡BBBB槡BBBB免费视频| 浮力影院av| 中文字幕北条麻妃| 影音先锋麻豆传媒| 操B视频在线| 亚洲精品另类| 无码精品一区二区三区在线观看 | 无码AV高清| 青草伊人网| www.黄色| 日韩一级在线| 中文无码字幕| 桃色av| 亚洲另类视频| 91精品婷婷国产综合久久竹菊| 欧美精品成人| 亚洲艹| 久久精品小视频| 小處女末发育嫩苞AV| 久久久久久精| 高清无码三级| 做爱网站免费| 免费黄色成人视频| AV片在线观看| 日韩中出| 一区高清无码| 日韩AV免费看| 91国产爽黄在线| 亚洲专区视频| 香蕉久久a毛片| 人妻p| 天天av天天av天天爽| 久久精品小视频| 欧美一级内射| 免费a在线观看| 男人先锋| 色色色综合| 青春草在线观看| 国产免费一区二区三区| 热逼视频| 亚洲无码影视| www.亚洲无码| 国产午夜男女性爱| 欧美夜夜草视频| 免费看毛片中文字幕| 成人国产精品免费观看| 亚洲无码在线观看免费| 俺来也影院| 777偷窥盗摄00000| 成人无码日本动漫电影| 星空AV| www.蜜桃av| 国产小福利| 日日夜夜精选视频| 日本国产视频| 激情久久av| 欧美性爱手机在线| 精品国产午夜福利在线观看| 色色婷婷五月| 国产日韩欧美在线播放| 91精品大屁股白浆自慰久久久| 天天插天天射| 夜夜骑天天操| 成人A片在线观看| 青青草手机在线视频| 内射网站| 免费一级无码婬片A片AAA毛片| 肥臀AV在线| 亚洲高清福利| 淫香欲色| 久久福利电影| 免费的黄片| 大香蕉免费网| 红桃视频无码| 俺去草| 国产午夜福利在线| 免费在线成人网站| 日产久久视频| 操逼精品| 亚洲免费精品视频| 毛片黄色片| aa在线| 人人操狠狠操| 蜜桃传媒一区二区亚洲A| 亚洲AV播放| 很很干在线视频| 翔田AV无码秘三区| 人人摸人人干人人操| 99婷婷| 无码三级| 天天爽夜夜爽AA片免费| 国产三级偷拍| 极品久久久久| 自慰影院| 人人鲁人人操| 青娱乐AV在线| 国产精品久久久久久久久久久免费看 | 91性视频| 亚洲天堂在线免费观看| 无码免费视频| 麻豆黄网| 四川性BBB搡BBB爽爽爽小说| 人成在线视频| 秘亚洲国产精品成人网站| 亚洲成人Av| 欧美日韩无码| 制服丝袜人妻| 日韩在线| 西西www444无码大胆| 四川性BBB搡BBB爽爽爽小说| 国产精品毛片一区二区在线看| www俺来也com| 蜜桃av无码一区二区三区| 成片免费观看视频大全| 中文字幕综合| 炮友露脸青楼传媒刘颖儿| 亚洲成人在线视频观看| 围内精品久久久久久久久白丝制服 | 女人自慰网站在线观看| 日日夜夜草| 韩国无码视频在线观看| 午夜成人中文字幕| 特级西西444WWW视频| 怡红院男人天堂| 人妻少妇精品| 免费在线观看中文字幕| 超碰P| 亚洲A∨无码无在线观看| 中文字幕在线电影| 国产成人精品免高潮在线人与禽一| 国语对白做受欧美| 成人一级片| 色五月婷婷丁香五月| 自慰一区二区| 国产l精品久久久久久久久久| 北条麻妃无码一区二区| 久久国产劲爆∧v内射| 天天色图| 午夜成人黄色| 中文字幕国产在线观看| 91视频你懂的| 日韩无码黄片| 婷婷色在线| 人妻无码中文久久久久专区| 日逼高清视频| 蜜桃传媒一区二区亚洲A| www.亚洲| 午夜综合| 成人在线网站| 青青草国产在线视频| 国产成人综合电影| 久久午夜福利电影| 97国产精品视频| 午夜操一操| 精品视频在线免费| 国产69精品久久| 亚洲精品乱码久久久久久| 成人乱无码AV在线观看| 国产久久久| www.伊人网| wwwwww黄| av在线资源观看| 天天草天天射| 大秀91视频| AV无码人妻| 久久久久久黄色| 懂色av粉嫩av蜜臀av| 欧美视频一区二区| 蜜臀AV成人| 有码视频在线观看| 日逼欧美| 亚洲色综合| 五月天毛片| 九九九九九九精品视频| 嘉兴少妇按摩69XX| 91丨国产丨精品丨丝袜| 亚洲成人黄色电影| 色五月婷婷视频| 亚洲成人电影一区| 日韩性爱视频在线观看| 国产乱子伦真实精品| 青青草手机视频在线| 大香蕉少妇| 无码日韩视频| 91精品人妻一区二区三区| 欧美黄片免费视频| 无套内射在线| 蜜臀网在线| 成人性生活免费视频| 大香蕉大香蕉大香蕉| 国产精品在线看| 国产麻豆性爱视频| 欧美手机在线视频| A级毛片网站| 丁香AV| 一级黄色毛片视频| 人妻人人爱| 欧美久草蜜桃视频| 中文字幕中文| 亚洲欧美日韩一区| 亚洲在线免费| 亚洲中文字幕无码爆乳av| 无码aⅴ| 99re在线视频观看| 成人自拍视频| 高清免费在线中文Av| 久久久久久91| 亚洲女人被黑人巨大进入| 午夜视频在线| 日韩无码视频一区二区| 亚洲无码视频一区二区| 婷婷射| 大香蕉在线视频观看| 人人看AV| 亚洲无aV在线中文字幕| 极品小仙女69| 中文字幕AV在线免费观看| 探花视频在线观看| 天天久久综合| 日韩精品成人在线视频| 中国特级毛片| 日本黄色小视频| 俺来了俺去了www色官网| 免费的黄片| 久久久久久久久免费看无码| 一级黄色毛片| 五月婷婷五月丁香| 国产三级片自拍| 免费亚洲婷婷| 操B视频在线播放| 久久99精品久久久久久水蜜桃 | 人妻懂色av粉嫩av浪潮av| 久久婷婷五月综合伊人| 91AV在线看| 松岛枫在线视频| 免费性片| 天天色av| 日韩美女做爱| 大学生一级特黄大片| 亚洲一区二区av| 在线免费小黄片| 欧美日韩亚洲成人| 日韩五月天| 一区二区Av| 粉嫩小泬BBBBBB免费看| 婷婷中文在线| 日韩一区二区AV| 久久婷婷成人综合色怡春院| 国产乱子伦一区二区三精品| 乱子伦一区二区三区视频在线观看 | 老司机无码| 国产成人亚洲综合AV婷婷| 亚洲天堂无码视频| 秋霞网一区二区| 国产操逼大全| 成人做爰A片一区二区| 久久依人大香蕉| 欧美成人在线视频网站| 成人在线无码| 精品视频免费在线| 久操无码| 欧美色大香蕉| 亚洲精品一区二区三区四区五区六区| 人人看人人做| 人妻公日日澡久久久| 9久9久9久9久女女女女| 欧美精品成人| 日韩无码第四页| 免费在线观看黄色片| 天天色综| 亚洲爱爱网站| av无码电影| 欧美成人aaa| 免费久草视频| 国产亚洲色婷婷| 精品人人人人| 西西人体444rt高清大胆模特| 无码免费一区二区三区| 国产熟女在线| 亚洲第一页在线观看| 蜜臀久久99精品久久一区二区| 无码欧美成人| 婷婷色导航| 清清草在线视频| 亚洲精品成人在线| 国产女人精品视频| 亚洲色射| 亚洲AV无码A片在线观看蜜桃|