SpringCloud3.0+Sa-token+Gateway网关实现鉴权和token登录拦截功能
版本:
- Springboot3.0.5以及对应的Springcloud,SpringcloudAlibaba依赖
- nacos 2.2.0,sa-token1.34.0,Mysql8.0
前提:
- 我这边是主要是对管理员进行鉴权的,所以划分了管理员以及网关服务,而sa-token的统一鉴权是在网关服务里面设计的。
- Sa-token官方网址:Sa-Token
父依赖:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<java.version>17</java.version>
<!-- springboot 3 对应mybatis 3.0.0以上,否则会报错-->
<mybatis-spring-boot-starter.version>3.0.0</mybatis-spring-boot-starter.version>
<spring-cloud-alibaba.version>2022.0.0.0-RC1</spring-cloud-alibaba.version>
<spring.cloud.dependencies>2022.0.1</spring.cloud.dependencies>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.34.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!--springcloud 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>
<!-- 这里引入最新的SpringCloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.dependencies}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Admin服务子模块
依赖
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- nacos 配置中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 这里需要单独导入LoadBalancer依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.34.0</version>
</dependency>
<!-- 自定义公共模块-->
<dependency>
<groupId>com.example</groupId>
<artifactId>commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.5.4</version>
</dependency>
</dependencies>
控制器类:按照官方文档,我们知道只需要实现它的StpUtil.login(Object id); 方法就行。
@RestController
@RequestMapping("/admin")
public class AdminController{
@Resource
AdminService adminService;
@PostMapping("/login")
public R login(@RequestParam("id") int id,
@RequestParam("password") String password)
{
Admin admin=adminService.login(id,password);
if(!ObjectUtils.isEmpty(admin))
{
// 登录
StpUtil1.setStpLogic(new StpLogic("admin"));
StpUtil1.login(admin.getId());
// 获取token并返回
return R.success("登录成功",StpUtil1.getTokenInfo());
}
return R.error("登录失败");
}
上面我用的是StpUtil1而不是StpUtil ,原因是我系统有两个角色,超级管理员和管理员,所以根据官方文档我创建了一个新的类,叫做StpUtil1,然后将其StpUtil复制过来,更改其属性TYPE为admin 。同理超级管理员也是创建多一个类就行,更改TYPE,这让我想起了设计模式的开闭原则哈哈哈。
配置文件bootstrap.yml,因为配置数据库的配置在配置中心,这里就不放出来了
Redis整合
这里sa-token整合redis只需要将配置文件配置,和导入依赖,sa-token就会自动将数据存入redis里面了,注意版本要对上,以防错误。
server:
port: 8081
spring:
application:
name: Admin-service
profiles:
# 环境也是和配置文件保持一致
active: db
cloud:
nacos:
config:
# 配置文件后缀名
file-extension: yml
# 配置中心服务器地址,也就是Nacos地址
server-addr: localhost:8848
prefix: dataBase
discovery:
# 配置Nacos注册中心地址
server-addr: localhost:8848
data:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
Gateway服务子模块
依赖
<?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>competition</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>GateWay</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
<version>1.34.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 一定要引入 loadbalancer才能使用 lb://进行负载均衡调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--依赖冲突,通过该方法可以巧妙去除(重写覆盖,)父类依赖,这就是maven解决冲突的最短依赖路径原则。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<!-- 自定义公共模块-->
<dependency>
<groupId>com.example</groupId>
<artifactId>commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
整合Gateway注意点
值得注意的是,因为Gateway是基于 WebFlux开发的,所以呢,我们需要去到官网,找一下下资料,如图,要导入的依赖是带有reactor的噢,而且因为使用的是springboot3,所以还要将boot改为boot3,别导错依赖了!!
全局过滤器类(鉴权,登录拦截token,跨域)
- SptUtil1和SptUtil2都是在公共模块commons中的哈,然后两者除了Type不一样其他都一样,用来区分不同管理员,超级管理员是拥有所有权限的,而管理员权限是超级管理员分配的。
- 所以超级管理员只要登录就可以访问各个路径啦
- 但是管理员就要检测一下权限了。看官网给出的详细代码解释
package com.test.conf;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.test.entity.StpUtil1;
import com.test.entity.StpUtil2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* @author LiYa
* @create 2023-04-05 22:36
*/
@Configuration
public class SaTokenConfigure {
// 注册 Sa-Token全局过滤器
@Bean
@Primary
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter ()
// 拦截地址
.addInclude("/**")
// 开放地址
.addExclude("/admin/login")
.addExclude("/SuperAdmin/login")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
System.out.println("---------- sa全局认证");
System.out.println(StpUtil1.isLogin());
System.out.println(StpUtil2.isLogin());
System.out.println(StpUtil1.getPermissionList());
// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
// SaRouter.match("/**", "/admin/login", r -> StpUtil1.checkLogin());
// 角色认证 -- 拦截以 admin 开头的路由,必须具备 admin 角色或者 super-admin 角色才可以通过认证
// 权限认证 -- 不同模块, 校验不同权限
SaRouter.match("/events/**").check(r -> {
if(StpUtil1.hasPermission("mEvent")==false && StpUtil2.isLogin()==false)
{
throw new SaTokenException("没有权限噢");
}
});
SaRouter.match("/admin/**").check(r -> {
if(StpUtil2.isLogin()==false)
{
throw new SaTokenException("没有权限噢");
}
});
SaRouter.match("/advertisement/**").check(r -> {
if(StpUtil1.hasPermission("mAdvertisement")==false && StpUtil2.isLogin()==false)
{
throw new SaTokenException("没有权限噢");
}
});
SaRouter.match("/award/**").check(r -> {
if(StpUtil1.hasPermission("mAward")==false && StpUtil2.isLogin()==false)
{
throw new SaTokenException("没有权限噢");
}
});
SaRouter.match("/notice/**").check(r -> {
if(StpUtil1.hasPermission("mNotice")==false && StpUtil2.isLogin()==false)
{
throw new SaTokenException("没有权限噢");
}
});
})
// 异常处理方法:每次setAuth函数出现异常时进入
.setError(e -> {
System.out.println("---------- sa异常认证");
return SaResult.error(e.getMessage());
})
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(obj -> {
// ---------- 设置跨域响应头 ----------
SaHolder.getResponse()
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", "*")
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "*");
// 如果是预检请求,则立即返回到前端
SaRouter.match(SaHttpMethod.OPTIONS)
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
.back();
});
}
}
那么管理员的权限从哪来呢?不用思考直接看官网。
只要实现了这个接口,就可以设计权限了,ohyeah,真的太腻害了 !
但值得注意的是,因为采用的是Springcloud,所以不能在admin服务那边实现该接口,必须要在网关服务里面实现,否则是没有的!!!!!!!空说无凭,上证据,看官网
实现StpInterface 接口的代码(在网关服务里面实现)
package com.test.auth;
import cn.dev33.satoken.stp.StpInterface;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author LiYa
* @create 2023-04-09 16:50
* 通过前端传递过来的token可以找到该logintype对应的id
*/
@Component
public class stp implements StpInterface {
@Resource
RedisTemplate redisTemplate;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return (List<String>) redisTemplate.opsForList().range(loginType+loginId, 0,-1);
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
List<String> list =new ArrayList<>();
list.add(loginType);
return list;
}
}
bootstrap.yml
server:
port: 8500
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
username: nacos
password: nacos
config:
extension-configs:
- data-id: Gateway-dev.yml
group: DEFAULT_GROUP
refresh: true
# - data-id: intercept-dev.yml
# group: DEFAULT_GROUP
# refresh: true
data:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位秒,-1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期),单位秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# 是否从cookie中读取token
is-read-cookie: false
接下来就是测试了,启动服务,访问登录,呐呐呐,看到返回值没有,我们这个时候就可以提取tokenValue和tokenName去访问服务了。
访问redis成功出现数据 。
此时我们去访问某个服务,先不带token访问,毫无疑问被拦截了
设置token,yeah成功有数据返回啦
总结:看官网,多实践,给博主点赞关注,这样就可以理解解决任何bug,最主要的还是多实践以及给博主点赞关注。