为了账号安全,请及时绑定邮箱和手机立即绑定
首页 手记 【九月打卡】第17天 缓存 HTTP 请求 Body...

【九月打卡】第17天 缓存 HTTP 请求 Body 的全局过滤器

2022.09.21 21:02 41浏览

课程名称: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;
    }
}

课程截图:

img

img

点击查看更多内容
0人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
0
获赞与收藏
3

关注TA,一起探索更多经验知识

同主题相似文章浏览排行榜

风间影月说签约讲师

50篇手记,涉及Java、MySQL、Redis、Spring等方向

进入讨论

Tony Bai 说签约讲师

145篇手记,涉及Go、C、Java、Python等方向

进入讨论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消