Sa-Token实现网关统一鉴权和内部服务外网隔离

本文详细介绍了如何在微服务环境中使用Sa-Token进行统一鉴权,包括SpringBoot中的Servlet和Reactor模型集成,Redis依赖的选择,以及与JWT的集成,还涉及了全局过滤器的配置和服务间权限验证的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

🎈 1 参考文档

网管关统一鉴权 | sa-token.cc


🥩2 微服务中使用Sa-Token依赖引入说明

2.1 Sa-Token依赖

对于网关服务,大体来讲分为两种:

  • 一种是基于Servlet模型的,如:Zuul,我们需要引入的是:sa-token-spring-boot-starter
  • 一种是基于Reactor模型的,如:SpringCloud Gateway、ShenYu 等等,我们需要引入的是:sa-token-reactor-spring-boot-starter并且注册全局过滤器!

注意:切不可直接在一个项目里同时引入这两个依赖,否则会造成项目无法启动

2.1.1 基于Servlet模型

<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.35.0.RC</version>
</dependency>

注意:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter 即可。

2.1.2 基于Reactor模型

<!-- Sa-Token 权限认证(Reactor响应式集成),在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
    <version>1.35.0.RC</version>
</dependency>

注意:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-reactor-spring-boot-starter 修改为 sa-token-reactor-spring-boot3-starter 即可。

2.2 Redis依赖

无论使用哪种序列化方式,你都必须为项目提供一个 Redis 实例化方案,因为我们需要和各个服务通过Redis来同步数据。

2.2.1 jdk 默认序列化方式

优点:兼容性好,缺点:Session 序列化后基本不可读,对开发者来讲等同于乱码。

<!-- Sa-Token 整合 Redis (使用 jdk 默认序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis</artifactId>
    <version>1.35.0.RC</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2.2.2 jackson 序列化方式

优点:Session 序列化后可读性强,可灵活手动修改,缺点:兼容性稍差。

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-jackson</artifactId>
    <version>1.35.0.RC</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

🚀3 和jwt集成

3.1 引入依赖

<!-- Sa-Token 整合 jwt -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-jwt</artifactId>
    <version>1.35.0.RC</version>
</dependency>
  1. 注意: sa-token-jwt 显式依赖 hutool-jwt 5.7.14 版本,保险起见:你的项目中要么不引入 hutool,要么引入版本 >= 5.7.14 的 hutool 版本。
  2. hutool 5.8.13 和 5.8.14 版本下会出现类型转换问题,关联issue

3.2 配置秘钥

application.yml 配置文件中配置 jwt 生成秘钥:

sa-token:
    # jwt秘钥 
    jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk

3.3 注入jwt实现

根据不同的整合规则,插件提供了三种不同的模式,你需要选择其中一种注入到你的项目中

@Configuration
public class SaTokenConfigure {
    // Sa-Token 整合 jwt (Simple 简单模式)
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForSimple();
    }
    
    // Sa-Token 整合 jwt (Mixin 混入模式)
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForMixin();
    }
    
    // Sa-Token 整合 jwt (Stateless 无状态模式)
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForStateless();
    }
}

🚀4 实现鉴权接口

关于数据的获取,建议以下方案三选一:

  1. 在网关处集成ORM框架,直接从数据库查询数据。
  2. 先从Redis中获取数据,获取不到时走ORM框架查询数据库。
  3. 先从Redis中获取缓存数据,获取不到时走RPC调用子服务 (专门的权限数据提供服务) 获取。

放到登录服务中:

/**
 * 自定义权限验证接口扩展
 */
@Component
public class StpInterfaceImpl implements StpInterface {
    @Autowired
    private MenuService menuService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private ThreadPoolConfig threadPoolConfig;

    /**
     * 返回一个账号所拥有的权限码集合
     *
     * @param loginId
     * @param loginType
     * @return
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        List<String> res = (List<String>) StpUtil.getTokenSession().get("PERMISSION-LIST");
        if (res == null) {
            CompletableFuture<List<String>> permissionFuture = CompletableFuture.supplyAsync(() -> {
                List<MenuVO> menuVOList = menuService.getPermissionList(Convert.toLong(loginId),null);
                return menuVOList.stream().map(MenuVO::getPermission).collect(Collectors.toList());
            }, threadPoolConfig.USER_ROLE_PERM_THREAD_POOL);
            try {
                return permissionFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
        return res;
    }

    /**
     * 返回一个账号所拥有的角色标识集合
     *
     * @param loginId
     * @param loginType
     * @return
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        List<String> res = (List<String>) StpUtil.getTokenSession().get("ROLE-LIST");
        if (res == null) {
            CompletableFuture<List<String>> roleFuture = CompletableFuture.supplyAsync(() -> {
                // 返回此 loginId 拥有的权限列表
                List<RoleVO> roles = roleService.getRoleByUserId(Convert.toLong(loginId));
                return roles.stream().map(RoleVO::getRoleKey).collect(Collectors.toList());
            }, threadPoolConfig.USER_ROLE_PERM_THREAD_POOL);
            try {
                return roleFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
        return res;
    }
}

其他子服务:

/**
 * 自定义权限验证接口扩展
 */
@Component
public class StpInterfaceImpl implements StpInterface {
    /**
     * 返回一个账号所拥有的权限码集合
     *
     * @param loginId
     * @param loginType
     * @return
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        return (List<String>) StpUtil.getTokenSession().get("PERMISSION-LIST");
    }

    /**
     * 返回一个账号所拥有的角色标识集合
     *
     * @param loginId
     * @param loginType
     * @return
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        return (List<String>) StpUtil.getTokenSession().get("ROLE-LIST");
    }
}

🚀5 注册全局过滤器

然后在网关处注册全局过滤器进行鉴权操作

@Configuration
public class SaTokenConfigure{
    /**
     * Sa-Token 整合 jwt (Simple 简单模式)
     *
     * @return
     */
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForSimple();
    }

    /**
     * 注册 [Sa-Token全局过滤器]
     *
     * @return
     */
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 拦截地址 - 拦截全部path
                .addInclude("/**")
                // 开放地址
                .addExclude("/favicon.png")
                // 鉴权方法:每次访问进入
                // 全局认证函数
                .setAuth(obj -> {
                    SaRouter
                            // 拦截的所有接口
                            .match("/**")
                            // 忽略所有登陆相关接口
                            .notMatch("/account/**")
                            // 忽略所有个人信息相关接口
                            .notMatch("/manage/profile/**")
                            // 忽略获得文件下载链接相关接口
                            .notMatch("/file/fileTransfer/getFileUrl/**")
                            // 忽略获取站点信息列表相关接口
                            .notMatch("/manage/config/get")
                            // 忽略获取所有的公告相关接口
                            .notMatch("/manage/notice/list")
                            // 忽略所有接口文档相关接口
                            .notMatch("/doc.html",
                                    "/doc.html*",
                                    "/doc.html/*",
                                    "/webjars/**",
                                    "/img.icons/**",
                                    "/swagger-resources/**",
                                    "/**/v2/api-docs")
                            // 要执行的校验动作,可以写完整的 lambda 表达式
                            .check(r -> StpUtil.checkLogin());
                })
                // 异常处理函数
                .setError(e -> {
                    return ResultResponse.fail().message(e.getMessage());
                })
                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {
                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*");
                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> System.out.println("--------OPTIONS预检请求,不做处理--------"))
                            .back();
                });
    }
}

🚀6 使用Same-Token模块提供的身份校验能力,完成服务间的权限认证

6.1 网关处添加Same-Token

为网关添加全局过滤器:此过滤器会为Request请求头追加 Same-Token 参数,这个参数会被转发到子服务。

/**
 * 全局过滤器,为请求添加 Same-Token 
 */
@Component
public class ForwardAuthFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest newRequest = exchange
                .getRequest()
                .mutate()
                // 为请求追加 Same-Token 参数 
                .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken())
                .build();
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        return chain.filter(newExchange);
    }
}

6.2 在子服务里校验参数

在子服务添加过滤器校验参数。

/**
 * Sa-Token 权限认证 配置类 
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册 Sa-Token 全局过滤器 
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                .addInclude("/**")
                .addExclude("/favicon.ico")
                .setAuth(obj -> {
                    // 校验 Same-Token 身份凭证     —— 以下两句代码可简化为:SaSameUtil.checkCurrentRequestToken(); 
                    String token = SaHolder.getRequest().getHeader(SaSameUtil.SAME_TOKEN);
                    SaSameUtil.checkToken(token);
                })
                .setError(e -> {
                    return SaResult.error(e.getMessage());
                })
                ;
    }
}

启动网关与子服务,访问测试:

如果通过网关转发,可以正常访问,直接访问子服务会提示:无效Same-Token:xxx

6.3 服务间内部调用鉴权

有时候我们需要在一个服务调用另一个服务的接口,这也是需要添加Same-Token作为身份凭证的

在服务里添加 Same-Token 流程与网关类似,我们以RPC框架 Feign 为例:

6.3.1 首先在调用方添加 FeignInterceptor

/**
 * feign拦截器, 在feign请求发出之前,加入一些操作 
 */
@Component
public class FeignInterceptor implements RequestInterceptor {
    // 为 Feign 的 RCP调用 添加请求头Same-Token 
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken());
        
        // 如果希望被调用方有会话状态,此处就还需要将 satoken 添加到请求头中
        // requestTemplate.header(StpUtil.getTokenName(), StpUtil.getTokenValue());
    }
}

6.3.2 在调用接口里使用此Interceptor

/**
 * 在调用接口里使用 Interceptor
 */
@FeignClient(name = "netdisk-file", configuration = FeignInterceptor.class)
public interface FileFeignService {
    @RequestMapping("/file/getUserFile/{userFileId}")
    ResultResponse<Boolean> getUserFile(@PathVariable Long userFileId);
}
<think>嗯,用户问的是Sa-Token微服务网关是怎么做的。首先,我得回想一下Sa-Token的相关文档功能。Sa-Token是一个轻量级的Java限认证框架,我之前了解过它的核心功能,比如登录认证、限控制等。但微服务网关这部分可能需要更深入的了解。 首先,微服务架构中的网关通常有两种方式:一种是在每个服务中单独处理,另一种是在网关统一处理。Sa-Token推荐的可能是在网关统一,这样能避免重复代码,提高效率。需要确认Sa-Token是否提供了网关的具体模块或者配置方法。 然后,Sa-Token的文档里提到过与Spring Cloud Gateway、Zuul等网关的整合。可能的步骤包括引入依赖、配置拦截器、编写逻辑。比如,在网关的全局过滤器中进行Token校验,获取登录状态限,然后决定是否放行请求。 接下来要考虑的是具体的流程。用户请求到达网关时,网关需要检查请求中的Token是否有效。Sa-Token的StpUtil类可以用来校验Token,获取用户信息。然后根据请求的路径或服务判断用户是否有限访问。如果有限,转发到对应的服务;否则返回401或403。 可能还需要处理路由信息限的匹配。比如,配置路由对应的限码,或者在网关层面动态获取限。此外,跨服务可能需要使用Sa-Token的全局会话解析,或者通过Feign拦截器传递Token。 还要注意微服务中的Token传递问题。在服务之间调用时,如何确保Token正确传递,避免信息丢失。Sa-Token可能提供了Feign的集成方案,自动在请求头中添加Token。 另外,用户可能关心的是如何实现细粒度的限控制,比如基于角色或限码的验证。Sa-Token支持注解式限检查,但在网关层面可能需要更灵活的方式,比如从路由元数据中获取所需限,然后进行匹配。 最后,是否需要结合其他组件,比如Redis来存储Token,以实现分布式会话。Sa-Token默认支持Redis集成,这样在网关时,各个微服务可以共享会话状态。 总结一下,回答用户时需要分步骤说明Sa-Token微服务网关实现方式,包括依赖引入、过滤器配置、Token校验、限匹配以及服务间的Token传递。同时,给出一个简单的示例代码,帮助用户快速理解。</think>Sa-Token微服务网关中主要通过 **统一拦截、路由匹配、限校验** 实现,以下是核心流程与实现思路: --- ### **1. 网关统一原理** 微服务架构中,**网关作为所有请求的入口**,Sa-Token 在此处拦截请求,集中完成登录状态校验与限验证,避免每个服务重复处理逻辑。 --- ### **2. 核心步骤** #### **① 引入依赖** 在网关项目中添加 Sa-Token 相关依赖(如 Spring Cloud Gateway 集成): ```xml <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-reactor-spring-boot-starter</artifactId> <version>最新版本</version> </dependency> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-dao-redis</artifactId> <version>最新版本</version> </dependency> ``` #### **② 配置路由-限映射** 在网关配置文件中(如 `application.yml`),定义路由与所需限的对应关系: ```yaml spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** metadata: sa-auth: user.get,user.update # 该路由需要的限码 ``` #### **③ 实现全局过滤器** 创建全局过滤器(如 `SaTokenGatewayFilter`),拦截请求并: ```java public class SaTokenGatewayFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 获取当前请求路径对应的限码(从路由元数据) String requiredPermissions = exchange.getAttribute(SA_AUTH_METADATA_KEY); // 2. 校验登录状态 (Sa-Token 自动完成) if (StpUtil.isLogin() == false) { return unauthorized(exchange, "未登录"); } // 3. 校验限(如:需同时具备 user.get user.update 限) if (requiredPermissions != null) { String[] perms = requiredPermissions.split(","); if (!StpUtil.hasPermissionAnd(perms)) { return forbidden(exchange, "限不足"); } } // 4. 放行请求 return chain.filter(exchange); } // 返回响应示例(根据实际封装) private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().writeWith(Mono.just(...)); } } ``` #### **④ 跨服务Token传递** 微服务间调用时(如通过 Feign),需自动传递 Token: ```java // Feign 请求拦截器 public class SaTokenFeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header(StpUtil.getTokenName(), StpUtil.getTokenValue()); } } ``` --- ### **3. 关键特性** - **路由级限控制**:通过路由元数据动态绑定限,灵活适配不同服务- **会话全局解析**:网关校验后,Token 可在下游服务中直接使用(需共享 Redis 会话存储)。 - **精细化控制**:支持 `AND/OR` 限逻辑、角色验证、拦截器扩展等。 - **高性能**:基于 Reactor 异步模型,避免阻塞请求。 --- ### **4. 配置示例(Redis 存储)** ```yaml sa-token: token-name: satoken timeout: 1800 token-style: uuid is-share: true # 所有服务共享同一个 Token 前缀 is-read-body: false # Redis 配置 jedis: host: 127.0.0.1 port: 6379 ``` --- ### **5. 进阶场景** - **动态路由**:结合数据库/Nacos 动态加载路由限配置。 - **灰度发布**:根据请求头中的版本标识匹配不同限规则。 - **限流与熔断**:结合 Sentinel 在网关实现限+流控双保障。 通过以上步骤,Sa-Token 能够在网关层高效完成,确保微服务体系的安全性,同时保持代码简洁与架构清晰。具体实现需根据实际网关类型(如 Spring Cloud Gateway、Zuul)调整配置细节。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值