weblog/doc/7、Gateway 网关搭建与接口鉴权/7.2 网关整合 SaToken 实现接口鉴权(1).md
2025-02-17 11:57:55 +08:00

14 KiB
Raw Blame History

上小节 中,我们已经将网关服务初步搭建好了,并且支持上了路由转发。但是,光有转发肯定还是不够的,转发之前应该还需要进行接口鉴权,比如说,前端请求用户退出登录接口,在转发路由之前,应该先校验该用户是否已经登录过了,如果没有登录,则不应进行转发操作,而是提示用户先去登录。

添加用户登出接口

为了等会方便演示,编辑 xiaohashu-auth 认证服务,添加一个 /user/logout 登出接口,逻辑先不写,代码如下:

package com.quanxiaoha.xiaohashu.auth.controller;

import com.quanxiaoha.framework.biz.operationlog.aspect.ApiOperationLog;
import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.xiaohashu.auth.model.vo.user.UserLoginReqVO;
import com.quanxiaoha.xiaohashu.auth.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Resource
    private UserService userService;

	// 省略...

    @PostMapping("/logout")
    @ApiOperationLog(description = "账号登出")
    public Response<?> logout() {
    
        // todo 账号退出登录逻辑待实现
        
        return Response.success();
    }

}

接口添加完毕后,重启认证服务。并通过 Apipost 工具,通过网关来请求该接口,看看效果:

可以看到,直接就转发到子服务上了,网关层目前没有任何鉴权操作。

添加 SaToken 依赖

接下来,我们就将为网关添加 SaToken 相关依赖,以实现接口鉴权。关于 Gateway 网关集成 SaToken, 详细文档可访问:https://sa-token.cc/doc.html#/micro/gateway-auth 小哈这里就直接上手演示,如何在小哈书项目集成它。

编辑项目最外层 pom.xml 文件,除了之前声明的 SaToken 相关依赖外,网关如果想整合 SaToken , 还需额外添加如下依赖:

            // 省略...
            
            <dependency>
                <groupId>cn.dev33</groupId>
                <artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
                <version>${sa-token.version}</version>
            </dependency>
            
            // 省略...

接着,编辑 xiaohashu-gateway 网关服务的 pom.xml 文件,添加依赖如下:

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

        <!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-redis-jackson</artifactId>
        </dependency>

        <!-- 提供Redis连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

Tip

: 依赖添加完成后,别忘了重新 Reload 一下 Maven 依赖,将包下载到本地仓库中。

配置 SaToken

然后,将 xiaohashu-auth 服务中,已经配置好的 SaToken 相关配置项,复制一份到网关服务的 applicaiton.yml 文件中,内容如下,要注意层级格式哟,别配置错了:

spring:
  cloud:
	// 省略...
  data:
    redis:
      database: 0 # Redis 数据库索引(默认为 0
      host: 127.0.0.1 # Redis 服务器地址
      port: 6379 # Redis 服务器连接端口
      password: qwe123!@# # Redis 服务器连接密码(默认为空)
      timeout: 5s # 读超时时间
      connect-timeout: 5s # 链接超时时间
      lettuce:
        pool:
          max-active: 200 # 连接池最大连接数
          max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
          min-idle: 0 # 连接池中的最小空闲连接
          max-idle: 10 # 连接池中的最大空闲连接
          
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
  # token 名称(同时也是 cookie 名称)
  token-name: satoken
  # token 有效期(单位:秒) 默认30天-1 代表永久有效
  timeout: 2592000
  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
  active-timeout: -1
  # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token
  is-share: true
  # token 风格默认可取值uuid、simple-uuid、random-32、random-64、random-128、tik
  token-style: uuid
  # 是否输出操作日志
  is-log: true

实现鉴权接口

配置完成后,在网关服务中创建一个 /auth 包,用于统一放置鉴权相关的代码。然后,在包内创建一个 SaToken 自定义权限验证接口扩展类 StpInterfaceImpl, 代码如下:

package com.quanxiaoha.xiaohashu.gateway.auth;

import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;

/**
 * @author: 犬小哈
 * @date: 2024/4/5 18:04
 * @version: v1.0.0
 * @description: 自定义权限验证接口扩展
 **/
/**
 * 自定义权限验证接口扩展
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的权限列表
        
        // todo 从 redis 获取
        
        return Collections.emptyList();
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的角色列表
        
        // todo 从 redis 获取
        
        return Collections.emptyList();
    }

}

上面这个自定义权限验证接口扩展类中,有两个方法:

  • getPermissionList() : 根据 loginId 获取该用户的权限列表,这里暂时返回一个空集合,后续需要从 Redis 中查询;

    入参中的 loginId 是什么?

    loginId 即登录接口中,StpUtil.login() 方法的入参,我们传入的是用户 ID, 那么,这里的 loginId 对应的即是用户 ID 。

  • getRoleList() : 根据 loginId 获取该用户的角色列表,这里暂时返回一个空集合;

注册全局过滤器

接着,在 /auth 包下,再创建一个配置类 SaTokenConfigure,用于将全局过滤器注入到 Spring 容器中。下面是摘自官方的示例代码:

/**
 * [Sa-Token 权限认证] 配置类 
 * @author click33
 */
@Configuration
public class SaTokenConfigure {
    // 注册 Sa-Token全局过滤器 
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
            // 拦截地址 
            .addInclude("/**")    /* 拦截全部path */
            // 开放地址 
            .addExclude("/favicon.ico")
            // 鉴权方法:每次访问进入 
            .setAuth(obj -> {
                // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 
                SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin());
                
                // 权限认证 -- 不同模块, 校验不同权限 
                SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
                SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
                SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
                SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
                
                // 更多匹配 ...  */
            })
            // 异常处理方法每次setAuth函数出现异常时进入 
            .setError(e -> {
                return SaResult.error(e.getMessage());
            })
            ;
    }
}

官方示例中,注释写的很详细,包含需要拦截的地址,以及登录校验,权限校验部分,就不具体解释了,还是比较好理解的。

结合小哈书项目的现状,对示例代码稍作改动,最终代码如下:

package com.quanxiaoha.xiaohashu.gateway.auth;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
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;

/**
 * @author: 犬小哈
 * @date: 2024/6/13 14:48
 * @version: v1.0.0
 * @description: [Sa-Token 权限认证] 配置类
 **/
@Configuration
public class SaTokenConfigure {
    // 注册 Sa-Token全局过滤器
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")    /* 拦截全部path */
                // 鉴权方法:每次访问进入
                .setAuth(obj -> {
                    // 登录校验
                    SaRouter.match("/**") // 拦截所有路由
                            .notMatch("/auth/user/login") // 排除登录接口
                            .notMatch("/auth/verification/code/send") // 排除验证码发送接口
                            .check(r -> StpUtil.checkLogin()) // 校验是否登录
                    ;

                    // 权限认证 -- 不同模块, 校验不同权限
                    // SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
                    // SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
                    // SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
                    // SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));

                    // 更多匹配 ...  */
                })
                // 异常处理方法每次setAuth函数出现异常时进入
                .setError(e -> {
                    return SaResult.error(e.getMessage());
                })
                ;
    }
}

修改的地方如下:

  • 登录校验:对所有接口进行拦截,通过 StpUtil.checkLogin() 方法校验是否已经登录,注意,这里仅排除掉登录接口验证码发送接口,这两个接口无需登录就能被请求;
  • 权限认证:权限认证部分,暂时全部注释掉,后续写具体接口时,再回过头来修改这块,比如笔记发布接口,需要校验是否拥有笔记发布的权限。

自测一波

编码工作完成后,重启网关服务。再次请求用户登出接口,如下:

哎,怎么返参 successtrue , 再查看一下认证服务的控制台日志,会发现接口还是被请求到了。不是已经在网关层配置了登录校验了吗,啥子情况?

莫慌!在 Apipost 工具中,看一下接口的具体请求头信息,如下:

可以看到,请求头中携带了 token 令牌。并且这个后缀为 88513 的令牌,在 Redis 中是存在的,如下图所示,由于咱们设置的令牌过期时间较长,该令牌还没失效,导致网关认为此次请求的用户,已经登录过了,才会将该请求转发到认证服务上:

为了避免这个问题,可以点击 Apipost 顶部的 Cookie 管理器 全局 Cookie 关闭掉:

再次测试登录接口,注意,现在请求头中还未设置 token 令牌,可以看到这次返回的是 SaToken 默认的提示信息:未能读取到有效 token ,如下图所示:

再来测试一下携带上令牌的请求效果。依次请求获取验证码接口 | 登录接口 拿到最新的 token 令牌,并设置到请求头中,令牌的名称为 satoken ,值为刚刚获取的令牌值,点击发送

OK , 携带上令牌再请求登出接口,可以看到返参提示成功,再观察一下认证服务的控制台日志,确认一下,看看有没有打印请求切面日志。不出意外是正常打印日志了,说明网关服务校验登录功能正常。

本小节源码下载

https://t.zsxq.com/DDTBQ