网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 网关来做,这样既提高业务灵活性又不缺安全性。
RBAC基于角色访问控制,目前使用最为广泛的权限模型。相信大家对这种权限模型已经比较了解了。此模型有三个用户、角色和权限,在传统的权限模型用户直接关联加了角色,解耦了用户和权限,使得权限系统有了更清晰的职责划分和更高的灵活度
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson.version}version>
dependency>
@Order(1)
@Component
public class AuthorizationFilter implements GlobalFilter {
@Autowired
private AuthConfig myConfig;
@Autowired
private AuthService authService;
@Autowired
private RedisUtil redisUtil;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
JSONObject message = new JSONObject();
ServerHttpRequest request = exchange.getRequest();
//1、获取请求路径
String path = request.getPath().toString();
//处理白名单内不需要进行拦截的地址
if(!Objects.isNull(myConfig.getWhiteList())){
for (String whitePath : myConfig.getWhiteList()) {
if(path.contains(whitePath)){
return chain.filter(exchange);
}
}
}
//获取对应的token
String token = request.getHeaders().getFirst("Authorization");
//判断传递了token才可以继续解析
if(!StringUtils.isEmpty(token)){
try {
//先去本地redis根据token获取value
String userPhoneRedis = redisUtil.getTokenKey(token);
//如果没有查到,或者过期时间小于10分钟,则去权限获取信息,权限会进行token续约
if (Objects.isNull(userPhoneRedis) || redisUtil.getExpire(userPhoneRedis) < 600) {
String userId=authService.checkToken(token);
//如果校验token成功
if(null!=userId){
//缓存到redis中,一段时间内都不用校验token了
//token为key,用户id为value,存储7200秒(2小时)
redisUtil.set(token,userId,7200);
//放行继续向下调用
return chain.filter(exchange);
}
}else{
return chain.filter(exchange);//放行继续向下调用
}
}catch (Exception e){
//拦截请求,返回状态码401
message.put("status", -1);
message.put("data", "鉴权失败");
byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
message.put("status", -1);
message.put("data", "鉴权失败");
byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
GlobalFilter是Spring Cloud Gateway中用于过滤请求的组件,它可以在请求与路由匹配时,将所有的GlobalFilter和特定的GatewayFilter添加到过滤器链中,然后按照org.springframework.core.Ordered接口排序执行。
GlobalFilter具有与GatewayFilter相同的签名,因此可以在处理请求之前和之后执行一些操作,例如检查请求头、修改请求参数、验证权限等。在实现GlobalFilter接口时,需要实现filter方法,该方法接受一个WebExchange作为参数。
在Spring Cloud Gateway中,有一些默认的GlobalFilter,例如ForwardRoutingFilter和RouteToRequestUrlFilter,它们分别用于将请求转发到当前网关实例本地接口和将请求路由到指定的URL。
当请求与路由匹配时,过滤web处理程序会将GlobalFilter的所有实例和GatewayFilter的所有路由特定实例添加到过滤器链中。这个组合过滤器链由org.springframework.core.Ordered接口排序,您可以通过实现getOrder()方法来设置该接口,值越小,越先执行。由于Spring Cloud Gateway区分了过滤器逻辑执行的“pre”和“post”阶段,优先级最高的过滤器是“pre”阶段的第一个,“post”的最后一个。
在gateway模块中有多个GlobalFilter时,你可以通过实现Ordered接口,或者使用@Order注解来指定每个GlobalFilter的执行顺序。order值越小,优先级越高,执行顺序越靠前。
如果多个GlobalFilter的order值一样,会按照默认的顺序执行,这个默认顺序是:defaultFilter > 路由过滤器 > GlobalFilter。
例如,在我的网关模块中,有两个自定义的GlobalFilter,一个用于做跨域处理,一个用户做权限鉴权,我们需要指定它们的执行顺序,优先应该走权限鉴权的,假如你都没有权限去请求服务,那么就不需要再去考虑当前请求是否跨域了。以上图片展示是通过@Order的方式实现的GlobalFilter自定义执行顺序;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 在请求处理之前执行一些操作
// ...
// 继续执行下一个过滤器或路由处理程序
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 返回该过滤器的优先级值,值越小优先级越高
return 1;
}
}
这个示例中,我们创建了一个名为CustomGlobalFilter的类,它实现了GlobalFilter和Ordered接口。在filter()方法中,我们可以执行一些在请求处理之前需要执行的操作。getOrder()方法返回该过滤器的优先级值,这里我们返回了1,表示该过滤器将优先执行。