如何使用网关认证实现白名单功能,白名单就是相对于黑名单的一个概念,在网络访问中,白名单可以不需要验证直接进行操作
那么如何实现白名单的功能?
核心依赖添加
<dependencies>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--响应式编程-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!--mybatis和数据库连接的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
</dependencies>
1.在数据库中添加白名单表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for finance_white
-- ----------------------------
DROP TABLE IF EXISTS `finance_white`;
CREATE TABLE `finance_white` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求路径',
`route_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路由类型',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_date` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建人',
`update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of finance_white
-- ----------------------------
INSERT INTO `finance_white` VALUES (1, '/login', '登陆', '2023-08-19 14:33:10', '2023-08-19 14:33:19', 'zhangsan', 'zhangsan ');
SET FOREIGN_KEY_CHECKS = 1;
数据表
使用batis-x生成mapper、pojo、service
使用redis缓存白名单信息,可能需要redis的配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplateInit(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化Key的实例化对象
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
/**
*
* 设置Hash类型存储时,对象序列化报错解决
*/
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
在过滤器中验证请求是否属于白名单,如果属于白名单直接进行放行,如果不在白名单内,拦截器将对请求进行拦截,并且返回403状态码,提醒访问者
public class LoginFilter implements GlobalFilter {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private FinanceWhiteMapper whiteMapper;
@Autowired
private FinanceWhiteService whiteService;
/**
* 方法签名,表示一个用于网关过滤器的filter方法
* @param exchange
* ServerWebExchange:表示当前请求和响应的上下文对象,
* 包含了与请求相关的信息(如请求头、请求体等)以及响应的操作(如设置响应头、发送响应等)
* @param chain
* GatewayFilterChain:表示过滤器链,
* 通过调用该链的next方法来继续执行后续的过滤器或者转发到路由处理器进行请求处理
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
//认证,(获得请求)
ServerHttpRequest request = exchange.getRequest();
/**
* 通过调用getHeaders()方法可以获取到请求头(HttpHeaders)对象。
* 然后,通过调用getFirst("token")方法
* 获取到名为"token"的请求头的第一个值
*/
//从请求头获取token
String token = request.getHeaders().getFirst("token");
//登陆path不需要进行验证(白名单:存放在配置文件或者redis中
String path = request.getPath().toString();
// ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
//在redis里面查询已经有的路径;大键white,小健path,如果查不到直接返回403,查到了就通过
//,删redis,查数据库,存redis,匹配成功通过
redisTemplate.opsForHash().delete("whitePath", "path");
//查询数据库
List<FinanceWhite> list1 = whiteService.list();
String jsonStr = JSONUtil.toJsonStr(list1);
//存redis
redisTemplate.opsForHash().put("whitePath", "path",jsonStr);
String s = (String) redisTemplate.opsForHash().get("whitePath", "path");
JSONArray objects = JSONUtil.parseArray(s);
List<FinanceWhite> list2 = JSONUtil.toList(objects, FinanceWhite.class);
// boolean isHavePath=false;
//匹配请求的路径是否是白名单,放行
for (FinanceWhite financeWhite : list2) {
if(path.equals(financeWhite.getPath())){
// isHavePath=true;
return chain.filter(exchange);
}
}
//list从redis获取
List<String> list=new ArrayList<>();
// list.add(opsForValue.toString());
list.add("/login");
if(list.contains(path)){
return chain.filter(exchange);
}
//认证不通过,无法获取token返回404forbidden
if(StringUtils.isBlank(token) || !JWTUtil.verify(token,"woniu".getBytes())){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
/**
* 认证通过,直接放行
*/
return chain.filter(exchange);
}
}
为什么要先删除redis里面的白名单?
1.防止数据库里面的白名单发生变化,所以每次请求先删除白名单,然后访问数据库查询白名单信息,将数据库的白名单信息缓存到redis,再将redis的白名单path和请求对比
2.对比通过就成功放行,对比不通过,禁止放行
缺点是如果有大量访问,可能会导致数据库访问太大,导致宕机,所以接下来会采用响应式编程来解决白名单的访问