- Mvn包配置
- 配置文件
- 代码
Mvn包配置
<dependencies>
<!-- 只需要网关服务需要 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos 服务注册 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- spring boot 2.4之后默认禁用bootstrap.yml配置文件,生效需要引入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
配置文件
Gateway服务配置文件: bootstrap.yml
server:
port: 8080
spring:
application:
name: gateway
profiles:
active: @profiles.active@
cloud:
nacos:
discovery: # 服务注册
server-addr: @nacos.server@
username: @nacos.username@
password: @nacos.password@
namespace: @nacos.namespace-id@
service: ${spring.application.name}-${spring.profiles.active}
config: # 配置管理
server-addr: @nacos.server@
username: @nacos.username@
password: @nacos.password@
namespace: @nacos.namespace-id@
file-extension: yml
shared-configs: # 指定额外的配置文件
- data-id: application.yml
- data-id: redis.yml
loadbalancer:
nacos:
enabled: true # 开启nacos负载均衡策略
gateway:
routes: # 网关路由配置
- id: auth-service # 路由id,自定义,只要唯一即可
#uri: http://127.0.0.1:8088 # 路由的目标地址 http就是固定地址
uri: lb://auth-${spring.profiles.active} # 路由的目标地址 lb就是负载均衡,后面跟Nacos服务发现名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/auth/** # 这个是按照路径匹配,只要以 /auth/ 开头就符合要求
filters: # 路由过滤器,对请求或者响应做出处理
- StripPrefix=1 # 去掉路径一条前缀,这里是: /auth
metadata:
auth-white-list:
- /login/**
- /logout/**
- /v3/api-docs/**
- id: chat-service # 路由id,自定义,只要唯一即可
#uri: http://127.0.0.1:8088 # 路由的目标地址 http就是固定地址
uri: lb://chat-${spring.profiles.active} # 路由的目标地址 lb就是负载均衡,后面跟Nacos服务发现名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/chat/** # 这个是按照路径匹配,只要以 /chat/ 开头就符合要求
filters: # 路由过滤器,对请求或者响应做出处理
- StripPrefix=1 # 去掉路径一条前缀,这里是: /chat
metadata:
auth-white-list:
- /v3/api-docs/**
代码
路由管理器,主要用于获取metadata.auth-white-list 的白名单列表数据,用于跳过鉴权。
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/***
* 路由管理
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class RouteMetadataService {
private final RouteLocator routeLocator;
private final Map<String, List<String>> routeExcludePaths = new HashMap<>();
/**
* 加载所有路由的元数据
*/
public void loadMetadata() {
Flux<Route> routes = routeLocator.getRoutes();
routes.subscribe(route -> {
// 从元数据获取鉴权白名单,路径在白名单中,无需鉴权
List<String> authWhiteList = Optional.ofNullable(route.getMetadata().get("auth-white-list"))
.map(obj -> {
if (obj instanceof List) {
// 直接转换为List<String>
return (List<String>) obj;
} else if (obj instanceof Map) {
// 如果是Map,提取所有值组成List
Map<?, ?> map = (Map<?, ?>) obj;
return map.values().stream()
.filter(String.class::isInstance)
.map(String.class::cast)
.collect(Collectors.toList());
} else {
log.warn("auth-white-list 类型不匹配: {}", obj.getClass().getName());
return List.<String>of();
}
})
.orElse(List.of()); // 默认返回空列表
routeExcludePaths.put(route.getId(), authWhiteList);
});
}
/**
* 获取指定路由的白名单路径列表
*/
public List<String> getauthWhiteList(String routeId) {
return routeExcludePaths.getOrDefault(routeId, List.of());
}
/**
* 动态路由更新时调用此方法刷新缓存
*/
@PostConstruct
public void refreshMetadata() {
routeExcludePaths.clear();
loadMetadata();
}
}
AuthFilter鉴权过滤器
import com.fasterxml.jackson.core.JsonProcessingException;
import com.jagger.ai.services.gateway.service.RouteMetadataService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
// 设置优先级高于WebsocketRoutingFilter的等级(Integer.MAX_VALUE - 1),否则不会过滤Websocket请求,
// 要大于等于2,否则影响 StripPrefix 截取路由获取path(不影响后续转发)
@Order(Integer.MAX_VALUE - 2)
public class AuthFilter implements GlobalFilter {
private final RouteMetadataService routeMetadataService; // gateway路由元数据
private final PathMatcher pathMatcher = new AntPathMatcher(); // Spring内置的路径匹配器
private final ObjectMapper objectMapper; // JSON 序列化工具
private final RedisService redisService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.debug("全局鉴权开始");
HttpHeaders headers = exchange.getRequest().getHeaders();
// 检查是否包含 WebSocket 升级头
boolean isWebSocket = headers.containsKey(HttpHeaders.UPGRADE) &&
headers.getConnection().contains("Upgrade") &&
"websocket".equalsIgnoreCase(headers.getFirst(HttpHeaders.UPGRADE));
String token;
if (isWebSocket) {
// WebSocket 请求处理
log.debug("检测到 WebSocket 请求");
token = exchange.getRequest().getQueryParams().getFirst("token");
log.debug("WebSocket token: {}", token);
} else {
log.info("开始鉴权Http");
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
if (route != null) {
// 过滤白名单路由鉴权
List<String> authWhiteList = routeMetadataService.getauthWhiteList(route.getId());
String path = exchange.getRequest().getURI().getPath();
if (authWhiteList.stream().anyMatch(pattern -> pathMatcher.match(pattern, path))) {
log.debug("白名单路由: {}", path);
return chain.filter(exchange);
}
}
// 从请求头获取Token
token = exchange.getRequest().getHeaders().getFirst("Authorization");
}
// 验证Token逻辑(如调用Auth服务)
User user = isValidToken(token);
if (user == null) {
return Result.error(HttpStatus.UNAUTHORIZED).toMono(exchange.getResponse(), objectMapper);
}
// 鉴权通过,添加用户信息到请求头
try {
objectMapper.writeValueAsString(user);
ServerHttpRequest request = exchange.getRequest()
.mutate()
.header(HttpHeaderKeys.USER_INFO, objectMapper.writeValueAsString(user)) // 添加完整用户 JSON
.build();
// 构建新的 ServerWebExchange 并继续传递
ServerWebExchange newExchange = exchange.mutate().request(request).build();
return chain.filter(newExchange);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/***
* 鉴权判断
*/
private User isValidToken(String token) {
log.debug("鉴权token: {}", token);
return redisService.getCacheObject(RedisPath.USER_TOKEN + token);
}
}
调用示例
Http调用,获取token对应用户信息
curl --location --request GET '127.0.0.1:8080/auth/get_user_by_token' \
--header 'Authorization: e49d0fd1-5248-4f5a-bc49-ebaf5cb1ce68'
Websocket因为js不能带有Header信息,所以放在请求地址上
// 创建WebSocket连接(替换为你的WebSocket服务器地址)
const socket = new WebSocket('ws://127.0.0.1:8080/chat/im?token=e49d0fd1-5248-4f5a-bc49-ebaf5cb1ce68');