精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

告別 Spring Security!Sa-Token + Gateway + Nacos 極簡鑒權實戰(zhàn)

開發(fā) 前端
還真有!今天咱就聊個 “極簡派” 方案 ——Sa-Token + Gateway + Nacos,不用復雜配置,不用繞彎子,5 步搞定全鏈路鑒權,看完你絕對會說:“早知道這玩意兒,誰還折騰 Spring Security 啊!”

兄弟們,作為 Java 開發(fā)者,誰沒在 Spring Security 上栽過跟頭啊?明明就想做個 “判斷用戶能不能訪問接口” 的簡單需求,結果一打開文檔,又是 OAuth2、又是 JWT、又是 SecurityContextHolder,配置文件寫了一大堆,還動不動就報個 “403 Forbidden” 找不著北。

我之前就踩過這坑:為了加個簡單的 token 驗證,硬著頭皮啃了三天 Spring Security 文檔,配置類堆了快 200 行,最后還因為 “權限注解沒掃描到” 卡了一下午。當時就想:就沒有個 “開箱能用、配置簡單、報錯還能看懂” 的鑒權框架嗎?

還真有!今天咱就聊個 “極簡派” 方案 ——Sa-Token + Gateway + Nacos,不用復雜配置,不用繞彎子,5 步搞定全鏈路鑒權,看完你絕對會說:“早知道這玩意兒,誰還折騰 Spring Security 啊!”

一、先搞懂:為啥選這仨組合?

在擼代碼之前,咱先掰扯清楚:這三個工具各自是干啥的?湊一起為啥這么牛?

1. Sa-Token:鑒權界的 “小清新”

Sa-Token 這玩意兒,官網(wǎng)一句話總結得特到位:“一個輕量級 Java 權限認證框架,讓鑒權變得簡單、優(yōu)雅”。咱用大白話翻譯下:

  • 不用寫復雜配置:Spring Security 要配 “安全鏈、認證管理器、權限過濾器”,Sa-Token 一行代碼搞定登錄 ——StpUtil.login(userId),沒了。
  • 功能全還不啰嗦:token 過期、刷新、角色權限、單點登錄,這些常用功能它都有,而且 API 長得特直觀,比如判斷角色就是StpUtil.hasRole("admin"),判斷權限就是StpUtil.hasPermission("user:add"),誰看誰懂。
  • 報錯信息賊友好:Spring Security 報 “AccessDeniedException”,你還得猜是 “沒登錄” 還是 “沒權限”;Sa-Token 直接給你報 “未登錄,請先登錄”“無此權限,請聯(lián)系管理員”,連排查方向都給你指好了。

簡單說:Sa-Token 就是把 “鑒權” 這件事,從 “需要解密的復雜工程” 變成了 “擰瓶蓋級別的簡單操作”。

2. Gateway:流量入口的 “守門神”

Gateway 咱都熟,Spring Cloud 全家桶里的網(wǎng)關,負責 “轉發(fā)請求、攔截請求、統(tǒng)一處理跨域”。為啥鑒權要帶它玩?

你想啊:如果每個微服務都自己做鑒權,那不是重復勞動嗎?用戶訪問 “訂單服務” 要驗 token,訪問 “用戶服務” 還要驗 token,萬一 token 規(guī)則改了,所有服務都得改一遍,這不瘋了?

Gateway 作為 “所有請求的入口”,剛好能把 “鑒權邏輯” 抽出來統(tǒng)一處理:所有請求先經(jīng)過 Gateway,驗完 token 沒問題了再轉發(fā)到具體服務,有問題直接在網(wǎng)關層就打回去。這樣一來,后面的微服務根本不用管鑒權的事兒,專心搞業(yè)務就行 —— 這才叫 “解耦” 嘛!

3. Nacos:配置界的 “變形金剛”

Nacos 咱也熟,配置中心 + 服務發(fā)現(xiàn)。它在這組合里干啥用?

鑒權場景里,有很多 “經(jīng)常變的配置”:比如 “哪些接口不用驗 token(像登錄、注冊接口)”“token 過期時間設多久”“黑名單 IP 列表”。如果這些配置寫死在代碼里,改一次就得重啟服務,多麻煩?

Nacos 剛好能解決這問題:把這些動態(tài)配置放到 Nacos 上,服務啟動時從 Nacos 拉取,配置改了還能實時刷新,不用重啟服務。比如你想臨時開放某個測試接口,直接在 Nacos 上改 “排除攔截列表”,10 秒內生效,多爽!

總結下:這仨組合的優(yōu)勢

  • 簡單:Sa-Token 讓鑒權代碼量減少 80%,新手也能上手。
  • 統(tǒng)一:Gateway 集中處理鑒權,微服務不用重復造輪子。
  • 靈活:Nacos 動態(tài)配置,改規(guī)則不用重啟服務。
  • 穩(wěn)定:都是經(jīng)過大量實踐的成熟框架,踩坑概率低。

好了,廢話不多說,咱直接上實戰(zhàn) —— 從 0 到 1 搭一個完整的鑒權系統(tǒng),保證你跟著做就能跑通!

二、實戰(zhàn)準備:環(huán)境搭好,少走彎路

先把 “彈藥” 備齊,避免等會兒擼代碼的時候 “缺這少那”。咱用的版本都是經(jīng)過驗證的,兼容性沒問題,別瞎換版本踩坑!

1. 基礎環(huán)境

  • JDK:1.8(別問為啥不用 11,大部分公司還在 8 呢,實用為主)
  • Maven:3.6.3(版本太新可能和依賴不兼容)
  • Spring Boot:2.6.13(穩(wěn)定版,別用 2.7+,Gateway 有些配置不一樣)
  • Spring Cloud:2021.0.5(和 Boot 2.6.x 匹配)
  • Spring Cloud Alibaba:2021.0.5.0(Nacos 用這個版本不報錯)

2. 核心依賴清單

后面搭項目會用到這些依賴,先列出來讓你有個底,不用記,后面直接復制粘貼就行:

依賴名稱

作用

sa-token-spring-boot-starter

Sa-Token 核心依賴,開箱即用

sa-token-reactor-spring-boot-starter

Sa-Token 適配 Gateway 的依賴(關鍵)

spring-cloud-starter-gateway

Gateway 核心依賴

com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config

Nacos 配置中心依賴

com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery

Nacos 服務發(fā)現(xiàn)依賴(可選,這次實戰(zhàn)用不上,但建議加)

lombok

省代碼神器,不用寫 getter/setter

spring-boot-starter-web

但注意:Gateway 是基于 WebFlux 的,別加 spring-boot-starter-web,會沖突!

3. 項目結構

咱這次搭個 “多模塊項目”,結構清晰,也符合實際開發(fā)場景:

sa-token-auth-demo
├── sa-token-auth-parent(父工程,管理依賴版本)
├── sa-token-auth-gateway(網(wǎng)關模塊,核心鑒權邏輯在這)
└── sa-token-auth-service(業(yè)務服務模塊,比如用戶服務,演示鑒權效果)

為啥這么分?因為實際項目里,網(wǎng)關和業(yè)務服務肯定是分開部署的,咱這么搭更貼近真實場景。

三、第一步:搭父工程,統(tǒng)一管理依賴

先搞父工程,把所有依賴的版本定好,后面子模塊直接繼承就行,不用每個模塊都寫版本號,避免版本混亂。

1. 創(chuàng)建父工程(sa-token-auth-parent)

新建一個 Maven 項目,打包方式選pom(父工程都是 pom 打包),然后修改pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- 父工程坐標,自己改groupId和artifactId -->
    <groupId>com.example</groupId>
    <artifactId>sa-token-auth-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>sa-token-auth-parent</name>
    <description>Sa-Token+Gateway+Nacos鑒權實戰(zhàn)父工程</description>
    <!-- 統(tǒng)一管理依賴版本 -->
    <properties>
        <java.version>1.8</java.version>
        <spring-boot.version>2.6.13</spring-boot.version>
        <spring-cloud.version>2021.0.5</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
        <sa-token.version>1.34.0</sa-token.version>
        <lombok.version>1.18.24</lombok.version>
    </properties>
    <!--  dependencyManagement:只管理版本,不實際引入依賴 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot 父依賴 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Spring Cloud 依賴 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Spring Cloud Alibaba 依賴 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Sa-Token 依賴 -->
            <dependency>
                <groupId>cn.dev33</groupId>
                <artifactId>sa-token-spring-boot-starter</artifactId>
                <version>${sa-token.version}</version>
            </dependency>
            <dependency>
                <groupId>cn.dev33</groupId>
                <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
                <version>${sa-token.version}</version>
            </dependency>
            <!-- Lombok 依賴 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!-- 子模塊聲明:后面加子模塊的時候要在這寫 -->
    <modules>
        <module>sa-token-auth-gateway</module>
        <module>sa-token-auth-service</module>
    </modules>
</project>

這里要注意:dependencyManagement標簽只是 “管理版本”,子模塊要實際引入依賴還得寫dependency標簽,只是不用寫版本號了 —— 這是 Maven 父工程的常規(guī)操作,老司機都懂,新手記著就行。

四、第二步:搭網(wǎng)關模塊,實現(xiàn)統(tǒng)一鑒權

網(wǎng)關模塊(sa-token-auth-gateway)是這次實戰(zhàn)的核心,所有鑒權邏輯都在這處理。咱分三步走:先搭基礎框架,再配 Sa-Token 鑒權,最后整合 Nacos 動態(tài)配置。

1. 創(chuàng)建網(wǎng)關模塊(sa-token-auth-gateway)

在父工程下新建一個 Maven 子模塊,artifactId 設為sa-token-auth-gateway,然后修改它的pom.xml,引入依賴:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>sa-token-auth-parent</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>sa-token-auth-gateway</artifactId>
    <name>sa-token-auth-gateway</name>
    <description>網(wǎng)關模塊:統(tǒng)一鑒權入口</description>
    <dependencies>
        <!-- Gateway 核心依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- Sa-Token 核心依賴 + Gateway適配依賴 -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
        </dependency>
        <!-- Nacos 配置中心依賴 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- Spring Boot 測試依賴(可選) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <!-- 打包插件,不然SpringBoot項目跑不起來 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

這里有個坑要注意:Gateway 是基于 WebFlux 的,千萬不能引入spring-boot-starter-web依賴,不然會沖突!如果不小心加了,趕緊刪掉,不然啟動會報 “Circular view path” 之類的錯。

2. 寫網(wǎng)關啟動類

新建一個啟動類GatewayApplication.java,很簡單,就加個@SpringBootApplication注解:

package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * 網(wǎng)關啟動類
 */
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
        System.out.println("網(wǎng)關啟動成功!??");
    }
}

3. 配置 Nacos 連接(關鍵!)

因為要從 Nacos 拉取配置,所以得先配置 Nacos 的地址。在src/main/resources下新建bootstrap.yml文件(注意是 bootstrap.yml,不是 application.yml,因為 bootstrap 加載優(yōu)先級更高,要先連 Nacos):

# bootstrap.yml:先加載這個文件,連接Nacos
spring:
  application:
    name: sa-token-auth-gateway  # 服務名,后面Nacos配置要用到
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848  # Nacos地址,本地搭的話就是這個
        file-extension: yaml  # 配置文件格式,yaml或properties
        group: DEFAULT_GROUP  # 配置分組,默認DEFAULT_GROUP
        namespace:  # Nacos命名空間,默認是空,不用改(如果自己建了命名空間就填ID)
      discovery:
        server-addr: ${spring.cloud.nacos.config.server-addr}  # 服務發(fā)現(xiàn)地址,和配置中心一樣
# 日志配置:讓Sa-Token的日志打印出來,方便排查問題
logging:
  level:
    cn.dev33: debug  # Sa-Token的包日志級別設為debug

這里要先確保你本地的 Nacos 已經(jīng)啟動了!如果還沒裝 Nacos,趕緊去官網(wǎng)下載個 1.4.3 版本(穩(wěn)定),解壓后雙擊bin/startup.cmd(Windows)或bin/startup.sh(Linux)就能啟動,默認端口 8848,訪問http://localhost:8848/nacos,賬號密碼都是 nacos。

4. 在 Nacos 上創(chuàng)建網(wǎng)關配置

啟動 Nacos 后,登錄控制臺,點擊左側 “配置管理”→“配置列表”→“+” 號,新建配置:

  • Data ID:sa-token-auth-gateway.yaml(格式:服務名。文件格式,和 bootstrap.yml 里的配置對應)
  • Group:DEFAULT_GROUP(和 bootstrap.yml 里的 group 對應)
  • 配置格式:YAML
  • 配置內容:下面這段,包含 Gateway 路由配置和 Sa-Token 基礎配置
# Nacos上的sa-token-auth-gateway.yaml配置
server:
  port: 8080  # 網(wǎng)關端口,后面訪問都走這個端口
spring:
  cloud:
    gateway:
      # 路由配置:把請求轉發(fā)到對應的業(yè)務服務
      routes:
        # 路由1:轉發(fā)到用戶服務(sa-token-auth-service)
        - id: user-service-route  # 路由ID,唯一就行
          uri: http://localhost:8081  # 業(yè)務服務地址(實際項目用服務名,這里先寫固定地址)
          predicates:  # 路由匹配規(guī)則:請求路徑以/api/user開頭的,都走這個路由
            - Path=/api/user/**
          filters:  # 過濾器:給請求加個前綴(可選,看業(yè)務需求)
            - StripPrefix=1  # 去掉路徑的第一個前綴,比如/api/user/info變成/user/info
# Sa-Token 核心配置
sa-token:
  # token名稱(Header里的key)
  token-name: Authorization
  # token有效期(單位:秒),默認30天,這里設1小時方便測試
  timeout: 3600
  # token過期后是否允許刷新,默認true
  is-refresh-token: true
  # 刷新token的有效時間(單位:秒),默認7天,這里設2小時
  refresh-token-timeout: 7200
  # 排除攔截的路徑(不用登錄就能訪問的接口)
  exclude-path-patterns:
    - /api/user/login  # 登錄接口
    - /api/user/register  # 注冊接口
    - /doc.html  # Swagger文檔(如果加了的話)
    - /webjars/**  # Swagger靜態(tài)資源
    - /v3/api-docs/**  # Swagger接口文檔
  # 是否在控制臺打印日志,默認false
  is-log: true

配置完點擊 “發(fā)布”,這樣網(wǎng)關啟動時就會從 Nacos 拉取這些配置了。

5. 寫 Sa-Token 網(wǎng)關鑒權過濾器

這步是核心!要在 Gateway 里加一個 Sa-Token 的過濾器,實現(xiàn) “所有請求先驗 token,沒 token 或 token 無效就攔截” 的邏輯。

新建一個配置類SaTokenGatewayConfig.java:

package com.example.gateway.config;
import cn.dev33.satoken.reactor.filter.SaTokenGatewayFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
 * Sa-Token網(wǎng)關鑒權配置
 */
@Configuration
public class SaTokenGatewayConfig {
    /**
     * 注冊Sa-Token網(wǎng)關過濾器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)  // 優(yōu)先級設最高,確保先執(zhí)行鑒權
    public WebFilter saTokenGatewayFilter() {
        return new SaTokenGatewayFilter()
                // 配置攔截規(guī)則:除了exclude-path-patterns里的路徑,其他都要鑒權
                .addAuth(obj -> {
                    // 1. 獲取當前請求路徑
                    ServerWebExchange exchange = (ServerWebExchange) obj;
                    String path = exchange.getRequest().getURI().getPath();
                    System.out.println("當前請求路徑:" + path);
                    // 2. 路由匹配:排除不需要鑒權的路徑
                    SaRouter.match("/**", stp -> {
                        // 3. 執(zhí)行鑒權:檢查是否登錄(如果需要角色/權限,這里可以加StpUtil.hasRole("admin")等)
                        StpUtil.checkLogin();
                        // (可選)如果需要更細粒度的權限控制,比如某個路徑需要特定角色
                        // SaRouter.match("/api/admin/**", () -> StpUtil.hasRole("admin"));
                    })
                    // 排除不需要鑒權的路徑(和Nacos里的exclude-path-patterns對應,雙重保險)
                    .notMatch("/api/user/login", "/api/user/register", "/doc.html", "/webjars/**", "/v3/api-docs/**")
                    .doAuth();
                })
                // 配置未登錄的處理邏輯
                .setUnauthorizedHandler(obj -> {
                    ServerWebExchange exchange = (ServerWebExchange) obj;
                    // 設置響應狀態(tài)碼401(未授權)
                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    // 返回JSON提示:未登錄
                    return SaResult.error("未登錄,請先登錄!").toMono(exchange);
                })
                // 配置無權限的處理邏輯
                .setAccessDeniedHandler(obj -> {
                    ServerWebExchange exchange = (ServerWebExchange) obj;
                    // 設置響應狀態(tài)碼403(禁止訪問)
                    exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                    // 返回JSON提示:無權限
                    return SaResult.error("無此權限,請聯(lián)系管理員!").toMono(exchange);
                });
    }
    /**
     * 配置跨域(前后端分離必加,不然前端調接口會報跨域錯)
     */
    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange exchange, WebFilterChain chain) -> {
            // 允許所有來源(實際項目要寫具體的前端地址,比如http://localhost:8080)
            exchange.getResponse().getHeaders().add("Access-Control-Allow-Origin", "*");
            // 允許的請求頭
            exchange.getResponse().getHeaders().add("Access-Control-Allow-Headers", "*");
            // 允許的請求方法
            exchange.getResponse().getHeaders().add("Access-Control-Allow-Methods", "*");
            // 允許攜帶Cookie(如果需要的話)
            exchange.getResponse().getHeaders().add("Access-Control-Allow-Credentials", "true");
            // 預檢請求的緩存時間(秒),避免頻繁發(fā)預檢請求
            exchange.getResponse().getHeaders().add("Access-Control-Max-Age", "3600");
            // 如果是預檢請求(OPTIONS),直接返回成功
            if ("OPTIONS".equals(exchange.getRequest().getMethodValue())) {
                exchange.getResponse().setStatusCode(HttpStatus.OK);
                return Mono.empty();
            }
            // 不是預檢請求,繼續(xù)走過濾鏈
            return chain.filter(exchange);
        };
    }
}

這段代碼要重點說下:

  • SaTokenGatewayFilter:Sa-Token 專門為 Gateway 提供的過濾器,不用自己寫復雜的攔截邏輯。
  • addAuth:配置鑒權規(guī)則,StpUtil.checkLogin()就是 “檢查是否登錄”,一行代碼搞定核心鑒權。
  • setUnauthorizedHandler:沒登錄時的處理,返回 401 和 “未登錄” 提示,前端能直接拿到。
  • setAccessDeniedHandler:沒權限時的處理,返回 403 和 “無權限” 提示。
  • corsFilter:跨域配置,前后端分離項目必加,不然前端調接口會報 “Access to XMLHttpRequest at ... from origin ... has been blocked by CORS policy” 錯。

6. 測試網(wǎng)關鑒權(先跑通基礎流程)

現(xiàn)在網(wǎng)關模塊基本搭好了,咱先啟動網(wǎng)關,測試下鑒權邏輯:

  • 啟動 Nacos(確保配置已發(fā)布)。
  • 啟動網(wǎng)關模塊(GatewayApplication),控制臺看到 “網(wǎng)關啟動成功!??” 就說明沒問題。
  • 用 Postman 或瀏覽器訪問 “不需要鑒權的接口”,比如http://localhost:8080/api/user/login(雖然業(yè)務服務還沒寫,但網(wǎng)關會轉發(fā)請求,此時會報 “503 Service Unavailable”,因為業(yè)務服務沒啟動,這是正常的)。
  • 訪問 “需要鑒權的接口”,比如http://localhost:8080/api/user/info,此時網(wǎng)關會攔截,返回:
{
    "code": 401,
    "msg": "未登錄,請先登錄!",
    "data": null
}

這就對了!說明鑒權過濾器生效了 —— 沒登錄的請求被攔截了。

五、第三步:搭業(yè)務服務,演示鑒權效果

網(wǎng)關搭好了,現(xiàn)在要搭個業(yè)務服務(sa-token-auth-service),寫個登錄接口和需要鑒權的接口,演示 “登錄獲取 token→攜帶 token 訪問接口” 的完整流程。

1. 創(chuàng)建業(yè)務服務模塊(sa-token-auth-service)

在父工程下新建 Maven 子模塊,artifactId 設為sa-token-auth-service,修改pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>sa-token-auth-parent</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sa-token-auth-service</artifactId>
    <name>sa-token-auth-service</name>
    <description>業(yè)務服務模塊:用戶服務示例</description>

    <dependencies>
        <!-- Spring Boot Web依賴(業(yè)務服務用Web,網(wǎng)關用WebFlux,不沖突) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Sa-Token 核心依賴(業(yè)務服務也要加,用來操作登錄、判斷權限) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
        </dependency>

        <!-- Nacos 配置中心依賴(可選,業(yè)務服務如果要動態(tài)配置也可以加) -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- Spring Boot 測試依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

這里注意:業(yè)務服務用的是spring-boot-starter-web(基于 Servlet),網(wǎng)關用的是spring-cloud-starter-gateway(基于 WebFlux),兩者不沖突,因為是不同的模塊。

2. 寫業(yè)務服務啟動類

新建UserServiceApplication.java:

package com.example.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 業(yè)務服務啟動類(用戶服務)
 */
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
        System.out.println("用戶服務啟動成功!??");
    }
}

3. 配置業(yè)務服務(application.yml)

新建src/main/resources/application.yml:

server:
  port: 8081  # 業(yè)務服務端口,和網(wǎng)關路由里的uri對應

spring:
  application:
    name: sa-token-auth-service # 服務名

# Sa-Token 配置(和網(wǎng)關保持一致,比如token名稱)
sa-token:
  token-name: Authorization # 和網(wǎng)關的token-name一致,不然解析不到token
  is-log: true # 打印日志,方便排查

4. 寫核心業(yè)務代碼(登錄 + 用戶信息接口)

咱寫個簡單的用戶服務,包含三個接口:

  • 登錄接口:/user/login(不用鑒權,返回 token)
  • 用戶信息接口:/user/info(需要鑒權,返回當前登錄用戶信息)
  • 注冊接口:/user/register(不用鑒權,模擬注冊)

(1)定義用戶實體類

新建entity/User.java:

package com.example.service.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 用戶實體類
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
publicclass User {
    private Long id; // 用戶ID
    privateString username; // 用戶名
    privateString password; // 密碼(實際項目要加密,這里演示用明文)
    privateString role; // 角色(比如admin、user)
}

(2)寫用戶服務(模擬數(shù)據(jù)庫操作)

新建service/UserService.java:

package com.example.service.service;

import com.example.service.entity.User;
import org.springframework.stereotype.Service;

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

/**
 * 用戶服務(模擬數(shù)據(jù)庫操作,實際項目要連MySQL)
 */
@Service
publicclass UserService {

    // 模擬數(shù)據(jù)庫:存儲用戶信息
    privatestatic final Map<String, User> USER_MAP = new HashMap<>();

    // 初始化數(shù)據(jù):加個測試用戶(username: test, password: 123456)
    static {
        USER_MAP.put("test", new User(1L, "test", "123456", "user"));
        USER_MAP.put("admin", new User(2L, "admin", "admin123", "admin"));
    }

    /**
     * 登錄:根據(jù)用戶名和密碼查詢用戶
     */
    public User login(String username, String password) {
        // 1. 從模擬數(shù)據(jù)庫獲取用戶
        User user = USER_MAP.get(username);
        // 2. 判斷用戶是否存在,密碼是否正確
        if (user == null || !user.getPassword().equals(password)) {
            returnnull; // 登錄失敗
        }
        return user; // 登錄成功
    }

    /**
     * 注冊:新增用戶到模擬數(shù)據(jù)庫
     */
    publicboolean register(String username, String password) {
        // 1. 判斷用戶名是否已存在
        if (USER_MAP.containsKey(username)) {
            returnfalse; // 用戶名已存在,注冊失敗
        }
        // 2. 新增用戶(ID用UUID簡化,實際項目用自增ID)
        User newUser = new User(
                Long.parseLong(UUID.randomUUID().toString().substring(0, 8), 16),
                username,
                password,
                "user"http:// 新用戶默認角色是user
        );
        USER_MAP.put(username, newUser);
        returntrue; // 注冊成功
    }

    /**
     * 根據(jù)用戶名獲取用戶信息(用于登錄后查詢)
     */
    public User getUserByUsername(String username) {
        return USER_MAP.get(username);
    }
}

(3)寫控制器(接口)

新建controller/UserController.java:

package com.example.service.controller;

import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.example.service.entity.User;
import com.example.service.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 用戶控制器:提供登錄、注冊、用戶信息接口
 */
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor// Lombok注解:自動注入依賴,不用寫@Autowired
publicclass UserController {

    // 注入用戶服務
    private final UserService userService;

    /**
     * 登錄接口
     * 請求地址:http://localhost:8080/api/user/login(通過網(wǎng)關訪問)
     * 請求參數(shù):username(用戶名),password(密碼)
     */
    @PostMapping("/login")
    public SaResult login(String username, String password) {
        // 1. 調用服務層驗證用戶名密碼
        User user = userService.login(username, password);
        if (user == null) {
            return SaResult.error("用戶名或密碼錯誤!");
        }

        // 2. 登錄成功:調用Sa-Token的login方法,傳入用戶ID(這里用用戶名當ID,實際項目用用戶表的ID)
        StpUtil.login(user.getUsername());

        // 3. 獲取token(Sa-Token自動生成)
        String token = StpUtil.getTokenValue();

        // 4. 返回結果:token + 用戶信息(脫敏,不要返回密碼)
        Map<String, Object> data = new HashMap<>();
        data.put("token", token);
        data.put("user", new HashMap<String, Object>() {{
            put("id", user.getId());
            put("username", user.getUsername());
            put("role", user.getRole());
        }});

        return SaResult.ok("登錄成功!").setData(data);
    }

    /**
     * 注冊接口
     * 請求地址:http://localhost:8080/api/user/register(通過網(wǎng)關訪問)
     * 請求參數(shù):username(用戶名),password(密碼)
     */
    @PostMapping("/register")
    public SaResult register(String username, String password) {
        // 1. 調用服務層注冊用戶
        boolean success = userService.register(username, password);
        if (!success) {
            return SaResult.error("用戶名已存在!");
        }
        return SaResult.ok("注冊成功!");
    }

    /**
     * 獲取當前登錄用戶信息(需要鑒權)
     * 請求地址:http://localhost:8080/api/user/info(通過網(wǎng)關訪問)
     * 請求頭:Authorization: token(登錄時返回的token)
     */
    @GetMapping("/info")
    public SaResult getUserInfo() {
        // 1. 獲取當前登錄用戶的ID(這里是用戶名,因為登錄時傳的是用戶名)
        String username = (String) StpUtil.getLoginId();

        // 2. 根據(jù)用戶名查詢用戶信息
        User user = userService.getUserByUsername(username);
        if (user == null) {
            return SaResult.error("用戶不存在!");
        }

        // 3. 返回用戶信息(脫敏)
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("id", user.getId());
        userInfo.put("username", user.getUsername());
        userInfo.put("role", user.getRole());
        userInfo.put("tokenTimeout", StpUtil.getTokenTimeout()); // 返回token剩余有效期(秒)

        return SaResult.ok("獲取用戶信息成功!").setData(userInfo);
    }

    /**
     * 退出登錄接口(需要鑒權)
     * 請求地址:http://localhost:8080/api/user/logout(通過網(wǎng)關訪問)
     * 請求頭:Authorization: token
     */
    @PostMapping("/logout")
    public SaResult logout() {
        // 調用Sa-Token的退出方法,清除token
        StpUtil.logout();
        return SaResult.ok("退出登錄成功!");
    }
}

這段代碼里有個關鍵:StpUtil.login(user.getUsername())—— 這就是 Sa-Token 的登錄核心方法,傳入用戶唯一標識(這里用用戶名,實際項目用用戶 ID),Sa-Token 會自動生成 token,不用你自己處理 token 的生成、存儲邏輯,太省心了!

六、第四步:完整流程測試,驗證鑒權效果

現(xiàn)在網(wǎng)關和業(yè)務服務都搭好了,咱來測一遍完整流程,確保每個環(huán)節(jié)都沒問題。測試工具用 Postman(或 Apifox,都一樣)。

1. 啟動所有服務

  1. 啟動 Nacos(必須先啟動,不然網(wǎng)關和業(yè)務服務拉不到配置)。
  2. 啟動網(wǎng)關模塊(GatewayApplication,端口 8080)。
  3. 啟動業(yè)務服務模塊(UserServiceApplication,端口 8081)。

確保三個服務都啟動成功,控制臺沒有報錯。

2. 測試 1:注冊用戶

  • 請求地址:POST http://localhost:8080/api/user/register
  • 請求參數(shù):username=zhangsan&password=654321(用表單形式傳參)
  • 預期結果:返回 “注冊成功!”

實際返回:

{
    "code": 200,
    "msg": "注冊成功!",
    "data": null
}

注冊成功!說明 “不需要鑒權的接口” 能正常訪問。

3. 測試 2:登錄獲取 token

  • 請求地址:POST http://localhost:8080/api/user/login
  • 請求參數(shù):username=zhangsan&password=654321(用剛注冊的用戶,或測試用戶 test/123456)
  • 預期結果:返回 token 和用戶信息

實際返回(重點看data.token,后面要用到):

{
    "code": 200,
    "msg": "登錄成功!",
    "data": {
        "token": "satoken:623a232f-7f5a-4b5c-8d1e-9a0b1c2d3e4f", // 這是token,每個人的不一樣
        "user": {
            "id": 123456789,
            "username": "zhangsan",
            "role": "user"
        }
    }
}

登錄成功!拿到 token 了,下一步用這個 token 訪問需要鑒權的接口。

4. 測試 3:攜帶 token 訪問用戶信息接口

  • 請求地址:GET http://localhost:8080/api/user/info
  • 請求頭:Authorization: satoken:623a232f-7f5a-4b5c-8d1e-9a0b1c2d3e4f(把登錄返回的 token 填進去)
  • 預期結果:返回當前登錄用戶的信息

實際返回:

{
    "code": 200,
    "msg": "獲取用戶信息成功!",
    "data": {
        "id": 123456789,
        "username": "zhangsan",
        "role": "user",
        "tokenTimeout": 3580 // token剩余有效期(秒),因為設置的是3600秒,過了20秒
    }
}

完美!說明鑒權通過了,網(wǎng)關正確識別了 token,業(yè)務服務正確獲取了當前登錄用戶。

5. 測試 4:不攜帶 token 訪問需要鑒權的接口

  • 請求地址:GET http://localhost:8080/api/user/info
  • 不填Authorization請求頭
  • 預期結果:網(wǎng)關攔截,返回 “未登錄,請先登錄!”

實際返回:

{
    "code": 401,
    "msg": "未登錄,請先登錄!",
    "data": null
}

正確!鑒權攔截生效了。

6. 測試 5:攜帶無效 token 訪問

  • 請求地址:GET http://localhost:8080/api/user/info
  • 請求頭:Authorization: invalid-token(隨便寫個無效的 token)
  • 預期結果:返回 “未登錄,請先登錄!”(Sa-Token 會識別無效 token 為未登錄)

實際返回和測試 4 一樣,正確。

7. 測試 6:退出登錄后訪問接口

  • 先調用退出登錄接口:POST http://localhost:8080/api/user/logout,請求頭帶之前的 token,返回 “退出登錄成功!”。
  • 再用同一個 token 訪問/api/user/info,預期結果:返回 “未登錄,請先登錄!”。

實際返回正確,說明退出登錄后 token 失效了,鑒權邏輯沒問題。

七、第五步:Nacos 動態(tài)配置實戰(zhàn)(進階)

前面咱把 Nacos 搭好了,現(xiàn)在來演示 “動態(tài)修改鑒權規(guī)則,不用重啟服務”—— 這才是 Nacos 的核心價值之一。

1. 需求:臨時開放一個測試接口,不用鑒權

比如業(yè)務服務加了個/user/test接口,想臨時開放,不用登錄就能訪問,怎么用 Nacos 動態(tài)配置實現(xiàn)?

(1)業(yè)務服務加測試接口

在UserController里加一個接口:

/**
 * 測試接口(臨時開放,不用鑒權)
 * 請求地址:http://localhost:8080/api/user/test
 */
@GetMapping("/test")
public SaResult test() {
    return SaResult.ok("這是臨時開放的測試接口,不用登錄就能訪問!");
}

重啟業(yè)務服務(這次是因為加了接口,實際改配置不用重啟)。

(2)不修改配置時訪問測試接口

用 Postman 訪問GET http://localhost:8080/api/user/test,不攜帶 token,預期結果:網(wǎng)關攔截,返回 “未登錄”。

實際返回確實是 “未登錄”,因為/api/user/test不在 Nacos 的exclude-path-patterns里。

(3)在 Nacos 上動態(tài)修改配置

登錄 Nacos 控制臺,找到sa-token-auth-gateway.yaml配置,修改sa-token.exclude-path-patterns,加上/api/user/test:

sa-token:
  # 其他配置不變,只加一行
  exclude-path-patterns:
    - /api/user/login
    - /api/user/register
    - /doc.html
    - /webjars/**
    - /v3/api-docs/**
    - /api/user/test # 新增:測試接口不用鑒權

點擊 “發(fā)布”,不用重啟網(wǎng)關!

(4)再次訪問測試接口

還是訪問GET http://localhost:8080/api/user/test,不攜帶 token,預期結果:返回測試接口的信息。

實際返回:

{
    "code": 200,
    "msg": "這是臨時開放的測試接口,不用登錄就能訪問!",
    "data": null
}

成了!配置改了 10 秒內就生效了,不用重啟網(wǎng)關,這就是動態(tài)配置的魅力!

2. 再試一個:動態(tài)修改 token 有效期

比如想把 token 有效期從 1 小時(3600 秒)改成 2 小時(7200 秒),直接在 Nacos 上改sa-token.timeout:

sa-token:
  timeout: 7200  # 從3600改成7200
  # 其他配置不變

發(fā)布后,新登錄的用戶 token 有效期就是 2 小時了,老用戶的 token 還是按之前的 1 小時算 —— 這很合理,動態(tài)配置只對新生成的 token 生效。

八、實戰(zhàn)踩坑指南(必看!)

咱實戰(zhàn)過程中肯定會遇到坑,我把我踩過的坑整理出來,幫你少走彎路:

1. 網(wǎng)關啟動報 “Circular view path” 錯

  • 原因:網(wǎng)關模塊引入了spring-boot-starter-web依賴,和 Gateway 的 WebFlux 沖突了。
  • 解決:刪掉網(wǎng)關模塊的spring-boot-starter-web依賴,只留spring-cloud-starter-gateway。

2. 網(wǎng)關拉不到 Nacos 配置,報 “Could not resolve placeholder” 錯

  • 原因 1:bootstrap.yml沒寫對,比如 Nacos 地址錯了,或者 Data ID 和服務名不匹配。
  • 原因 2:Nacos 里的配置沒發(fā)布,或者 Group、Namespace 和bootstrap.yml里的不一致。
  • 解決:檢查bootstrap.yml的spring.application.name和 Nacos 的 Data ID 是否一致(Data ID 是 “服務名。文件格式”),檢查 Nacos 地址是否正確,配置是否發(fā)布。

3. 攜帶 token 訪問接口,還是返回 “未登錄”

  • 原因 1:請求頭的 key 和 Sa-Token 配置的token-name不一致,比如配置的是Authorization,請求頭寫的是token。
  • 原因 2:token 傳錯了,或者 token 已經(jīng)過期 / 被退出登錄了。
  • 原因 3:網(wǎng)關的exclude-path-patterns配置錯了,把需要鑒權的路徑加進去了。
  • 解決:檢查請求頭 key 是否和sa-token.token-name一致,重新登錄獲取新 token,檢查 Nacos 的exclude-path-patterns配置。

4. 跨域問題,前端調接口報 “CORS policy” 錯

  • 原因:網(wǎng)關沒配置跨域過濾器,或者跨域配置不正確。
  • 解決:參考前面的corsFilter配置,確保Access-Control-Allow-Origin、Access-Control-Allow-Headers、Access-Control-Allow-Methods都配置對了,預檢請求(OPTIONS)要返回 200。

5. Nacos 配置修改后不生效

  • 原因 1:沒加spring-cloud-starter-alibaba-nacos-config依賴,或者依賴版本不對。
  • 原因 2:bootstrap.yml里沒配置 Nacos 的server-addr,或者配置錯了。
  • 解決:檢查依賴是否正確,檢查bootstrap.yml的 Nacos 地址是否正確,配置發(fā)布后等 10 秒再測試(Nacos 有緩存)。

九、總結:這組合為啥比 Spring Security 香?

咱花了這么多時間搭完這個鑒權系統(tǒng),最后來總結下:Sa-Token + Gateway + Nacos 這組合,到底比 Spring Security 好在哪?

1. 代碼量少到離譜

  • Spring Security:實現(xiàn)一個簡單的 token 鑒權,要寫WebSecurityConfigurerAdapter、UserDetailsService、JwtTokenProvider等一堆類,配置文件還得寫半天。
  • Sa-Token:登錄就一行StpUtil.login(userId),鑒權就一行StpUtil.checkLogin(),網(wǎng)關過濾器幾行代碼搞定,代碼量至少減少 80%。

2. 學習成本低

  • Spring Security:要理解 “認證流程”“授權流程”“過濾器鏈”“SecurityContext” 等一堆概念,新手入門至少得一周。
  • Sa-Token:API 直觀到不用看文檔都能猜懂,StpUtil.hasRole()就是判斷角色,StpUtil.logout()就是退出登錄,新手半天就能上手。

3. 動態(tài)配置更靈活

  • Spring Security:要改個攔截路徑、token 有效期,得改代碼、重啟服務,麻煩得很。
  • Sa-Token + Nacos:直接在 Nacos 上改配置,10 秒生效,不用重啟服務,運維效率直接拉滿。

4. 報錯信息更友好

  • Spring Security:報個AccessDeniedException,你還得自己排查是 “沒登錄” 還是 “沒權限”。
  • Sa-Token:直接報 “未登錄,請先登錄!”“無此權限,請聯(lián)系管理員!”,連排查方向都給你指好了,調試效率高多了。
責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2025-07-28 00:00:55

2023-11-28 17:24:45

2025-10-09 00:00:15

2022-02-18 08:34:33

JavaSa-Token項目

2021-04-13 14:47:53

認證授權Java

2021-04-23 07:33:10

SpringSecurity單元

2025-10-13 05:00:00

網(wǎng)頁版APIPostman

2023-12-08 12:12:21

2025-02-03 23:35:56

API技術.NET

2025-10-09 07:47:04

2023-02-28 08:57:06

Spring上下線緩存

2025-06-30 01:33:00

2016-12-28 10:00:03

銳捷網(wǎng)絡

2016-12-06 10:07:01

銳捷網(wǎng)絡

2014-05-04 13:47:39

銳捷網(wǎng)絡極簡網(wǎng)絡

2021-11-04 10:11:02

Sentinel網(wǎng)關限流

2021-01-28 09:50:29

分布式對象SharedObjec

2021-04-19 07:33:04

WebSecuritySpringHttpSecurit

2021-05-31 07:18:46

SpringSecurity信息

2022-04-09 14:45:02

微服務常見概念Spring
點贊
收藏

51CTO技術棧公眾號

亚洲国产精品日韩专区av有中文| 中国字幕a在线看韩国电影| 韩国理伦片一区二区三区在线播放| 久久综合五月天| 97人人干人人| 日本在线视频免费| 欧美日韩一区二区综合| 91精品国产综合久久福利软件| 国产aaa免费视频| 国产中文字幕在线观看| 国产一区啦啦啦在线观看| 97不卡在线视频| 99热6这里只有精品| 国语一区二区三区| 欧美日韩大陆一区二区| 97干在线视频| 黄色成人在线| 国产欧美一区二区精品性色| 国新精品乱码一区二区三区18| 18国产免费视频| 一本色道久久综合亚洲精品不卡| 久久久97精品| 久久日免费视频| 欧美18xxxx| 亚洲电影一区二区| 亚洲视频小说| 可以免费看污视频的网站在线| 国产精品自产自拍| 国产精品永久免费观看| 在线观看亚洲欧美| 亚洲午夜视频| 美女撒尿一区二区三区| 波多野结衣一二三四区| 亚洲人成精品久久久| 亚洲国产日韩欧美在线动漫| 成人网站免费观看入口| 欧美三级电影一区二区三区| 久久久99精品免费观看不卡| 九九九九九精品| 激情五月婷婷六月| av网页在线观看| 午夜日韩成人影院| 精品久久久国产| aa视频在线播放| 好看的中文字幕在线播放| 亚洲视频一区在线| 97人人模人人爽人人少妇| 久久久久久av无码免费看大片| 亚洲一区视频| 国产91精品久久久久| 精品成人久久久| 国产亚洲一区在线| 欧美一级bbbbb性bbbb喷潮片| 中文字幕一区二区三区手机版| 狠狠爱综合网| 91精品国产沙发| 探花视频在线观看| 久久久精品午夜少妇| 日本欧美在线视频| 一级特黄免费视频| 日本美女视频一区二区| 国产日韩欧美日韩| 91丨九色丨蝌蚪丨对白| 国产一区亚洲一区| 99蜜桃在线观看免费视频网站| 国产福利免费视频| 国产精品资源在线看| 99超碰麻豆| 三级小视频在线观看| 久久在线免费观看| 日韩av大片在线| 欧美男人亚洲天堂| 日本伊人午夜精品| 亚洲va欧美va国产综合剧情| wwwav在线播放| 99re热这里只有精品免费视频| 国产精品igao视频| 一区二区视频网| 国产一区二区三区在线观看免费视频| 成人在线视频网址| 麻豆影视在线| 亚洲视频一二区| 亚洲精品蜜桃久久久久久| 一个人www视频在线免费观看| 久久久国产精品入口麻豆| 亚洲aⅴ怡春院| 成人久久久久久久久| 免费日韩成人| 亚洲精品在线三区| jizz中文字幕| 欧美久久一区| 国产精品激情av电影在线观看 | 欧美另类一区二区三区| 一本二本三本亚洲码| 视频在线这里都是精品| 精品久久久久久中文字幕一区奶水 | 欧美视频一区二区三区在线观看| 欧洲美女亚洲激情| 欧美sss在线视频| 色噜噜狠狠狠综合曰曰曰88av| 免费麻豆国产一区二区三区四区| 自拍偷拍一区| 久久影视电视剧免费网站| 天堂网一区二区三区| 久久99久久精品欧美| 精品福利影视| www红色一片_亚洲成a人片在线观看_| 激情久久av一区av二区av三区| 中文字幕第100页| 麻豆成人入口| 久久99精品视频一区97| 中文字幕 自拍偷拍| 99热99精品| 国产亚洲福利社区| melody高清在线观看| 午夜av电影一区| 一区二区三区四区毛片| 久久av综合| 午夜精品久久久久久久白皮肤 | 日韩黄色在线视频| 久久精品国产77777蜜臀| 久久人人九九| 91白丝在线| 日韩一区二区三区精品视频| 青青草华人在线视频| 亚洲欧美日韩国产一区| 精品999在线观看| 秋霞在线视频| 欧美一区二区三区系列电影| 后入内射无码人妻一区| 日韩精品免费视频人成| 蜜桃传媒一区二区| wwww在线观看免费视频| 日韩久久久精品| 很污很黄的网站| 免费观看成人av| 欧美污视频久久久| 欧美片第一页| 亚洲欧美日韩精品| 日韩人妻精品中文字幕| 91视频观看视频| 欧美 日韩 国产一区| 亚洲第一av| 亚洲国产毛片完整版| 国产主播在线观看| 老牛嫩草一区二区三区日本| 国产精品区一区二区三含羞草| 日本欧美电影在线观看| 91精品国产综合久久精品图片 | 午夜久久tv| 91深夜福利视频| 二区在线播放| 日韩免费一区二区| 国产亚洲小视频| 成人精品视频.| 欧美日韩最好看的视频| 成人啊v在线| 在线播放国产一区中文字幕剧情欧美| 亚洲视屏在线观看| 国产精品不卡在线观看| 香蕉视频xxxx| 影音先锋国产精品| 久久久久久亚洲精品不卡4k岛国| 在线精品亚洲欧美日韩国产| 亚洲人成网站免费播放| 又骚又黄的视频| 亚洲免费电影在线| 欧美在线观看成人| 亚洲宅男一区| 国产精品丝袜高跟| 国产精品一区二区三区视频网站| 欧美成人一区二区三区片免费| 久草视频中文在线| xnxx国产精品| 色播五月激情五月| 亚洲一级一区| 欧美精品二区三区四区免费看视频| 欧美色片在线观看| 久热精品视频在线| 五月天丁香视频| 欧美亚洲免费在线一区| 欧美偷拍第一页| 成人h精品动漫一区二区三区| 成人在线观看a| 综合久久十次| 久久五月天婷婷| 伊人亚洲精品| 一区二区三区国产视频| 91 中文字幕| 午夜影院久久久| a级黄色免费视频| 成人一区二区三区| 9久久婷婷国产综合精品性色 | 大伊香蕉精品在线品播放| 国产成人亚洲精品| a视频在线观看| 亚洲精品小视频在线观看| 中文字幕在线播放不卡| 亚洲第一精品在线| 中文字幕求饶的少妇| 99re8在线精品视频免费播放| 91高清国产视频| 亚洲一区二区三区高清不卡| 中文字幕成人一区| 米奇777超碰欧美日韩亚洲| 亚洲japanese制服美女| 88xx成人永久免费观看| 欧美精品电影免费在线观看| 国产情侣av在线| 成人免费在线观看入口| 国产成人无码一区二区在线观看| 国内精品写真在线观看| 一级黄色香蕉视频| 99国产精品视频免费观看一公开| 婷婷视频在线播放| 成人激情电影在线| 蜜桃免费一区二区三区| 99精品国产高清一区二区麻豆| 国产欧洲精品视频| av高清一区| 欧美做爰性生交视频| 国产精品xx| 久久久久久com| 午夜在线激情影院| 精品国产一区二区三区四区在线观看 | 福利精品一区| 日韩av快播网址| 久久影院午夜精品| 国内外成人免费激情在线视频网站 | 18深夜在线观看免费视频| 日欧美一区二区| 东京热加勒比无码少妇| a91a精品视频在线观看| 青青青在线观看视频| 女生裸体视频一区二区三区| 91香蕉视频网址| 99久久夜色精品国产亚洲1000部| 日本一区二区三区四区在线观看| 色综合中文网| 欧美日韩国产精品一区二区| 午夜先锋成人动漫在线| 精品无人区一区二区三区| 国产伦精品一区二区三区免费优势| av日韩免费电影| 日韩精品久久久久久久软件91| 91久久伊人青青碰碰婷婷| 日本精品在线观看| 成人区精品一区二区| 成人在线tv视频| 激情小说网站亚洲综合网| 精品国内亚洲2022精品成人| 精品欧美一区二区在线观看视频 | 中文字幕在线亚洲| 日韩免费啪啪| 久久精品在线视频| 久色国产在线| 2019中文字幕全在线观看| sese综合| 国产日韩欧美在线看| 韩国三级成人在线| 国产高清在线一区| 欧美一级视频免费观看| 91农村精品一区二区在线| 免费看黄色的视频| 美女脱光内衣内裤视频久久网站 | 国产精品一区二区美女视频免费看 | 91精品国自产| 日韩欧美国产综合| 视频一区 中文字幕| 亚洲天堂av女优| 国产区在线观看| 91精品国产91久久久久久| av激情成人网| 亚洲已满18点击进入在线看片| 6080亚洲理论片在线观看| 精品日韩欧美| 日本a级不卡| 99久久国产综合精品五月天喷水| 国产精品日韩欧美一区| 伊人影院综合在线| 国产成人精品亚洲777人妖| 欧美 日本 国产| 日韩毛片视频在线看| 亚洲欧美在线视频免费| 欧美日韩成人在线一区| 蜜臀久久99精品久久久| 在线电影中文日韩| 蜜桃传媒在线观看免费进入 | 中文在线一区二区 | 欧美丝袜第三区| 免费激情视频网站| 在线观看视频亚洲| aa国产成人| 国产在线一区二区三区| 天天躁日日躁狠狠躁欧美| 中文字幕免费在线不卡| 在线亚洲自拍| 一级黄色大片儿| 国产网红主播福利一区二区| 久久久久久久久久一区二区三区| 色噜噜狠狠一区二区三区果冻| 国产精品美女一区| 亚洲天堂免费视频| 丰满大乳少妇在线观看网站| 国产精品久久久久久久久免费看| 高清日韩欧美| 国产精品无码乱伦| 久久最新视频| 女同性恋一区二区三区| 亚洲嫩草精品久久| 在线视频播放大全| 精品一区二区亚洲| 爱看av在线入口| 97久久夜色精品国产九色| 日韩av大片| 天天碰免费视频| 91污片在线观看| 国产精品第九页| 日韩小视频在线观看专区| av女优在线| 国产成人精品免费视频| 色橹橹欧美在线观看视频高清| 日本一区午夜艳熟免费| 国产一区二区导航在线播放| 日韩不卡av在线| 欧洲一区在线电影| 欧美日韩视频精品二区| 91精品成人久久| 欧洲亚洲成人| 日韩网站在线免费观看| 国产成人在线视频免费播放| 国产大学生自拍| 337p亚洲精品色噜噜| 色的视频在线免费看| 国产精品视频免费观看www| 啪啪亚洲精品| 992kp快乐看片永久免费网址| 久久久亚洲精品一区二区三区| 国产九色在线播放九色| 日韩电影在线观看中文字幕| 啊啊啊久久久| 超碰97人人在线| 激情久久一区| av网页在线观看| 粉嫩老牛aⅴ一区二区三区| 视频一区二区三区在线看免费看 | 9i看片成人免费看片| 国产午夜精品麻豆| 日韩大片欧美大片| 日韩精品欧美在线| 毛片av一区二区| 国产精品白丝喷水在线观看| 91精品国产乱| av福利在线导航| 欧美精品一区在线发布| 日韩va亚洲va欧美va久久| 伊人影院综合网| 91精品国产综合久久婷婷香蕉| 性欧美高清come| 极品校花啪啪激情久久| 日韩二区三区在线观看| 波多野结衣喷潮| 欧美成人a∨高清免费观看| 男人av在线播放| 亚洲欧美电影在线观看| 精品亚洲国产成人av制服丝袜| 国产三级国产精品国产国在线观看| 精品国产亚洲一区二区三区在线观看| 超碰99在线| 午夜精品一区二区三区四区 | 国外成人在线直播| 亚洲区小说区| 日韩av片免费观看| 亚洲成人黄色影院| 成人在线免费看| 51国产成人精品午夜福中文下载| 宅男噜噜噜66一区二区| 久久久精品成人| 日韩欧美二区三区| 一区一区三区| 成年人三级视频| 91丨porny丨最新| 国产又粗又猛视频| 91精品国产色综合久久不卡98口| 日韩欧美网址| 色综合久久五月| 欧美日韩免费观看一区三区| 国内高清免费在线视频| 亚洲精品影院| 99久久精品国产精品久久| 亚洲一区中文字幕在线| 97在线视频免费观看| 97久久视频| 91精品人妻一区二区三区蜜桃欧美| 欧美电影影音先锋| 3d欧美精品动漫xxxx无尽| 中文字幕人妻熟女人妻洋洋| 国产精品国产三级国产普通话三级 | 美女精品久久久| 日本女优一区|