添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xxx</groupId>
<artifactId>xxx-gateway</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>xxx-gateway</name>
<description>网关服务 限流 鉴权 转发</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
<lombok.version>1.18.20</lombok.version>
<fastjson.version>1.2.73</fastjson.version>
<hutool.version>5.7.2</hutool.version>
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos注册中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
添加配置
server:
port: 9001
servlet:
context-path: /
spring:
profiles:
active: dev
application:
name: gateway-server # 应用名称
cloud:
nacos:
username:
password:
#nacos注册地址
discovery:
server-addr: ${wfx.ip}:8848
config:
#配置文件后缀
file-extension: yaml
#命名空间 默认public
namespace:
#组
group: ${spring.application.name}-group
#配置中心地址
server-addr: ${wfx.ip}:8848
gateway:
#处理跨越问题
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
globalcors:
corsConfigurations:
'[/**]':
# 允许携带认证信息
allow-credentials: true
# 允许跨域的源(网站域名/ip),设置*为全部
allowedOrigins: "*"
# 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
allowedMethods: "*"
# 允许跨域请求里的head字段,设置*为全部
allowedHeaders: "*"
discovery:
locator:
enabled: true
routes:
#搜索服务
- id: search-server
# 重点!/info必须使用http进行转发,lb代表从注册中心获取服务
uri: lb://search-server
predicates:
#IP白名单针对当前服务的
- RemoteAddr=127.0.0.1
# 重点!转发该路径!,/**,
- Path=/search/**
filters:
#去掉第一个路径
- StripPrefix=1
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
#日志级别
org.springframework.cloud.gateway: info
#自定义配置
define:
ev: ${spring.profiles.active}
#放行url
urlWhileList:
# - /search/tesst/*
#全局黑名单
blackList:
- 127.0.0.2
Nacos动态刷新自定义配置
@Configuration
@RefreshScope
@Data
@ConfigurationProperties(prefix = "define")
public class BaseBean {
/**
* 黑名单
*/
private List<String> blackList;
/**
* 放行路径
*/
private List<String> urlWhileList;
/**
* 当前环境
*/
private String ev;
}
Gateway拦截器 公共token的校验 全局限流
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ApiGlobalFilter implements GlobalFilter, Ordered {
//自定义的配置
private final BaseBean baseBean;
private final String TEST = "test";
private final String DEV = "dev";
//默认店铺id
private final String SID = "0";
//默认分区
private final String DB_TAG = "fxcs";
//默认用户id
private final String UID = "0";
/**
* 拦截所有的请求头
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
Route gatewayUrl = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
URI uri = gatewayUrl.getUri();
baseBean.getEv();
//全局黑名单ip
if (!CollectionUtils.isEmpty(baseBean.getBlackList()) && baseBean.getBlackList().contains(IpUtil.getIp(exchange.getRequest()))) {
log.error("系统环境:{},禁止访问=====Ip:{},请求服务名:{},url:{},路径:{}"
, baseBean.getEv()
, IpUtil.getIp(exchange.getRequest())
, uri.getAuthority()
, exchange.getRequest().getURI()
, exchange.getRequest().getPath());
return authErr(response, ErrorEnum.SYSTEM_BLANK.code(), ErrorEnum.SYSTEM_BLANK.value());
}
//放行路径
if (UrlResolverUtil.check(baseBean.getUrlWhileList(), exchange.getRequest().getURI().getPath())) {
log.info("系统环境:{},请求======Ip:{},请求服务名:{},url:{},放行路径:{}"
, baseBean.getEv()
, IpUtil.getIp(exchange.getRequest())
, uri.getAuthority()
, exchange.getRequest().getURI()
, exchange.getRequest().getPath());
return chain.filter(exchange);
}
//测试环境或者开发环境 不需要效验
if (TEST.equals(baseBean.getEv()) || DEV.equals(baseBean.getEv())) {
log.info("系统环境:{},请求======Ip:{},集群标识:{},SID:{},请求服务名:{},url:{},路径:{}"
, baseBean.getEv()
, IpUtil.getIp(exchange.getRequest())
, DB_TAG
, SID
, uri.getAuthority()
, exchange.getRequest().getURI()
, exchange.getRequest().getPath());
Consumer<HttpHeaders> httpHeaders = httpHeader -> {
httpHeader.set("SID", SID);
httpHeader.set("DB_TAG", DB_TAG);
};
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeaders).build();
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(build);
}
//认证
String token = "";
MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
//先从header获取
List<String> list = exchange.getRequest().getHeaders().get("token");
//入参获取
String rToken = list == null ? exchange.getRequest().getQueryParams().getFirst("token") : list.get(0);
//从cookie获取
HttpCookie r_token = cookies.get("r_token") == null ? null : cookies.get("token").stream()
.filter(a -> "r_token".equals(a.getName()))
.findFirst()
.orElse(null);
token = r_token == null ? rToken : r_token.getValue();
if (StrUtil.isBlank(token)) {
log.error("系统环境:{},禁止访问=====Ip:{},请求服务名:{},url:{},路径:{}"
, baseBean.getEv()
, IpUtil.getIp(exchange.getRequest())
, uri.getAuthority()
, exchange.getRequest().getURI()
, exchange.getRequest().getPath());
return authErr(response, ErrorEnum.SYSTEM_BLANK.code(), ErrorEnum.SYSTEM_BLANK.value());
}
JSONObject jsonObject;
try {
//检验token
jsonObject = TokenUtil.verify(token);
} catch (Exception e) {
log.error("系统环境:{},禁止访问=====Ip:{},请求服务名:{},url:{},路径:{},异常:{}"
, baseBean.getEv()
, IpUtil.getIp(exchange.getRequest())
, uri.getAuthority()
, exchange.getRequest().getURI()
, exchange.getRequest().getPath()
, e);
return authErr(response, ErrorEnum.SYSTEM_ILLEGAL_TOKEN.code(), ErrorEnum.SYSTEM_ILLEGAL_TOKEN.value());
}
JSONObject finalJsonObject = jsonObject;
Consumer<HttpHeaders> httpHeaders = httpHeader -> {
httpHeader.set("SID", finalJsonObject.getString("SID"));
httpHeader.set("DB_TAG", finalJsonObject.getString("DB_TAG"));
httpHeader.set("UID", finalJsonObject.getString("UID"));
};
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeaders).build();
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(build);
}
private Mono<Void> authErr(ServerHttpResponse response, int code, String msg) {
JSONObject message = new JSONObject();
// 响应状态
message.put("code", code);
// 响应内容
message.put("msg", msg);
// 转换响应消息内容对象为字节
byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
// 设置响应对象状态码 200
response.setStatusCode(HttpStatus.OK);
// 设置响应对象内容并且指定编码,否则在浏览器中会中文乱码Content-Type:
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
// 返回响应对象
return response.writeWith(Mono.just(buffer));
}
/**
*负责filter的顺序,数字越小越优先,越靠前
*/
@Override
public int getOrder() {
return 0;
}
用到的工具类
public class UrlResolverUtil {
private final static PathMatcher MATCHER = new AntPathMatcher();
/**
* 验证url是否匹配,支持精确匹配和模糊匹配
*
* @param patternPaths
* @param requestPath
* @return
*/
public static boolean check(List<String> patternPaths, String requestPath) {
if (CollectionUtils.isEmpty(patternPaths)) {
return false;
}
for (String i : patternPaths) {
if (i.endsWith("*")) {
i = i.substring(0, i.length() - 1);
if (MATCHER.matchStart(requestPath, i)) {
return true;
}
}
if (MATCHER.match(i, requestPath)) {
return true;
}
}
return false;
}
}
public enum ErrorEnum {
SYSTEM_RED("网络异常,稍后再试", 500),
SYSTEM_SKIP("重定向", 301),
SYSTEM_GREEN("非法参数", 400),
SYSTEM_PARAM_NOT_EXIST("缺少参数", 402),
SYSTEM_BLANK("没有权限!", 401),
SYSTEM_ILLEGAL_TOKEN("非法token,请重新登录!", 403),
SYSTEM_RESOURCE_NOT_FIND("资源不存在",410),
SYSTEM_CONFLICT("数据冲突!", 409),
SYSTEM_OFTEN("操作太频繁,稍后再试", 423)
;
private final Integer code;
private final String value;
ErrorEnum(String value, Integer code) {
this.code = code;
this.value = value;
}
public Integer code() {
return this.code;
}
public String value() {
return this.value;
}
public static ErrorEnum getTypeByEnum(Integer code) {
if (code==null) {
return null;
}
for (ErrorEnum enums : ErrorEnum.values()) {
if (enums.code().equals(code)) {
return enums;
}
}
return null;
}
}
public class TokenUtil {
/**
* 秘钥
*/
public static final String key = "自定义";
/**
* 有效期,默认2小时
*/
public static final long expirationTimeInSecond = 60*60*2;
/**
* 生成token 无过期时间 默认算法是:HS256(HmacSHA256)
* @param payload 荷载信息
* @return String Token
*/
public static String createToken( Map<String, Object> payload) {
return JWT.create()
.addPayloads(payload)
.setKey(key.getBytes())
.sign();
}
/**
* 生成token 默认算法是:HS256(HmacSHA256)
* @param payload 荷载信息
* @param expiration 过期时间 单位秒 默认两小时
* @return String Token
*/
public static String createToken(Map<String, Object> payload, Long expiration) {
//签发时间
Date date = new Date();
payload.put(JWTPayload.ISSUED_AT, date);
//到期时间
long l = DateUtil.timeToLong(date).longValue();
long expirationTime = expiration == null ? l+expirationTimeInSecond : l+expiration.longValue();
payload.put(JWTPayload.EXPIRES_AT, DateUtil.longToDate(expirationTime));
return JWT.create()
.addPayloads(payload)
.setKey(key.getBytes())
.sign();
}
/**
* 验证JWT Token是否有效
*
* @param token token
* @return JSONObject 荷载信息的JSONObject
* @throws ValidateException 过期异常
* @throws JWTException 非法token异常
*/
public static JSONObject verify(String token) throws JWTException, ValidateException {
JWT jwt = JWT.of(token).setKey(key.getBytes());
JWTValidator jwtValidator = JWTValidator.of(jwt);
jwtValidator.validateDate(new Date());
return JSONObject.parseObject(jwt.getPayloads().toString());
}
}
public class IpUtil {
public static String getIp(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress().getAddress().getHostAddress();
}
return ip;
}
}
Gateway全局异常处理错误
@Slf4j
@Order(-1)
@RequiredArgsConstructor
@Configuration
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper;
private final BaseBean baseBean;
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
log.error("系统环境:{},请求======Ip:{},url:{},路径:{},Gateway全局异常处理:{}"
,baseBean.getEv()
, IpUtil.getIp(exchange.getRequest())
, exchange.getRequest().getURI()
, exchange.getRequest().getPath()
, ex);
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
Map map = new HashMap<>(2);
// header set
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
if (ex instanceof ResponseStatusException) {
HttpStatus status = ((ResponseStatusException) ex).getStatus();
response.setStatusCode(status);
map.put("code",status.value());
}else {
map.put("code", ErrorEnum.SYSTEM_RED.code());
}
map.put("msg",ex.getMessage());
map.put("data","");
return response
.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
try {
return bufferFactory.wrap(objectMapper.writeValueAsBytes(map));
} catch (JsonProcessingException e) {
log.error("Gateway全局异常处理错误", ex);
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
下期分享整合SpringCloudAlibaba----Gateway与Sentinel