parent
eed300274c
commit
400a561234
@ -0,0 +1,19 @@
|
|||||||
|
package org.example.common.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>通用模块常量定义</h1>
|
||||||
|
* */
|
||||||
|
public final class CommonConstant {
|
||||||
|
|
||||||
|
/** RSA 公钥 */
|
||||||
|
public static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnV+iGlE1e8Z825G+ChIwRJ2H2jOMCBu" +
|
||||||
|
"HV7BPrUE8dAGjqAlRtCaxMyJw7NV9NIUl/rY7RWBUQwelkGmGuQomnUAFIgN9f8UxSC6G935lo1ZoBVJWYmfs5ToXLz+fQugmqHZvF+Vc5l" +
|
||||||
|
"UEo1YapeiaymkOxDORMGjzQBoxoBt316IAwNEPIvcV+F6T+WNFJX/p5Xj48Z1rtmbOQ8ffF+pEWKZGsYg/9b+pKiqFJtuyHqwj/9oxFBE98" +
|
||||||
|
"MCu5RfK6M7Ff9/1dyNen1HKjI7Awj8ZnSceVUldcXEdnP89YagevbhtSl/+CvCsKwHq5+ZLkcuONSxE4dIFWTjxA92wJjYf9wIDAQAB";
|
||||||
|
|
||||||
|
/** JWT 中存储用户信息的 key */
|
||||||
|
public static final String JWT_USER_INFO_KEY = "e-commerce-user";
|
||||||
|
|
||||||
|
/** 授权中心的 service-id */
|
||||||
|
public static final String AUTHORITY_CENTER_SERVICE_ID = "e-commerce-authority-center";
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
package org.example.common;
|
@ -0,0 +1,63 @@
|
|||||||
|
package org.example.common.util;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jws;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import org.example.common.constant.CommonConstant;
|
||||||
|
import org.example.common.vo.LoginUserInfo;
|
||||||
|
import sun.misc.BASE64Decoder;
|
||||||
|
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>JWT Token 解析工具类</h1>
|
||||||
|
* */
|
||||||
|
public class TokenParseUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>从 JWT Token 中解析 LoginUserInfo 对象</h2>
|
||||||
|
* */
|
||||||
|
public static LoginUserInfo parseUserInfoFromToken(String token) throws Exception {
|
||||||
|
|
||||||
|
if (null == token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Jws<Claims> claimsJws = parseToken(token, getPublicKey());
|
||||||
|
Claims body = claimsJws.getBody();
|
||||||
|
|
||||||
|
// 如果 Token 已经过期了, 返回 null
|
||||||
|
if (body.getExpiration().before(Calendar.getInstance().getTime())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回 Token 中保存的用户信息
|
||||||
|
return JSON.parseObject(
|
||||||
|
body.get(CommonConstant.JWT_USER_INFO_KEY).toString(),
|
||||||
|
LoginUserInfo.class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>通过公钥去解析 JWT Token</h2>
|
||||||
|
* */
|
||||||
|
private static Jws<Claims> parseToken(String token, PublicKey publicKey) {
|
||||||
|
|
||||||
|
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>根据本地存储的公钥获取到 PublicKey 对象</h2>
|
||||||
|
* */
|
||||||
|
private static PublicKey getPublicKey() throws Exception {
|
||||||
|
|
||||||
|
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
|
||||||
|
new BASE64Decoder().decodeBuffer(CommonConstant.PUBLIC_KEY)
|
||||||
|
);
|
||||||
|
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.example.common.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>通用响应对象定义</h1>
|
||||||
|
* {
|
||||||
|
* "code": 0,
|
||||||
|
* "message": "",
|
||||||
|
* "data": {}
|
||||||
|
* }
|
||||||
|
* */
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CommonResponse<T> implements Serializable {
|
||||||
|
|
||||||
|
/** 错误码 */
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
/** 错误消息 */
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 泛型响应数据 */
|
||||||
|
private T Data;
|
||||||
|
|
||||||
|
public CommonResponse(Integer code, String message) {
|
||||||
|
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.example.common.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>授权中心鉴权之后给客户端的 Token</h1>
|
||||||
|
* */
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JwtToken {
|
||||||
|
|
||||||
|
/** JWT */
|
||||||
|
private String token;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.example.common.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>登录用户信息</h1>
|
||||||
|
* */
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LoginUserInfo {
|
||||||
|
|
||||||
|
/** 用户 id */
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 用户名 */
|
||||||
|
private String username;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.example.common.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>用户名和密码</h1>
|
||||||
|
* */
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UsernameAndPassword {
|
||||||
|
|
||||||
|
/** 用户名 */
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/** 密码 */
|
||||||
|
private String password;
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.example.conf;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>网关需要注入到容器中的 Bean</h1>
|
||||||
|
* */
|
||||||
|
@Configuration
|
||||||
|
public class GatewayBeanConf {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.example.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>网关常量定义</h1>
|
||||||
|
* */
|
||||||
|
public class GatewayConstant {
|
||||||
|
|
||||||
|
/** 登录 uri */
|
||||||
|
public static final String LOGIN_URI = "/dev-protocol-springcloud-gateway/login";
|
||||||
|
|
||||||
|
/** 注册 uri */
|
||||||
|
public static final String REGISTER_URI = "/dev-protocol-springcloud-gateway/register";
|
||||||
|
|
||||||
|
/** 去授权中心拿到登录 token 的 uri 格式化接口 */
|
||||||
|
public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT =
|
||||||
|
"http://%s:%s/dev-protocol-authority-center/authority/token";
|
||||||
|
|
||||||
|
/** 去授权中心注册并拿到 token 的 uri 格式化接口 */
|
||||||
|
public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT =
|
||||||
|
"http://%s:%s/dev-protocol-authority-center/authority/register";
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package org.example.filter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.example.constant.GatewayConstant;
|
||||||
|
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.core.io.buffer.DataBufferUtils;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>缓存请求 body 的全局过滤器</h1>
|
||||||
|
* Spring WebFlux
|
||||||
|
* */
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered {
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
|
||||||
|
// 只对 注册和登录的信息进行缓存
|
||||||
|
boolean isloginOrRegister =
|
||||||
|
exchange.getRequest().getURI().getPath().contains(GatewayConstant.LOGIN_URI)
|
||||||
|
|| exchange.getRequest().getURI().getPath().contains(GatewayConstant.REGISTER_URI);
|
||||||
|
|
||||||
|
if (null == exchange.getRequest().getHeaders().getContentType()
|
||||||
|
|| !isloginOrRegister) {
|
||||||
|
return chain.filter(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataBufferUtils.join 拿到请求中的数据 --> DataBuffer
|
||||||
|
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
|
||||||
|
|
||||||
|
// 确保数据缓冲区不被释放, 必须要 DataBufferUtils.retain
|
||||||
|
DataBufferUtils.retain(dataBuffer);
|
||||||
|
// defer、just 都是去创建数据源, 得到当前数据的副本
|
||||||
|
Flux<DataBuffer> cachedFlux = Flux.defer(() ->
|
||||||
|
Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
|
||||||
|
// 重新包装 ServerHttpRequest, 重写 getBody 方法, 能够返回请求数据
|
||||||
|
ServerHttpRequest mutatedRequest =
|
||||||
|
new ServerHttpRequestDecorator(exchange.getRequest()) {
|
||||||
|
@Override
|
||||||
|
public Flux<DataBuffer> getBody() {
|
||||||
|
return cachedFlux;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 将包装之后的 ServerHttpRequest 向下继续传递
|
||||||
|
return chain.filter(exchange.mutate().request(mutatedRequest).build());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return HIGHEST_PRECEDENCE + 1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.example.filter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.time.StopWatch;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>全局接口耗时日志过滤器</h1>
|
||||||
|
* */
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class GlobalElapsedLogFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
|
||||||
|
// 前置逻辑
|
||||||
|
StopWatch sw = StopWatch.createStarted();
|
||||||
|
String uri = exchange.getRequest().getURI().getPath();
|
||||||
|
|
||||||
|
return chain.filter(exchange).then(
|
||||||
|
// 后置逻辑
|
||||||
|
Mono.fromRunnable(() ->
|
||||||
|
log.info("[{}] elapsed: [{}ms]",
|
||||||
|
uri, sw.getTime(TimeUnit.MILLISECONDS)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return HIGHEST_PRECEDENCE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
package org.example.filter;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.example.common.constant.CommonConstant;
|
||||||
|
import org.example.common.util.TokenParseUtil;
|
||||||
|
import org.example.common.vo.JwtToken;
|
||||||
|
import org.example.common.vo.LoginUserInfo;
|
||||||
|
import org.example.common.vo.UsernameAndPassword;
|
||||||
|
import org.example.constant.GatewayConstant;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||||
|
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.DataBufferUtils;
|
||||||
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>全局登录鉴权过滤器</h1>
|
||||||
|
* */
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class GlobalLoginOrRegisterFilter implements GlobalFilter, Ordered {
|
||||||
|
/** 注册中心客户端, 可以从注册中心中获取服务实例信息 */
|
||||||
|
private final LoadBalancerClient loadBalancerClient;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public GlobalLoginOrRegisterFilter(LoadBalancerClient loadBalancerClient,
|
||||||
|
RestTemplate restTemplate) {
|
||||||
|
this.loadBalancerClient = loadBalancerClient;
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>登录、注册、鉴权</h2>
|
||||||
|
* 1. 如果是登录或注册, 则去授权中心拿到 Token 并返回给客户端
|
||||||
|
* 2. 如果是访问其他的服务, 则鉴权, 没有权限返回 401
|
||||||
|
* */
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
|
||||||
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
|
|
||||||
|
// 1. 如果是登录
|
||||||
|
if (request.getURI().getPath().contains(GatewayConstant.LOGIN_URI)) {
|
||||||
|
// 去授权中心拿 token
|
||||||
|
String token = getTokenFromAuthorityCenter(
|
||||||
|
request, GatewayConstant.AUTHORITY_CENTER_TOKEN_URL_FORMAT
|
||||||
|
);
|
||||||
|
// header 中不能设置 null
|
||||||
|
response.getHeaders().add(
|
||||||
|
CommonConstant.JWT_USER_INFO_KEY,
|
||||||
|
null == token ? "null" : token
|
||||||
|
);
|
||||||
|
response.setStatusCode(HttpStatus.OK);
|
||||||
|
return response.setComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果是注册
|
||||||
|
if (request.getURI().getPath().contains(GatewayConstant.REGISTER_URI)) {
|
||||||
|
// 去授权中心拿 token: 先创建用户, 再返回 Token
|
||||||
|
String token = getTokenFromAuthorityCenter(
|
||||||
|
request,
|
||||||
|
GatewayConstant.AUTHORITY_CENTER_REGISTER_URL_FORMAT
|
||||||
|
);
|
||||||
|
response.getHeaders().add(
|
||||||
|
CommonConstant.JWT_USER_INFO_KEY,
|
||||||
|
null == token ? "null" : token
|
||||||
|
);
|
||||||
|
response.setStatusCode(HttpStatus.OK);
|
||||||
|
return response.setComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 访问其他的服务, 则鉴权, 校验是否能够从 Token 中解析出用户信息
|
||||||
|
HttpHeaders headers = request.getHeaders();
|
||||||
|
String token = headers.getFirst(CommonConstant.JWT_USER_INFO_KEY);
|
||||||
|
LoginUserInfo loginUserInfo = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
loginUserInfo = TokenParseUtil.parseUserInfoFromToken(token);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("parse user info from token error: [{}]", ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取不到登录用户信息, 返回 401
|
||||||
|
if (null == loginUserInfo) {
|
||||||
|
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
return response.setComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析通过, 则放行
|
||||||
|
return chain.filter(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return HIGHEST_PRECEDENCE + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>从授权中心获取 Token</h2>
|
||||||
|
* */
|
||||||
|
private String getTokenFromAuthorityCenter(ServerHttpRequest request, String uriFormat) {
|
||||||
|
|
||||||
|
// service id 就是服务名字, 负载均衡
|
||||||
|
ServiceInstance serviceInstance = loadBalancerClient.choose(
|
||||||
|
CommonConstant.AUTHORITY_CENTER_SERVICE_ID
|
||||||
|
);
|
||||||
|
log.info("Nacos Client Info: [{}], [{}], [{}]",
|
||||||
|
serviceInstance.getServiceId(), serviceInstance.getInstanceId(),
|
||||||
|
JSON.toJSONString(serviceInstance.getMetadata()));
|
||||||
|
|
||||||
|
String requestUrl = String.format(
|
||||||
|
uriFormat, serviceInstance.getHost(), serviceInstance.getPort()
|
||||||
|
);
|
||||||
|
UsernameAndPassword requestBody = JSON.parseObject(
|
||||||
|
parseBodyFromRequest(request), UsernameAndPassword.class
|
||||||
|
);
|
||||||
|
log.info("login request url and body: [{}], [{}]", requestUrl, JSON.toJSONString(requestBody));
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
JwtToken token = restTemplate.postForObject(
|
||||||
|
requestUrl,
|
||||||
|
new HttpEntity<>(JSON.toJSONString(requestBody), headers),
|
||||||
|
JwtToken.class
|
||||||
|
);
|
||||||
|
|
||||||
|
if (null != token) {
|
||||||
|
return token.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>从 Post 请求中获取到请求数据</h2>
|
||||||
|
* */
|
||||||
|
private String parseBodyFromRequest(ServerHttpRequest request) {
|
||||||
|
|
||||||
|
// 获取请求体
|
||||||
|
Flux<DataBuffer> body = request.getBody();
|
||||||
|
// 原子引用
|
||||||
|
AtomicReference<String> bodyRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
// 订阅缓冲区去消费请求体中的数据
|
||||||
|
body.subscribe(buffer -> {
|
||||||
|
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
|
||||||
|
// 一定要使用 DataBufferUtils.release 释放掉, 因为前面使用 DataBufferUtils.retain(dataBuffer); 保证不被释放, 这里不手动释放, 会出现内存泄露
|
||||||
|
DataBufferUtils.release(buffer);
|
||||||
|
bodyRef.set(charBuffer.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取 request body
|
||||||
|
return bodyRef.get();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package org.example.filter;
|
||||||
|
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>HTTP 请求头部携带 Token 验证过滤器</h1>
|
||||||
|
* */
|
||||||
|
public class HeaderTokenGatewayFilter implements GatewayFilter, Ordered {
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
|
||||||
|
// 从 HTTP Header 中寻找 key 为 token, value 为 imooc 的键值对
|
||||||
|
String name = exchange.getRequest().getHeaders().getFirst("token");
|
||||||
|
if ("imooc".equals(name)) {
|
||||||
|
return chain.filter(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记此次请求没有权限, 并结束这次请求
|
||||||
|
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
return exchange.getResponse().setComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return HIGHEST_PRECEDENCE + 2;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package org.example.filter.factory;
|
||||||
|
|
||||||
|
import org.example.filter.HeaderTokenGatewayFilter;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||||
|
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
|
||||||
|
public class HeaderTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
|
||||||
|
@Override
|
||||||
|
public GatewayFilter apply(Object config) {
|
||||||
|
return new HeaderTokenGatewayFilter();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue