偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

告別 Spring Security!Sa-Token + Gateway + Nacos 極簡(jiǎn)鑒權(quán)實(shí)戰(zhàn)

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

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

我之前就踩過(guò)這坑:為了加個(gè)簡(jiǎn)單的 token 驗(yàn)證,硬著頭皮啃了三天 Spring Security 文檔,配置類堆了快 200 行,最后還因?yàn)?“權(quán)限注解沒(méi)掃描到” 卡了一下午。當(dāng)時(shí)就想:就沒(méi)有個(gè) “開(kāi)箱能用、配置簡(jiǎn)單、報(bào)錯(cuò)還能看懂” 的鑒權(quán)框架嗎?

還真有!今天咱就聊個(gè) “極簡(jiǎn)派” 方案 ——Sa-Token + Gateway + Nacos,不用復(fù)雜配置,不用繞彎子,5 步搞定全鏈路鑒權(quán),看完你絕對(duì)會(huì)說(shuō):“早知道這玩意兒,誰(shuí)還折騰 Spring Security 啊!”

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

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

1. Sa-Token:鑒權(quán)界的 “小清新”

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

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

簡(jiǎn)單說(shuō):Sa-Token 就是把 “鑒權(quán)” 這件事,從 “需要解密的復(fù)雜工程” 變成了 “擰瓶蓋級(jí)別的簡(jiǎn)單操作”。

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

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

你想?。喝绻總€(gè)微服務(wù)都自己做鑒權(quán),那不是重復(fù)勞動(dòng)嗎?用戶訪問(wèn) “訂單服務(wù)” 要驗(yàn) token,訪問(wèn) “用戶服務(wù)” 還要驗(yàn) token,萬(wàn)一 token 規(guī)則改了,所有服務(wù)都得改一遍,這不瘋了?

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

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

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

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

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

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

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

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

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

先把 “彈藥” 備齊,避免等會(huì)兒擼代碼的時(shí)候 “缺這少那”。咱用的版本都是經(jīng)過(guò)驗(yàn)證的,兼容性沒(méi)問(wèn)題,別瞎換版本踩坑!

1. 基礎(chǔ)環(huán)境

  • JDK:1.8(別問(wèn)為啥不用 11,大部分公司還在 8 呢,實(shí)用為主)
  • 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 用這個(gè)版本不報(bào)錯(cuò))

2. 核心依賴清單

后面搭項(xiàng)目會(huì)用到這些依賴,先列出來(lái)讓你有個(gè)底,不用記,后面直接復(fù)制粘貼就行:

依賴名稱

作用

sa-token-spring-boot-starter

Sa-Token 核心依賴,開(kāi)箱即用

sa-token-reactor-spring-boot-starter

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

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 服務(wù)發(fā)現(xiàn)依賴(可選,這次實(shí)戰(zhàn)用不上,但建議加)

lombok

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

spring-boot-starter-web

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

3. 項(xiàng)目結(jié)構(gòu)

咱這次搭個(gè) “多模塊項(xiàng)目”,結(jié)構(gòu)清晰,也符合實(shí)際開(kāi)發(fā)場(chǎng)景:

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

為啥這么分?因?yàn)閷?shí)際項(xiàng)目里,網(wǎng)關(guān)和業(yè)務(wù)服務(wù)肯定是分開(kāi)部署的,咱這么搭更貼近真實(shí)場(chǎng)景。

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

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

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

新建一個(gè) Maven 項(xiàng)目,打包方式選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>
    <!-- 父工程坐標(biāo),自己改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鑒權(quán)實(shí)戰(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:只管理版本,不實(shí)際引入依賴 -->
    <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>
    <!-- 子模塊聲明:后面加子模塊的時(shí)候要在這寫 -->
    <modules>
        <module>sa-token-auth-gateway</module>
        <module>sa-token-auth-service</module>
    </modules>
</project>

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

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

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

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

在父工程下新建一個(gè) Maven 子模塊,artifactId 設(shè)為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)關(guān)模塊:統(tǒng)一鑒權(quán)入口</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 測(cè)試依賴(可選) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <!-- 打包插件,不然SpringBoot項(xiàng)目跑不起來(lái) -->
    <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>

這里有個(gè)坑要注意:Gateway 是基于 WebFlux 的,千萬(wàn)不能引入spring-boot-starter-web依賴,不然會(huì)沖突!如果不小心加了,趕緊刪掉,不然啟動(dòng)會(huì)報(bào) “Circular view path” 之類的錯(cuò)。

2. 寫網(wǎng)關(guān)啟動(dòng)類

新建一個(gè)啟動(dòng)類GatewayApplication.java,很簡(jiǎn)單,就加個(gè)@SpringBootApplication注解:

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

3. 配置 Nacos 連接(關(guān)鍵?。?/h3>

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

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

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

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

啟動(dòng) Nacos 后,登錄控制臺(tái),點(diǎn)擊左側(cè) “配置管理”→“配置列表”→“+” 號(hào),新建配置:

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

配置完點(diǎn)擊 “發(fā)布”,這樣網(wǎng)關(guān)啟動(dòng)時(shí)就會(huì)從 Nacos 拉取這些配置了。

5. 寫 Sa-Token 網(wǎng)關(guān)鑒權(quán)過(guò)濾器

這步是核心!要在 Gateway 里加一個(gè) Sa-Token 的過(guò)濾器,實(shí)現(xiàn) “所有請(qǐng)求先驗(yàn) token,沒(méi) token 或 token 無(wú)效就攔截” 的邏輯。

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

這段代碼要重點(diǎn)說(shuō)下:

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

6. 測(cè)試網(wǎng)關(guān)鑒權(quán)(先跑通基礎(chǔ)流程)

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

  • 啟動(dòng) Nacos(確保配置已發(fā)布)。
  • 啟動(dòng)網(wǎng)關(guān)模塊(GatewayApplication),控制臺(tái)看到 “網(wǎng)關(guān)啟動(dòng)成功!??” 就說(shuō)明沒(méi)問(wèn)題。
  • 用 Postman 或?yàn)g覽器訪問(wèn) “不需要鑒權(quán)的接口”,比如http://localhost:8080/api/user/login(雖然業(yè)務(wù)服務(wù)還沒(méi)寫,但網(wǎng)關(guān)會(huì)轉(zhuǎn)發(fā)請(qǐng)求,此時(shí)會(huì)報(bào) “503 Service Unavailable”,因?yàn)闃I(yè)務(wù)服務(wù)沒(méi)啟動(dòng),這是正常的)。
  • 訪問(wèn) “需要鑒權(quán)的接口”,比如http://localhost:8080/api/user/info,此時(shí)網(wǎng)關(guān)會(huì)攔截,返回:
{
    "code": 401,
    "msg": "未登錄,請(qǐng)先登錄!",
    "data": null
}

這就對(duì)了!說(shuō)明鑒權(quán)過(guò)濾器生效了 —— 沒(méi)登錄的請(qǐng)求被攔截了。

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

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

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

在父工程下新建 Maven 子模塊,artifactId 設(shè)為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è)務(wù)服務(wù)模塊:用戶服務(wù)示例</description>

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

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

        <!-- Nacos 配置中心依賴(可選,業(yè)務(wù)服務(wù)如果要?jiǎng)討B(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 測(cè)試依賴 -->
        <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è)務(wù)服務(wù)用的是spring-boot-starter-web(基于 Servlet),網(wǎng)關(guān)用的是spring-cloud-starter-gateway(基于 WebFlux),兩者不沖突,因?yàn)槭遣煌哪K。

2. 寫業(yè)務(wù)服務(wù)啟動(dòng)類

新建UserServiceApplication.java:

package com.example.service;

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

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

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

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

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

spring:
  application:
    name: sa-token-auth-service # 服務(wù)名

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

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

咱寫個(gè)簡(jiǎn)單的用戶服務(wù),包含三個(gè)接口:

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

(1)定義用戶實(shí)體類

新建entity/User.java:

package com.example.service.entity;

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

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

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

新建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;

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

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

    // 初始化數(shù)據(jù):加個(gè)測(cè)試用戶(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ù)庫(kù)獲取用戶
        User user = USER_MAP.get(username);
        // 2. 判斷用戶是否存在,密碼是否正確
        if (user == null || !user.getPassword().equals(password)) {
            returnnull; // 登錄失敗
        }
        return user; // 登錄成功
    }

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

    /**
     * 根據(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;

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

    // 注入用戶服務(wù)
    private final UserService userService;

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

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

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

        // 4. 返回結(jié)果: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);
    }

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

    /**
     * 獲取當(dāng)前登錄用戶信息(需要鑒權(quán))
     * 請(qǐng)求地址:http://localhost:8080/api/user/info(通過(guò)網(wǎng)關(guān)訪問(wèn))
     * 請(qǐng)求頭:Authorization: token(登錄時(shí)返回的token)
     */
    @GetMapping("/info")
    public SaResult getUserInfo() {
        // 1. 獲取當(dāng)前登錄用戶的ID(這里是用戶名,因?yàn)榈卿洉r(shí)傳的是用戶名)
        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);
    }

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

這段代碼里有個(gè)關(guān)鍵:StpUtil.login(user.getUsername())—— 這就是 Sa-Token 的登錄核心方法,傳入用戶唯一標(biāo)識(shí)(這里用用戶名,實(shí)際項(xiàng)目用用戶 ID),Sa-Token 會(huì)自動(dòng)生成 token,不用你自己處理 token 的生成、存儲(chǔ)邏輯,太省心了!

六、第四步:完整流程測(cè)試,驗(yàn)證鑒權(quán)效果

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

1. 啟動(dòng)所有服務(wù)

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

確保三個(gè)服務(wù)都啟動(dòng)成功,控制臺(tái)沒(méi)有報(bào)錯(cuò)。

2. 測(cè)試 1:注冊(cè)用戶

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

實(shí)際返回:

{
    "code": 200,
    "msg": "注冊(cè)成功!",
    "data": null
}

注冊(cè)成功!說(shuō)明 “不需要鑒權(quán)的接口” 能正常訪問(wèn)。

3. 測(cè)試 2:登錄獲取 token

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

實(shí)際返回(重點(diǎn)看data.token,后面要用到):

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

登錄成功!拿到 token 了,下一步用這個(gè) token 訪問(wèn)需要鑒權(quán)的接口。

4. 測(cè)試 3:攜帶 token 訪問(wèn)用戶信息接口

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

實(shí)際返回:

{
    "code": 200,
    "msg": "獲取用戶信息成功!",
    "data": {
        "id": 123456789,
        "username": "zhangsan",
        "role": "user",
        "tokenTimeout": 3580 // token剩余有效期(秒),因?yàn)樵O(shè)置的是3600秒,過(guò)了20秒
    }
}

完美!說(shuō)明鑒權(quán)通過(guò)了,網(wǎng)關(guān)正確識(shí)別了 token,業(yè)務(wù)服務(wù)正確獲取了當(dāng)前登錄用戶。

5. 測(cè)試 4:不攜帶 token 訪問(wèn)需要鑒權(quán)的接口

  • 請(qǐng)求地址:GET http://localhost:8080/api/user/info
  • 不填A(yù)uthorization請(qǐng)求頭
  • 預(yù)期結(jié)果:網(wǎng)關(guān)攔截,返回 “未登錄,請(qǐng)先登錄!”

實(shí)際返回:

{
    "code": 401,
    "msg": "未登錄,請(qǐng)先登錄!",
    "data": null
}

正確!鑒權(quán)攔截生效了。

6. 測(cè)試 5:攜帶無(wú)效 token 訪問(wèn)

  • 請(qǐng)求地址:GET http://localhost:8080/api/user/info
  • 請(qǐng)求頭:Authorization: invalid-token(隨便寫個(gè)無(wú)效的 token)
  • 預(yù)期結(jié)果:返回 “未登錄,請(qǐng)先登錄!”(Sa-Token 會(huì)識(shí)別無(wú)效 token 為未登錄)

實(shí)際返回和測(cè)試 4 一樣,正確。

7. 測(cè)試 6:退出登錄后訪問(wèn)接口

  • 先調(diào)用退出登錄接口:POST http://localhost:8080/api/user/logout,請(qǐng)求頭帶之前的 token,返回 “退出登錄成功!”。
  • 再用同一個(gè) token 訪問(wèn)/api/user/info,預(yù)期結(jié)果:返回 “未登錄,請(qǐng)先登錄!”。

實(shí)際返回正確,說(shuō)明退出登錄后 token 失效了,鑒權(quán)邏輯沒(méi)問(wèn)題。

七、第五步:Nacos 動(dòng)態(tài)配置實(shí)戰(zhàn)(進(jìn)階)

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

1. 需求:臨時(shí)開(kāi)放一個(gè)測(cè)試接口,不用鑒權(quán)

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

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

在UserController里加一個(gè)接口:

/**
 * 測(cè)試接口(臨時(shí)開(kāi)放,不用鑒權(quán))
 * 請(qǐng)求地址:http://localhost:8080/api/user/test
 */
@GetMapping("/test")
public SaResult test() {
    return SaResult.ok("這是臨時(shí)開(kāi)放的測(cè)試接口,不用登錄就能訪問(wèn)!");
}

重啟業(yè)務(wù)服務(wù)(這次是因?yàn)榧恿私涌?,?shí)際改配置不用重啟)。

(2)不修改配置時(shí)訪問(wèn)測(cè)試接口

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

實(shí)際返回確實(shí)是 “未登錄”,因?yàn)?api/user/test不在 Nacos 的exclude-path-patterns里。

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

登錄 Nacos 控制臺(tái),找到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 # 新增:測(cè)試接口不用鑒權(quán)

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

(4)再次訪問(wèn)測(cè)試接口

還是訪問(wèn)GET http://localhost:8080/api/user/test,不攜帶 token,預(yù)期結(jié)果:返回測(cè)試接口的信息。

實(shí)際返回:

{
    "code": 200,
    "msg": "這是臨時(shí)開(kāi)放的測(cè)試接口,不用登錄就能訪問(wèn)!",
    "data": null
}

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

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

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

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

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

八、實(shí)戰(zhàn)踩坑指南(必看?。?/h2>

咱實(shí)戰(zhàn)過(guò)程中肯定會(huì)遇到坑,我把我踩過(guò)的坑整理出來(lái),幫你少走彎路:

1. 網(wǎng)關(guān)啟動(dòng)報(bào) “Circular view path” 錯(cuò)

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

2. 網(wǎng)關(guān)拉不到 Nacos 配置,報(bào) “Could not resolve placeholder” 錯(cuò)

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

3. 攜帶 token 訪問(wèn)接口,還是返回 “未登錄”

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

4. 跨域問(wèn)題,前端調(diào)接口報(bào) “CORS policy” 錯(cuò)

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

5. Nacos 配置修改后不生效

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

九、總結(jié):這組合為啥比 Spring Security 香?

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

1. 代碼量少到離譜

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

2. 學(xué)習(xí)成本低

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

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

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

4. 報(bào)錯(cuò)信息更友好

  • Spring Security:報(bào)個(gè)AccessDeniedException,你還得自己排查是 “沒(méi)登錄” 還是 “沒(méi)權(quán)限”。
  • Sa-Token:直接報(bào) “未登錄,請(qǐng)先登錄!”“無(wú)此權(quán)限,請(qǐng)聯(lián)系管理員!”,連排查方向都給你指好了,調(diào)試效率高多了。
責(zé)任編輯:武曉燕 來(lái)源: 石杉的架構(gòu)筆記
相關(guā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項(xiàng)目

2021-04-13 14:47:53

認(rèn)證授權(quán)Java

2021-04-23 07:33:10

SpringSecurity單元

2025-10-13 05:00:00

網(wǎng)頁(yè)版APIPostman

2023-12-08 12:12:21

2025-02-03 23:35:56

API技術(shù).NET

2025-10-09 07:47:04

2023-02-28 08:57:06

Spring上下線緩存

2025-06-30 01:33:00

2016-12-06 10:07:01

銳捷網(wǎng)絡(luò)

2014-05-04 13:47:39

銳捷網(wǎng)絡(luò)極簡(jiǎn)網(wǎng)絡(luò)

2016-12-28 10:00:03

銳捷網(wǎng)絡(luò)

2021-11-04 10:11:02

Sentinel網(wǎng)關(guān)限流

2021-01-28 09:50:29

分布式對(duì)象SharedObjec

2021-04-19 07:33:04

WebSecuritySpringHttpSecurit

2021-05-31 07:18:46

SpringSecurity信息

2022-04-09 14:45:02

微服務(wù)常見(jiàn)概念Spring
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)