精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

精品视频站长推荐| 97视频资源在线观看| 蜜桃传媒一区二区亚洲| 日日夜夜亚洲| 亚洲成人av一区| 视频一区二区在线观看| 999久久久久久| 一区二区国产精品| 播播国产欧美激情| 中文字幕 亚洲一区| 日韩福利在线观看| 国产鲁鲁视频在线观看免费| 妺妺窝人体色www聚色窝仙踪| 性欧美18xxxhd| 亚洲欧洲成人精品av97| 久久久福利视频| 99精品在线视频观看| 久久综合影音| 成人av电影在线| 日韩在线观看高清| 亚洲综合自拍网| crdy在线观看欧美| www.亚洲视频| 亚洲国产精品久久久久蝴蝶传媒| 亚洲黄页视频免费观看| 欧美一级免费在线| 国精产品一区一区三区四川| 天天免费综合色| 国产午夜精品视频一区二区三区| 国产黄在线观看| 99精品1区2区| 国产精品免费视频一区二区 | 成人午夜视频一区二区播放| 日韩av成人高清| 欧洲亚洲妇女av| 久草视频在线观| 黄色精品网站| 欧美激情啊啊啊| 91精品国产高清一区二区三蜜臀| 热久久天天拍国产| 一区国产精品视频| 精品无人区无码乱码毛片国产| 久本草在线中文字幕亚洲| 精品裸体舞一区二区三区| 亚洲av无一区二区三区久久| 亚洲欧美专区| 777午夜精品免费视频| 色综合色综合色综合色综合| 欧洲一级精品| 在线一区二区视频| 男女爽爽爽视频| 香蕉视频亚洲一级| 欧洲精品一区二区三区在线观看| 日本网站免费在线观看| 毛片在线网站| 色综合久久88色综合天天免费| 国产免费一区二区三区视频| sis001亚洲原创区| 午夜影院久久久| 妞干网在线视频观看| 国产美女精品写真福利视频| 婷婷综合五月天| 日韩精品视频一区二区在线观看| 日韩电影毛片| 在线国产电影不卡| 亚洲欧美日韩精品一区| 97公开免费视频| 欧美新色视频| 国产欧美日韩卡一| 日本精品一区二区三区不卡无字幕 | 99pao成人国产永久免费视频| 91禁外国网站| 日韩av黄色在线观看| 在线免费观看日韩欧美| 豆国产96在线|亚洲| 亚洲一区二区在线免费观看视频| 成年人三级视频| 日韩少妇视频| 黑人巨大精品欧美一区二区| 欧美一级裸体视频| gogo大尺度成人免费视频| 精品亚洲综合| 国产草草浮力影院| 国产超碰精品| 欧美美女一区二区在线观看| 在线观看网站黄| 国产精品极品在线观看| 亚洲欧美激情视频| 色哟哟一一国产精品| 国语精品一区| 日本国产高清不卡| 国产精品特级毛片一区二区三区| 亚洲最大成人综合网| 国内精品在线一区| 日产精品高清视频免费| jizz视频在线观看| 亚洲精品久久久蜜桃| 一区二区传媒有限公司| 国产精品亚洲成在人线| 性做久久久久久免费观看| 日本一区二区三区在线播放| 中文字幕在线一| 成人美女视频在线观看| 五月天丁香综合久久国产| www.在线视频| 色爱区综合激月婷婷| 人妻精品久久久久中文字幕69| 免费成人av| 欧美xxxx做受欧美.88| 一区二区三区在线观看av| 国产老女人精品毛片久久| 欧洲在线视频一区| 日本动漫同人动漫在线观看| 在线观看免费亚洲| 你懂的在线观看网站| 91亚洲国产成人久久精品| 97超视频免费观看| 999国产精品视频免费| 国产欧美精品区一区二区三区| 91精品国产高清一区二区三区| 亚洲深夜激情| 亚洲香蕉成视频在线观看 | 伊人久久中文字幕| www.欧美精品一二区| 欧美爱爱视频网站| 欧美男女交配| 日韩精品在线视频观看| 国产亚洲欧美精品久久久www| 老司机午夜精品| 欧美久久久久久| 国产一二在线播放| 精品日本一线二线三线不卡| 亚洲综合视频网站| 麻豆精品精品国产自在97香蕉| 茄子视频成人在线观看| 色偷偷偷在线视频播放 | 懂色av中文在线| 精品免费在线观看| 久久久久亚洲AV成人网人人小说| 91亚洲国产| 国产精品一久久香蕉国产线看观看 | 久久国产视频网| 亚洲国产欧美一区二区三区不卡| 性高爱久久久久久久久| 亚洲老板91色精品久久| 亚洲 欧美 成人| 久久午夜免费电影| aaa毛片在线观看| 蜜乳av综合| 日韩av电影在线免费播放| 欧美福利在线视频| 男人的天堂在线| 亚洲一区在线免费观看| 国产成人av片| 亚洲精品视频啊美女在线直播| 国产伦精品一区二区三区高清| 91丝袜在线| 日韩av在线不卡| 无码人妻丰满熟妇精品| 国产日产欧美一区二区三区| 天堂在线资源视频| 97视频精品| 99精品国产高清在线观看| 久草在线资源站资源站| 亚洲国产欧美日韩精品| 成人午夜视频在线播放| 亚洲国产精品成人综合色在线婷婷| 中文字幕欧美人妻精品一区| 日韩精品午夜| 999国产在线| 国产在线精彩视频| 亚洲欧洲日产国产网站| 亚洲图片在线播放| 亚洲国产精品欧美一二99| 亚洲黄色在线网站| 美女网站一区二区| 蜜臀av性久久久久蜜臀av| 成人盗摄视频| 国产精品吊钟奶在线| 久cao在线| 亚洲精品电影久久久| 波多野结衣视频在线观看| 亚洲欧洲色图综合| 人妻av一区二区| 日本人妖一区二区| 国产高清不卡无码视频| 希岛爱理av免费一区二区| 国产精自产拍久久久久久| 欧美性猛片xxxxx免费中国| 日韩av网址在线| 91精品视频免费在线观看| 亚洲一区二区三区四区在线观看| 中文字幕丰满乱子伦无码专区| 精品午夜久久福利影院| 成人在线免费观看av| 国产精品成人av| 免费成人在线观看av| 性欧美video另类hd尤物| 97精品国产97久久久久久| 男人资源在线播放| 亚洲精品之草原avav久久| 亚洲一二三精品| 成人av网在线| 手机在线国产视频| 国产精品永久| 亚洲色婷婷久久精品av蜜桃| 五月婷婷开心网| 国产精品黑丝在线播放| 国内一区在线| 欧美h版在线观看| 国产精品久久久久久久久久久新郎| caoporn视频在线观看| 日韩网站在线观看| 你懂的在线看| 亚洲精品不卡在线| 性中国xxx极品hd| 欧美日韩成人一区二区| 精品国产xxx| 亚洲国产裸拍裸体视频在线观看乱了 | 一区二区三区黄色片| 欧美天堂在线观看| 日本视频www| 一片黄亚洲嫩模| 中文字幕电影av| 国产精品热久久久久夜色精品三区| 朝桐光av一区二区三区| 国产成人精品免费网站| 波多野结衣作品集| 亚洲深夜福利| 欧美三级一级片| 亚洲精品三级| 欧美成人高潮一二区在线看| 欧美日韩调教| 中国女人做爰视频| 亚洲成人二区| 欧美亚洲视频一区| 999国产精品999久久久久久| 涩涩涩999| 欧美一区二区三| 日韩欧美手机在线| 精品日韩免费| 日韩影片在线播放| 精品一区二区三区在线 | 成人黄在线观看| 免费日韩成人| 国产日韩视频在线观看| 日韩黄色三级| 91理论片午午论夜理片久久| 在线观看亚洲精品福利片| 成人免费看吃奶视频网站| 亚洲精品一区av| 91欧美视频网站| 亚洲乱码一区| 精品久久久久久综合日本| 欧美成人专区| 日韩精品久久一区| 成人在线亚洲| 日本成人性视频| 欧美激情91| 成人免费性视频| 成人高潮aa毛片免费| 久久精品一区二区三区不卡牛牛| 北京富婆泄欲对白| 久久只精品国产| 中文字幕欧美激情极品| 18成人在线观看| 香蕉免费毛片视频| 色综合网站在线| 亚洲香蕉在线视频| 欧美大片一区二区三区| 日本一区高清| 久久久久www| 91九色国产在线播放| 国产成人精品午夜| 国产精品美女久久久久人| 国产精品二区在线观看| 亚州av日韩av| 熟女熟妇伦久久影院毛片一区二区| 欧美 日韩 国产 一区| 国产伦精品一区二区三区四区视频_| 麻豆精品网站| 999热精品视频| 99久久综合精品| www.4hu95.com四虎| 亚洲免费在线观看| 国产农村妇女aaaaa视频| 欧美日韩国产电影| 黑人乱码一区二区三区av| 在线精品播放av| 成人性生交大片免费看网站| 国产精品91在线| 中文字幕亚洲在线观看| 日本一区二区三区www| 欧美/亚洲一区| 不要播放器的av网站| 国产伦理精品不卡| 成都免费高清电影| 亚洲激情在线激情| 国产日韩在线免费观看| 欧美精品一区二区三| 91精品国产91久久久久游泳池 | www.99re7.com| 欧美日韩小视频| 人妻少妇一区二区三区| www.日韩欧美| 一呦二呦三呦精品国产| 国产精品果冻传媒潘| 五月婷婷六月综合| 人妻无码视频一区二区三区| 国产成人欧美日韩在线电影| 夜夜春很很躁夜夜躁| 午夜av区久久| www.97av| 久久久久www| 色综合.com| 日本视频一区二区在线观看| 在线日本成人| 久久黄色一级视频| 国产精品久久久一本精品| 69国产精品视频免费观看| 亚洲成人精品视频在线观看| 超鹏97在线| 91久久久久久久久久| 99久久亚洲精品蜜臀| 爱情岛论坛亚洲首页入口章节| 久久色中文字幕| 亚洲天堂视频网站| 亚洲精品网站在线播放gif| 不卡av免费观看| 99久久精品免费看国产四区| 999国产精品999久久久久久| 日韩中文字幕免费| 欧美13一16娇小xxxx| 国产精品白丝jk喷水视频一区| 日本欧美韩国国产| 欧美视频免费看欧美视频| 国产成人免费在线视频| 午夜爽爽爽男女免费观看| 国产精品欧美在线观看| 国产精品第二页| 红桃成人av在线播放| 亚洲人成无码www久久久| 久久久久高清精品| 欧美黄色一级大片| 国产视频欧美视频| 玛雅亚洲电影| 日本免费高清一区二区| 日韩精品乱码av一区二区| 第一次破处视频| 精品视频一区二区三区免费| 在线观看黄色av| 成人激情黄色网| 亚洲区综合中文字幕日日| 亚洲自拍第三页| 亚洲影视在线播放| 四虎永久在线观看| 91av在线视频观看| 国产一区二区观看| 日本 片 成人 在线| 亚洲免费看黄网站| 免费观看黄一级视频| 日韩av不卡电影| 99久久精品费精品国产| 韩国三级丰满少妇高潮| 亚洲一二三四区不卡| 亚洲色图狠狠干| 国产精品久久久久久久久久久久 | 国产欧美日韩亚州综合| 波多野结衣视频观看| 久久精品国产亚洲精品| 97久久综合精品久久久综合| 久久久噜噜噜www成人网| 中文字幕精品一区二区三区精品 | 日韩成人激情视频| 日韩av首页| 欧美 亚洲 视频| 久久久久久久久久久电影| 中文字幕 日韩有码| 九九热精品视频在线播放| 免费福利视频一区| www.久久av.com| 五月婷婷综合网| 1769视频在线播放免费观看| 99久re热视频这里只有精品6| 久久久久久久欧美精品| 色偷偷www8888| 亚洲国产精品资源| 日本a人精品| 欧美日韩福利在线| 国产精品系列在线| 色综合视频在线| 国产欧美一区二区三区在线| 亚洲欧洲一区| 国产91在线播放九色| 亚洲黄色在线看| 免费一区二区三区在线视频| 亚洲色成人一区二区三区小说| 亚洲免费看黄网站| 高清美女视频一区| 九色91在线视频|