14 KiB
上小节 中,我们已经将网关服务初步搭建好了,并且支持上了路由转发。但是,光有转发肯定还是不够的,转发之前应该还需要进行接口鉴权,比如说,前端请求用户退出登录接口,在转发路由之前,应该先校验该用户是否已经登录过了,如果没有登录,则不应进行转发操作,而是提示用户先去登录。
添加用户登出接口
为了等会方便演示,编辑 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();
}
}
上面这个自定义权限验证接口扩展类中,有两个方法:
注册全局过滤器
接着,在 /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()方法校验是否已经登录,注意,这里仅排除掉登录接口、验证码发送接口,这两个接口无需登录就能被请求;- 权限认证:权限认证部分,暂时全部注释掉,后续写具体接口时,再回过头来修改这块,比如笔记发布接口,需要校验是否拥有笔记发布的权限。
自测一波
编码工作完成后,重启网关服务。再次请求用户登出接口,如下:
哎,怎么返参 success 为 true , 再查看一下认证服务的控制台日志,会发现接口还是被请求到了。不是已经在网关层配置了登录校验了吗,啥子情况?
莫慌!在 Apipost 工具中,看一下接口的具体请求头信息,如下:
可以看到,请求头中携带了 token 令牌。并且这个后缀为 88513 的令牌,在 Redis 中是存在的,如下图所示,由于咱们设置的令牌过期时间较长,该令牌还没失效,导致网关认为此次请求的用户,已经登录过了,才会将该请求转发到认证服务上:
为了避免这个问题,可以点击 Apipost 顶部的 Cookie 管理器 , 将全局 Cookie 关闭掉:
再次测试登录接口,注意,现在请求头中还未设置 token 令牌,可以看到这次返回的是 SaToken 默认的提示信息:未能读取到有效 token ,如下图所示:
再来测试一下携带上令牌的请求效果。依次请求获取验证码接口 | 登录接口, 拿到最新的 token 令牌,并设置到请求头中,令牌的名称为 satoken ,值为刚刚获取的令牌值,点击发送:
OK , 携带上令牌再请求登出接口,可以看到返参提示成功,再观察一下认证服务的控制台日志,确认一下,看看有没有打印请求切面日志。不出意外是正常打印日志了,说明网关服务校验登录功能正常。