1. 内容说明
本文旨在使用开源轻量级 Java 权限认证框架sa-token+springcloud-gateway实现微服务在网关处统一鉴权。sa-token参考地址:https://sa-token.cc/doc.html#/
项目按照业务分为三个板块,如图:
- api(也就是微服务中各种api接口,不涉及任何权限相关代码,只提供服务)
- auth(认证中心,实现登陆逻辑)
- gateway(网关,实现转发等,主要是统一鉴权)
2. 项目实现
首先在idea创建一个项目mirco,然后在项目下依次创建三个module,这就不展开说了。接下来各模块填充内容。
2.1. auth模块
2.1.1. pom.xml依赖
主要是web、satoken、satoken整合redis:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.34.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
2.1.2. controller类
提供登陆接口,实现登陆并返回登陆成功的token给前端:
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@GetMapping("/auth/doLogin")
public SaResult doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if ("admin".equals(username) && "123456".equals(password)) {
StpUtil.login(10001);
} else if ("super".equals(username) && "123456".equals(password)) {
StpUtil.login(10002);
}else {
return SaResult.error("登录失败");
}
// 第3步,返回给前端
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return SaResult.ok("登录成功").setData(tokenInfo);
}
}
2.1.3. 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
2.1.4. application.yml配置文件
server:
port: 8082
spring:
# 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
2.2. gateway模块
2.2.1. pom.xml依赖
主要是gateway、satoken、satoken整合redis:
<dependencies>
<!-- springCloud gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- httpClient依赖,缺少此依赖api网关转发请求时可能发生503错误 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.34.0</version>
</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>
</dependencies>
2.2.2. 全局过滤器
主要是配置路由进行拦截鉴权,这里是整个鉴权的核心,具体的路由配置规则参考sa-token官网:
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;
/**
* [Sa-Token 权限认证] 配置类
*/
@Configuration
public class SaTokenConfigure {
// 注册 Sa-Token全局过滤器
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**") /* 拦截全部path */
// 开放地址
.addExclude("/favicon.ico")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
SaRouter.match("/**", "/auth/doLogin", r -> StpUtil.checkLogin());
// 权限认证 -- 不同模块, 校验不同权限
SaRouter.match("/api/test1", r -> StpUtil.checkPermission("api.test1"));
SaRouter.match("/api/test2", r -> StpUtil.checkPermission("api.test2"));
SaRouter.match("/api/test3", r -> StpUtil.checkRoleOr("admin", "super"));
// 更多匹配 ... */
})
// 异常处理方法:每次setAuth函数出现异常时进入
.setError(e -> {
return SaResult.error(e.getMessage());
})
;
}
}
2.2.3. 自定义权限接口
主要是定义用户拥有的权限和角色,我这里直接写死的,实际项目中通常应该是auth认证模块登陆后从数据库查出用户的角色权限缓存到redis,然后这里再从redis取出来。
import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义权限验证接口扩展,在需要鉴权时自动调用
*/
@Component
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<>();
if (Integer.parseInt(loginId.toString()) == 10001) {
list.add("api.test1");
} else if (Integer.parseInt(loginId.toString()) == 10002) {
list.add("api.test2");
}
return list;
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<>();
if (Integer.parseInt(loginId.toString()) == 10001) {
list.add("admin");
} else if (Integer.parseInt(loginId.toString()) == 10002) {
list.add("super");
}
return list;
}
}
2.2.4. 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
2.2.5. application.yml配置文件
server:
port: 8083
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: api
uri: http://127.0.0.1:8081
predicates:
- Path=/api/**
- id: auth
uri: http://127.0.0.1:8082
predicates:
- Path=/auth/**
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
2.3. auth模块
2.3.1. pom.xml依赖
主要是web:
<dependencies>
<!-- SpringBoot Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.3.2. controller类
提供一些接口,用来验证satoken权限:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/api/test1")
public String test1() {
return "访问成功--只有《api.test1》权限的才能访问";
}
@GetMapping("/api/test2")
public String test2() {
return "访问成功--只有《api.test2》权限的才能访问";
}
@GetMapping("/api/test3")
public String test3() {
return "访问成功--拥有《admin或者super角色》可以访问";
}
@GetMapping("/api/test4")
public String test4() {
return "访问成功--无需权限,登陆即可访问";
}
}
2.3.3. 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
2.3.4. application.yml配置文件
server:
port: 8081
3. 项目测试
- 首先通过网关登陆:http://localhost:8083/auth/doLogin?username=admin&password=123456
- 如图正确的账户密码登陆成功返回token,这里登陆的admin用户。
- 不带token访问,访问失败:http://localhost:8083/api/test1
- 携带token访问,访问成功:http://localhost:8083/api/test1
- 访问无权限的接口失败:http://localhost:8083/api/test2
- 访问有角色权限的接口成功:http://localhost:8083/api/test3
4. 项目总结
至此,我们完成了gateway处统一使用sa-token鉴权。以上只是最基础的网关统一鉴权的使用,更深层的比如内外网隔离等请自行参考sa-token官网。个人觉得sa-token相当牛逼,十分容易上手,不管你是单体、前后端分离还是微服务项目,都能轻松集成。相比于springsecurity学习成本降低很多。
另外本博客涉及的代码已全部包含在文章里了,就不另外贴了。