文章目录
1 摘要
知名网站建设定制对于后台而言,知名网站建设定制网关层作为所有网络请求的入口。知名网站建设定制一般基于安全考虑,知名网站建设定制会在网关层做权限认证,但是对于一些例如登录、注册等接口以及一些资源数据,这些是不需要有认证信息,因此需要在网关层设计一个白名单的功能。本文将基于 Spring Cloud Gateway 2.X 实现白名单功能。
注意事项 : Gateway 层的白名单实现原理是在过滤器内判断请求地址是否符合白名单,如果通过则跳过当前过滤器。如果有多个过滤器,则需要在每一个过滤器里边添加白名单判断。
2 核心 Maven 依赖
./cloud-alibaba-gateway-filter/pom.xml
- 1
<!-- cloud gateway --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>${spring-cloud-gateway.version}</version> </dependency> <!-- hutool --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> <scope>runtime</scope> </dependency> <!-- Mybatis Plus(include Mybatis) --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- Redisson --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>${redisson-spring.version}</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
依赖对应的版本为:
<lombok.version>1.18.24</lombok.version> <spring-cloud-gateway.version>2.2.5.RELEASE</spring-cloud-gateway.version> <servlet-api.version>4.0.1</servlet-api.version> <hutool.version>5.7.8</hutool.version> <mysql.version>8.0.27</mysql.version> <mybatis-plus.version>3.4.3.4</mybatis-plus.version> <redisson-spring.version>3.17.5</redisson-spring.version>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
3 白名单数据库设计
./doc/sql/gateway-database-create.sql
- 1
/*==============================================================*//* Table: gateway_white_list *//*==============================================================*/create table gateway_white_list( id bigint unsigned not null comment 'id', route_type varchar(32) comment '路由类型', path varchar(128) comment '请求路径', comment varchar(128) comment '说明', create_date bigint unsigned comment '创建时间', update_date bigint unsigned comment '更新时间', primary key (id))engine = innodb defaultcharset = utf8mb4;alter table gateway_white_list comment '网关路由白名单';
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
4 核心代码
4.1 白名单实体类
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/model/WhiteListEntity.java
- 1
package com.ljq.demo.springboot.alibaba.gateway.filter.model;import com.baomidou.mybatisplus.annotation.*;import lombok.Data;import java.io.Serializable;/** * @Description: 网关路由白名单 * @Author: junqiang.lu * @Date: 2022/8/23 */@Data@TableName(value = "gateway_white_list")public class WhiteListEntity implements Serializable { private static final long serialVersionUID = -854919732121208131L; /** * id */ @TableId(type = IdType.NONE) private Long id; /** * 路由类型 */ private String routeType; /** * 请求路径 */ private String path; /** * 说明 */ private String comment; /** * 创建时间 */ private Long createDate; /** * 更新时间 */ private Long updateDate;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
4.2 白名单 DAO 接口
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/dao/WhiteListMapper.java
- 1
package com.ljq.demo.springboot.alibaba.gateway.filter.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.ljq.demo.springboot.alibaba.gateway.filter.model.WhiteListEntity;import org.springframework.stereotype.Repository;/** * @Description: 网关路由白名单DAO接口 * @Author: junqiang.lu * @Date: 2022/8/23 */@Repositorypublic interface WhiteListMapper extends BaseMapper<WhiteListEntity> {}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
4.3 初始化加载白名单
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/common/component/WhiteListHandler.java
- 1
package com.ljq.demo.springboot.alibaba.gateway.filter.common.component;import com.baomidou.mybatisplus.core.toolkit.Wrappers;import com.ljq.demo.springboot.alibaba.gateway.filter.common.constant.RedisKeyConst;import com.ljq.demo.springboot.alibaba.gateway.filter.dao.WhiteListMapper;import com.ljq.demo.springboot.alibaba.gateway.filter.model.WhiteListEntity;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.ApplicationArguments;import org.springframework.boot.ApplicationRunner;import org.springframework.stereotype.Component;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @Description: 网关白名单处理类 * @Author: junqiang.lu * @Date: 2022/8/23 */@Slf4j@Componentpublic class WhiteListHandler implements ApplicationRunner { @Autowired private WhiteListMapper whiteListMapper; @Autowired private RedisComponent redisComponent; @Override public void run(ApplicationArguments args) throws Exception { // 将所有白名单加载到缓存中 log.info("-------------加载网关路由白名单------------------"); List<WhiteListEntity> whiteListList = whiteListMapper.selectList(Wrappers.emptyWrapper()); Map<String, Object> whiteListMap = new HashMap<>(16); whiteListList.forEach(whiteList -> whiteListMap.put(whiteList.getId().toString(), whiteList)); redisComponent.mapPutBatch(RedisKeyConst.KEY_GATEWAY_WHITE_LIST, whiteListMap); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
4.4 权限过滤器
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/interceptor/AuthFilter.java
- 1
package com.ljq.demo.springboot.alibaba.gateway.filter.interceptor;import cn.hutool.json.JSONUtil;import com.ljq.demo.springboot.alibaba.gateway.filter.common.api.ApiResult;import com.ljq.demo.springboot.alibaba.gateway.filter.common.component.RedisComponent;import com.ljq.demo.springboot.alibaba.gateway.filter.common.constant.RedisKeyConst;import com.ljq.demo.springboot.alibaba.gateway.filter.model.WhiteListEntity;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;import java.util.List;/** * @Description: 鉴权拦截器 * @Author: junqiang.lu * @Date: 2020/12/8 */@Slf4j@Componentpublic class AuthFilter implements GlobalFilter, Ordered { private static final String TOKEN_KEY = "token"; @Autowired private RedisComponent redisComponent; /** * 权限过滤 * * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String requestPath = exchange.getRequest().getPath().value(); log.info("requestPath: {}",requestPath); // 判断是否符合白名单 if (validateWhiteList(requestPath)) { return chain.filter(exchange); } List<String> tokenList = exchange.getRequest().getHeaders().get(TOKEN_KEY); log.info("token: {}", tokenList); if (CollectionUtils.isEmpty(tokenList) || tokenList.get(0).trim().isEmpty()) { ServerHttpResponse response = exchange.getResponse(); // 错误信息 byte[] data = JSONUtil.toJsonStr(ApiResult.fail("Token is null")).getBytes(StandardCharsets.UTF_8); DataBuffer buffer = response.bufferFactory().wrap(data); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); return response.writeWith(Mono.just(buffer)); } return chain.filter(exchange); } /** * 设置执行级别 * * @return */ @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } /** * 请求路径 * * @param requestPath * @return */ public boolean validateWhiteList(String requestPath) { List<WhiteListEntity> whiteListList = redisComponent.mapGetAll(RedisKeyConst.KEY_GATEWAY_WHITE_LIST, WhiteListEntity.class); for (WhiteListEntity whiteList : whiteListList) { if (requestPath.contains(whiteList.getPath()) || requestPath.matches(whiteList.getPath())) { return true; } } return false; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
必须在进入过滤器后首先进行白名单校验
白名单规则支持正则表达式
4.5 白名单 Service 层
Service 接口
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/service/WhiteListService.java
- 1
package com.ljq.demo.springboot.alibaba.gateway.filter.service;import com.ljq.demo.springboot.alibaba.gateway.filter.common.api.ApiResult;import com.ljq.demo.springboot.alibaba.gateway.filter.model.*;/** * @Description: 白名单业务接口 * @Author: junqiang.lu * @Date: 2022/8/23 */public interface WhiteListService { /** * 新增单条 * * @param addParam * @return */ ApiResult add(WhiteListAddParam addParam); /** * 查询单条 * * @param infoParam * @return */ ApiResult info(WhiteListInfoParam infoParam); /** * 分页查询 * * @param pageParam * @return */ ApiResult page(WhiteListPageParam pageParam); /** * 更新单条 * * @param updateParam * @return */ ApiResult update(WhiteListUpdateParam updateParam); /** * 删除单条 * * @param deleteParam * @return */ ApiResult delete(WhiteListDeleteParam deleteParam);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
Service 业务实现类
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/service/impl/WhiteListServiceImpl.java
- 1
package com.ljq.demo.springboot.alibaba.gateway.filter.service.impl;import cn.hutool.core.bean.BeanUtil;import cn.hutool.core.bean.copier.CopyOptions;import cn.hutool.core.util.StrUtil;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.core.metadata.IPage;import com.baomidou.mybatisplus.core.toolkit.Wrappers;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.ljq.demo.springboot.alibaba.gateway.filter.common.api.ApiResult;import com.ljq.demo.springboot.alibaba.gateway.filter.common.component.RedisComponent;import com.ljq.demo.springboot.alibaba.gateway.filter.common.constant.RedisKeyConst;import com.ljq.demo.springboot.alibaba.gateway.filter.dao.WhiteListMapper;import com.ljq.demo.springboot.alibaba.gateway.filter.model.*;import com.ljq.demo.springboot.alibaba.gateway.filter.service.WhiteListService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.Objects;/** * @Description: 网关路由白名单业务实现类 * @Author: junqiang.lu * @Date: 2022/8/23 */@Servicepublic class WhiteListServiceImpl extends ServiceImpl<WhiteListMapper, WhiteListEntity> implements WhiteListService { @Autowired private RedisComponent redisComponent; /** * 新增单条 * * @param addParam * @return */ @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class}) public ApiResult add(WhiteListAddParam addParam) { WhiteListEntity whiteListParam = new WhiteListEntity(); BeanUtil.copyProperties(addParam, whiteListParam, CopyOptions.create().ignoreError().ignoreNullValue()); long nowTime = System.currentTimeMillis(); whiteListParam.setCreateDate(nowTime); whiteListParam.setUpdateDate(nowTime); this.save(whiteListParam); redisComponent.mapPut(RedisKeyConst.KEY_GATEWAY_WHITE_LIST, whiteListParam.getId().toString(), whiteListParam); return ApiResult.success(whiteListParam); } /** * 查询单条 * * @param infoParam * @return */ @Override public ApiResult info(WhiteListInfoParam infoParam) { WhiteListEntity whiteList = redisComponent.mapGet(RedisKeyConst.KEY_GATEWAY_WHITE_LIST, infoParam.getId().toString(), WhiteListEntity.class); if (Objects.isNull(whiteList)) { whiteList = this.getById(infoParam.getId()); } return ApiResult.success(whiteList); } /** * 分页查询 * * @param pageParam * @return */ @Override public ApiResult page(WhiteListPageParam pageParam) { IPage<WhiteListEntity> page = new Page<>(pageParam.getCurrentPage(), pageParam.getPageSize()); LambdaQueryWrapper<WhiteListEntity> queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(StrUtil.isNotBlank(pageParam.getRouteType()), WhiteListEntity::getRouteType, pageParam.getRouteType()) .like(StrUtil.isNotBlank(pageParam.getPath()), WhiteListEntity::getPath, pageParam.getPath()) .like(StrUtil.isNotBlank(pageParam.getComment()), WhiteListEntity::getComment, pageParam.getComment()); return ApiResult.success(this.page(page, queryWrapper)); } /** * 更新单条 * * @param updateParam * @return */ @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class}) public ApiResult update(WhiteListUpdateParam updateParam) { WhiteListEntity whiteListParam = new WhiteListEntity(); BeanUtil.copyProperties(updateParam, whiteListParam, CopyOptions.create().ignoreError().ignoreNullValue()); long nowTime = System.currentTimeMillis(); whiteListParam.setUpdateDate(nowTime); boolean flag = this.updateById(whiteListParam); if (flag) { redisComponent.mapPut(RedisKeyConst.KEY_GATEWAY_WHITE_LIST, whiteListParam.getId().toString(), whiteListParam); } return ApiResult.success(flag); } /** * 删除单条 * * @param deleteParam * @return */ @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class}) public ApiResult delete(WhiteListDeleteParam deleteParam) { boolean flag = this.removeById(deleteParam.getId()); if (flag) { redisComponent.mapRemove(RedisKeyConst.KEY_GATEWAY_WHITE_LIST, deleteParam.getId().toString()); } return ApiResult.success(flag); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
白名单的写操作都需要同步到缓存中
4.6 白名单控制层
./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/controller/WhiteListController.java
- 1
package com.ljq.demo.springboot.alibaba.gateway.filter.controller;import com.baomidou.mybatisplus.core.metadata.IPage;import com.ljq.demo.springboot.alibaba.gateway.filter.common.api.ApiResult;import com.ljq.demo.springboot.alibaba.gateway.filter.model.*;import com.ljq.demo.springboot.alibaba.gateway.filter.service.WhiteListService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.*;/** * @Description: 网关路由白名单控制器 * @Author: junqiang.lu * @Date: 2022/8/23 */@Slf4j@RestController@RequestMapping(value = "/api/gateway/whitelist")public class WhiteListController { @Autowired private WhiteListService whiteListService; /** * 新增白名单 * * @param addParam * @return */ @PostMapping(value = "/add", produces = {MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<ApiResult<WhiteListEntity>> add(@RequestBody @Validated WhiteListAddParam addParam) { log.info("/add,新增白名单参数: {}", addParam); return ResponseEntity.ok(whiteListService.add(addParam)); } /** * 查询单条白名单 * * @param infoParam * @return */ @GetMapping(value = "/info", produces = {MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<ApiResult<WhiteListEntity>> info(@Validated WhiteListInfoParam infoParam) { log.info("/info,查询单条白名单参数: {}", infoParam); return ResponseEntity.ok(whiteListService.info(infoParam)); } /** * 查询单条白名单 * * @param pageParam * @return */ @GetMapping(value = "/page", produces = {MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<ApiResult<IPage<WhiteListEntity>>> page(@Validated WhiteListPageParam pageParam) { log.info("/page,分页查询白名单参数: {}", pageParam); return ResponseEntity.ok(whiteListService.page(pageParam)); } /** * 修改白名单 * * @param updateParam * @return */ @PutMapping(value = "/update", produces = {MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<ApiResult<Boolean>> update(@RequestBody @Validated WhiteListUpdateParam updateParam) { log.info("/update,修改白名单参数: {}", updateParam); return ResponseEntity.ok(whiteListService.update(updateParam)); } /** * 删除单条白名单 * * @param deleteParam * @return */ @DeleteMapping(value = "/delete", produces = {MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<ApiResult<Boolean>> delete(@RequestBody @Validated WhiteListDeleteParam deleteParam) { log.info("/delete,删除单条白名单参数: {}", deleteParam); return ResponseEntity.ok(whiteListService.delete(deleteParam)); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
5 Github 源码
Gtihub 源码地址 :