【九月打卡】第17天 缓存 HTTP 请求 Body 的全局过滤器
课程名称:Spring Cloud / Alibaba 微服务架构实战
课程章节:第7章-缓存 HTTP 请求 Body 的全局过滤器
课程讲师:张勤一
课程内容:
1. 缓存 HTTP 请求 Body 的全局过滤器
package com.imooc.ecommerce.filter;
import com.imooc.ecommerce.constant.GatewayConstant;
import lombok.extern.slf4j.Slf4j;
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.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 缓存请求体的全局过滤器
*/
@Slf4j
@Component
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);
}
//缓存
//获取请求体的数据
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
//确保数据缓存区不被释放,必须设置。
DataBufferUtils.retain(dataBuffer);
//defer,just都是去获取数据源,创建数据副本
Flux<DataBuffer> cacheFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
//重新包装 ServerHttpRequest, 重写getBody方法
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cacheFlux;
}
};
//将包装过后的请求体继续先向下传递
return chain.filter(exchange.mutate().request(requestDecorator).build());
});
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 1;
}
}
package com.imooc.ecommerce.constant;
/**
* 网关常量定义
*/
public class GatewayConstant {
/** 登录 uri */
public static final String LOGIN_URI = "/e-commerce/login";
/** 注册 uri */
public static final String REGISTER_URI = "/e-commerce/register";
/** 登录 uri */
public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT = "http://%s:%s/ecommerce-authority-center/authority/token";
/** 登录 uri */
public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT = "http://%s:%s/ecommerce-authority-center/authority/register";
}
2. 登录、注册、鉴权全局过滤器
package com.imooc.ecommerce.filter;
import com.alibaba.fastjson.JSON;
import com.imooc.ecommerce.constant.CommonConstant;
import com.imooc.ecommerce.constant.GatewayConstant;
import com.imooc.ecommerce.utils.JwtTokenParseUtil;
import com.imooc.ecommerce.vo.JwtToken;
import com.imooc.ecommerce.vo.LoginUserInfo;
import com.imooc.ecommerce.vo.UsernameAndPassword;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.message.TokenParser;
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.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
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;
/**
* 全局登录注册鉴权过滤器
*/
@Slf4j
@Component
public class GlobalLoginOrRegisterFilter implements GlobalFilter, Ordered {
// gateway中存在的bean:注册中心客服端,可以从注册中心获取服务实例信息
private LoadBalancerClient loadBalancerClient;
private RestTemplate restTemplate;
public GlobalLoginOrRegisterFilter(LoadBalancerClient loadBalancerClient, RestTemplate restTemplate) {
this.loadBalancerClient = loadBalancerClient;
this.restTemplate = restTemplate;
}
/**
* 登录,注册,授权 过滤功能
* 1. 如果为登录或注册,则去授权中心拿到 Token 并返回给客服端
* 2. 如果是其他服务,则进行授权,失败返回401
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//登录请求
if(request.getURI().getPath().contains(GatewayConstant.LOGIN_URI)){
//去授权中心获取token
String token = getTokenFromAuthorityCenter(request, GatewayConstant.AUTHORITY_CENTER_TOKEN_URL_FORMAT);
//将token存入响应头中,header中不能设置null
response.getHeaders().add(CommonConstant.JWT_USER_INFO_KEY, null == token ? "null" : token);
response.setStatusCode(HttpStatus.OK);
return response.setComplete();
}
//注册请求
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();
}
//其他服务,鉴权,校验token中解析用户信息
HttpHeaders httpHeaders = request.getHeaders();
String token = httpHeaders.getFirst(CommonConstant.JWT_USER_INFO_KEY);
LoginUserInfo loginUserInfo = null;
try {
//解析token
LoginUserInfo userInfo = JwtTokenParseUtil.parseUserInfoFromToken(token);
} catch (Exception e) {
throw new RuntimeException(e);
}
//获取不到用于信息,则结束返回401
if(null == loginUserInfo) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//解析通过,则放行
return chain.filter(exchange);
}
/**
* GlobalLoginOrRegisterFilter 需要在 GlobalCacheRequestBodyFilter 之后执行
* 因为需要获取 GlobalCacheRequestBodyFilter 缓存的请求体
* @return
*/
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 2;
}
/**
* 从 Post请求中获取请求数据
* @param request
* @return
*/
private String parseBodyFromRequest(ServerHttpRequest request) {
//获取请求体
Flux<DataBuffer> body = request.getBody();
AtomicReference<String> atomicReference = new AtomicReference<>();
//获取缓冲区中请求体的数据
body.subscribe(dataBuffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
//获取数据后,一定要使用 DataBufferUtils.release 释放缓冲区,防止内存泄露
DataBufferUtils.release(dataBuffer);
//向原子项中设置值
atomicReference.set(charBuffer.toString());
});
//获取请求体中的数据
return atomicReference.get();
}
/**
* 从授权中心获取token
* @param request
* @param uriFormat
* @return
*/
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.toJSON(serviceInstance.getMetadata()));
//填充uri为正确的uri
String requestUri = String.format(uriFormat, serviceInstance.getHost(), serviceInstance.getPort());
//反序序列化数据
UsernameAndPassword requestBody = JSON.parseObject(parseBodyFromRequest(request), UsernameAndPassword.class);
log.info("login request url and body: [{}], [{}]", requestUri, JSON.toJSON(requestBody));
//发送请求
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
JwtToken token = restTemplate.postForObject(requestUri, new HttpEntity<>(JSON.toJSONString(requestBody), httpHeaders), JwtToken.class);
if (null != token){
return token.getToken();
}
return null;
}
}
课程截图:
点击查看更多内容
为 TA 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦