- 过滤器示例:
package com.ttpark.gateway.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ttpark.common.exception.CommonException;
import com.ttpark.common.utils.SecureUtil;
import com.ttpark.common.web.ResponseModel;
import com.ttpark.gateway.enums.ErrorEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Configuration
public class AuthVerifyFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(AuthVerifyFilter.class);
@Autowired
GatewayFilterConfiguration gatewayFilterConfiguration;
@Autowired
private RedisTemplate redisTemplate;
private static final String oss_cache_prefix = "ONET-OSS-";
private static final String pda_cache_prefix = "ONET-PDA-";
private static final String app_cache_prefix = "ONET-APP-";
private static final Long EXPIRE_TIME = 10000L; //10秒钟
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
logger.info("请求地址:{}", path);
AuthVerifyExclude authVerifyExclude = gatewayFilterConfiguration.getAuthVerifyExclude(path);
if (authVerifyExclude != null) { //忽略验证
logger.info("忽略验证:{}", path);
if (authVerifyExclude.isGenerateToken()) { //是否生成临时token
logger.info("生成临时token");
Integer tenantId = -1;
if (request.getHeaders().getFirst("tenantId") != null) {
tenantId = Integer.parseInt(request.getHeaders().getFirst("tenantId"));
}
String token = SecureUtil.getToken(tenantId,-1,"", EXPIRE_TIME); //生成一个临时的token
ServerHttpRequest host = exchange.getRequest().mutate().header("authKey", token).build();
ServerWebExchange change = exchange.mutate().request(host).build();
return chain.filter(change);
}
return chain.filter(exchange);
}
String authKey = request.getHeaders().getFirst("authKey");
ServerHttpResponse response = exchange.getResponse();
if (StringUtils.isEmpty(authKey)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return response.writeWith(Mono.just(getFailureResponseBody(exchange, ErrorEnum.UNAUTHENTICATED.getValue(), ErrorEnum.UNAUTHENTICATED.getName())));
}
try {
tokenVerify(authKey, path);
return chain.filter(exchange);
} catch (Exception e) {
if (e instanceof CommonException) {
logger.error("网关检验未通过{}", e.getMessage());
CommonException ex = (CommonException) e;
return response.writeWith(Mono.just(getFailureResponseBody(exchange, ex.getCode(), ex.getMessage())));
}
logger.error("网关异常", e);
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return response.writeWith(Mono.just(getFailureResponseBody(exchange, ErrorEnum.UNAUTHORIZED.getValue(), ErrorEnum.UNAUTHORIZED.getName())));
}
}
@Override
public int getOrder() {
return -2;
}
private DataBuffer getFailureResponseBody(ServerWebExchange exchange, Integer code, String message) {
ServerHttpResponse response = exchange.getResponse();
//设置headers
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
return response.bufferFactory().wrap(JSON.toJSONBytes(new ResponseModel(code, message)));
}
private void tokenVerify(String authKey, String path) {
if (StringUtils.isEmpty(path)) {
return;
}
String subject = SecureUtil.getContent(authKey).getSubject();
JSONObject jsonObject = JSONObject.parseObject(subject);
int minutes=7*24*60;
String cacheValueKey = null;
String cacheKey = null;
if (path.toLowerCase().contains("onet-oss")) {
cacheKey = oss_cache_prefix;
cacheValueKey = "userName";
minutes=30;
} else if (path.toLowerCase().contains("onet-app")) {
cacheKey = app_cache_prefix;
cacheValueKey = "userId";
} else if (path.toLowerCase().contains("onet-pda")) {
cacheKey = pda_cache_prefix;
cacheValueKey = "userId";
}
if (cacheValueKey == null || cacheKey == null) {
throw new CommonException(ErrorEnum.UNAUTHORIZED.getValue(), ErrorEnum.UNAUTHORIZED.getName());
}
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
Object token = redisTemplate.opsForValue().get(cacheKey + jsonObject.get(cacheValueKey));
logger.info("authKey:{},redis-authKey:{}", authKey, token);
if (token == null) {
throw new CommonException(ErrorEnum.AUTHKEY.getValue(), ErrorEnum.AUTHKEY.getName());
}
if (!authKey.equals(String.valueOf(token))) {
throw new CommonException(ErrorEnum.OTHER_SPACE_LOGIN.getValue(), ErrorEnum.OTHER_SPACE_LOGIN.getName());
}
redisTemplate.opsForValue().set(cacheKey + jsonObject.get(cacheValueKey), token, minutes, TimeUnit.MINUTES);
}
}
@Component
@ConfigurationProperties(prefix = "spring.cloud.gateway.auth-verify-filter")
class GatewayFilterConfiguration {
private List excludes = new ArrayList<>();
private boolean enableSwagger = false;
public AuthVerifyExclude getAuthVerifyExclude(String path) {
if (StringUtils.isEmpty(path)) {
return null;
}
if (enableSwagger && (path.toLowerCase().contains("swagger-ui.html")
|| path.toLowerCase().contains("/webjars/springfox-swagger-ui"))
|| path.toLowerCase().contains("/swagger-resources") ||
path.toLowerCase().contains("/v2/api-docs")) {
return new AuthVerifyExclude();
}
if (excludes == null || excludes.size() == 0) {
return null;
}
for (AuthVerifyExclude authVerifyExclude : excludes) {
if (path.toLowerCase().contains(authVerifyExclude.getUri().toLowerCase())) {
return authVerifyExclude;
}
}
return null;
}
public List getExcludes() {
return excludes;
}
public void setExcludes(List excludes) {
this.excludes = excludes;
}
public void setEnableSwagger(boolean enableSwagger) {
this.enableSwagger = enableSwagger;
}
}
class AuthVerifyExclude {
private String uri;
private boolean generateToken = false; //是否生成临时token
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public boolean isGenerateToken() {
return generateToken;
}
public void setGenerateToken(boolean generateToken) {
this.generateToken = generateToken;
}
}
- 跨域请求配置
package com.ttpark.gateway.filter;
import com.alibaba.fastjson.JSON;
import com.ttpark.common.web.ResponseModel;
import com.ttpark.gateway.enums.ErrorEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.util.Set;
@Configuration
public class CorsFilter implements WebFilter {
@Autowired
CorsFilterConfiguration corsFilterConfiguration;
private static final String ALLOWED_EXPOSE = "*";
@Override
public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = exchange.getResponse();
String origin = request.getHeaders().getFirst(HttpHeaders.ORIGIN);
if (!allowOrigin(origin)) {
return response.writeWith(Mono.just(getFailureResponseBody(exchange, 403,
"跨域禁止访问")));
}
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.add("Access-Control-Allow-Headers", corsFilterConfiguration.getAllowHeaders());
httpHeaders.add("Access-Control-Allow-Methods", corsFilterConfiguration.getMethods());
httpHeaders.add("Access-Control-Allow-Origin", origin);
httpHeaders.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
httpHeaders.add("Access-Control-Max-Age", String.valueOf(corsFilterConfiguration.getMaxAge()));
httpHeaders.add("Access-Control-Allow-Credentials", String.valueOf(corsFilterConfiguration.getAllowCredentials()));
if (request.getMethod().name().equals(HttpMethod.OPTIONS.name())) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(exchange);
}
boolean allowOrigin(String host) {
if (StringUtils.isEmpty(host)) {
return false;
}
Set allowOrigins = corsFilterConfiguration.getAllowOrigins();
if (allowOrigins == null || allowOrigins.size() == 0) {
return true;
}
return allowOrigins.contains(host);
}
private DataBuffer getFailureResponseBody(ServerWebExchange exchange, Integer code, String message) {
ServerHttpResponse response = exchange.getResponse();
//设置headers
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
return response.bufferFactory().wrap(JSON.toJSONBytes(new ResponseModel(code, message)));
}
}
@Component
@ConfigurationProperties(prefix = "spring.cloud.gateway.cors-filter")
class CorsFilterConfiguration {
private String allowHeaders = "x-requested-with, Content-Type, Cache-Control,Authorization, credential, X-XSRF-TOKEN, authKey,tenantId,appType,version";
private Set allowOrigins;
private String methods = "*";
private Long maxAge = 18000L;
private Boolean allowCredentials = true;
public String getAllowHeaders() {
return allowHeaders;
}
public void setAllowHeaders(String allowHeaders) {
this.allowHeaders = allowHeaders;
}
public Set getAllowOrigins() {
return allowOrigins;
}
public void setAllowOrigins(Set allowOrigins) {
this.allowOrigins = allowOrigins;
}
public String getMethods() {
return methods;
}
public void setMethods(String methods) {
this.methods = methods;
}
public Long getMaxAge() {
return maxAge;
}
public void setMaxAge(Long maxAge) {
this.maxAge = maxAge;
}
public Boolean getAllowCredentials() {
return allowCredentials;
}
public void setAllowCredentials(Boolean allowCredentials) {
this.allowCredentials = allowCredentials;
}
}
- application.yml 配置
server:
port: 1202
#--------------------------------
spring:
application:
name: gateway-server
cloud:
gateway:
auth-verify-filter:
enableSwagger: true
excludes:
- uri: /onet-oss/auth/passwordLogin #这个头部应该添加 tenantId
generateToken: false #是否生成token
- uri: /onet-oss/onetBusiness/findOnetBusinessSelect
generateToken: false
- uri: /onet-pay/notify/notifyForAliPay
generateToken: false
- uri: /onet-pay/notify/notifyForBestPay
generateToken: false
- uri: /onet-pay/notify/notifyForWxPay
generateToken: false
- uri: /onet-app/appBanner/getStartImage
generateToken: false
- uri: /onet-app/login/passwordLogin
generateToken: false
- uri: /onet-app/login/smsLogin
generateToken: false
- uri: /onet-app/login/getImage
generateToken: false
- uri: /onet-app/login/sendLoginSms
generateToken: false
- uri: /ttpark-data/device/heart
generateToken: false
- uri: /ttpark-data/device/car/in
generateToken: false
- uri: /ttpark-data/device/car/out
generateToken: false
- uri: /ttpark-data/device/car/update
generateToken: false
- uri: /onet-pda/login/passwordLogin
generateToken: false
- uri: /onet-pda/login/authKeyLogin
generateToken: false
discovery:
locator:
enabled: true #是否开启自动发现路由的方式
lower-case-service-id: true
#default-filters: #默认header 添加授权信息
#- AddRequestHeader=authKey, eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJjb21wYW55SWRcIjoyLFwiY29tcGFueU5hbWVcIjpcIuWMl-S6rOeyvuiLsei3r-mAmlwiLFwidGVuYW50SWRcIjoxLFwidXNlck5hbWVcIjpcInBlbmd5b3VcIixcInVzZXJJZFwiOjF9In0.DJbNG5dikpgAZSeunfpRE0tVeWPX4d7OzfsQ9xjlyvqxROGtNYrORM4t1u6lL16YGe8uHZLqvlTD2BOwyNyocA
#-----------------------redis----------------------------
redis:
database: 1
host: 192.168.1.60
port: 6379
#----------------------------------------------------
eureka:
client:
serviceUrl:
defaultZone: http://192.168.1.22:1001/eureka/
instance:
# 是否注册IP到eureka server,如不指定或设为false,那就会注册主机名到eureka server
prefer-ip-address: true
#------------------actuator----------------------
management:
endpoint:
shutdown:
enabled: true
health:
show-details: always
#---------------------------------------------------
logging:
level:
org.springframework.cloud.gateway: debug