Spring Cloud Gateway 修改responseBody

2 篇文章 0 订阅

概述

各种业务场景,我们可能需要在网关中修改请求body或响应body(修改请求body请看SpringCloud gateway request的body验证或修改),下文参考spring提供的ModifyResponseBodyGatewayFilterFactory,实现自己的拦截器。

实现

直接贴代码


import com.alibaba.fastjson.JSONObject;
import com.tuzhanai.gateway.jwt.JwtHelper;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

/**
 * 参考 {@link org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory}
 * @author chenws
 * @date 2019/12/10 10:42:29
 */
@Slf4j
@Component
	public class CModifyResponseGatewayFilterFactory extends AbstractGatewayFilterFactory {

    @Override
    public GatewayFilter apply(Object config) {
        return new JwtResponseGatewayFilter();
    }

    public class JwtResponseGatewayFilter implements GatewayFilter,Ordered{
		//注意此处一定要比-1小
        @Override
        public int getOrder() {
            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return chain.filter(exchange.mutate().response(decorate(exchange)).build());
        }

        /**
         * 解决netty buffer默认长度1024导致的接受body不全问题
         * @param exchange
         * @return
         */
        @SuppressWarnings("unchecked")
        private ServerHttpResponse decorate(ServerWebExchange exchange) {
            return new ServerHttpResponseDecorator(exchange.getResponse()) {

                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                    String originalResponseContentType = exchange
                            .getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.add(HttpHeaders.CONTENT_TYPE,
                            originalResponseContentType);

                    ClientResponse clientResponse = ClientResponse
                            .create(exchange.getResponse().getStatusCode())
                            .headers(headers -> headers.putAll(httpHeaders))
                            .body(Flux.from(body)).build();

                    //修改body
                    Mono<String> modifiedBody = clientResponse.bodyToMono(String.class)
                            .flatMap(originalBody -> modifyBody()
                                    .apply(exchange,Mono.just(originalBody)));

                    BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
                            String.class);
                    CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
                            exchange, exchange.getResponse().getHeaders());
                    return bodyInserter.insert(outputMessage, new BodyInserterContext())
                            .then(Mono.defer(() -> {
                                Flux<DataBuffer> messageBody = outputMessage.getBody();
                                HttpHeaders headers = getDelegate().getHeaders();
                                if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                                    messageBody = messageBody.doOnNext(data -> headers
                                            .setContentLength(data.readableByteCount()));
                                }
                                return getDelegate().writeWith(messageBody);
                            }));
                }

                /**
                 * 修改body
                 * @return apply 返回Mono<String>,数据是修改后的body
                 */
                private BiFunction<ServerWebExchange,Mono<String>,Mono<String>> modifyBody(){
                    return (exchange,json)-> {
                        AtomicReference<JSONObject> jsonObject = new AtomicReference<>();
                        json.subscribe(
                                value -> {
                                   //value即为body
                                },
                                Throwable::printStackTrace
                        );
                        return Mono.just(jsonObject.get().toJSONString());
                    };
                }


                @Override
                public Mono<Void> writeAndFlushWith(
                        Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body).flatMapSequential(p -> p));
                }
            };
        }
    }
}

对比

spring提供的ModifyResponseBodyGatewayFilterFactory是带配置的,需要指定function,inClass,outClass。我们一般业务开发返回的一般都是JSON,因此我上面代码默认都为String类型。
可参考github上的网关Demo:SpringCloudGateway-Nacos-Demo

Spring Cloud Gateway是一个基于Spring Boot 2.x的API网关,可以作为微服务架构中的统一入口,提供路由、转发、负载均衡、限流、降级、统一认证和鉴权等功能。在实现统一认证和鉴权时,可以结合Spring Security和JWT来实现。 具体实现步骤如下: 1. 引入Spring Security和JWT的依赖 在Spring Cloud Gateway的pom.xml文件中,引入Spring Security和JWT的依赖: ``` <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring-security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency> ``` 2. 配置Spring Security 在Spring Cloud Gateway的配置类中,配置Spring Security: ``` @Configuration @EnableWebFluxSecurity public class SecurityConfig { @Autowired private JwtAuthenticationManager jwtAuthenticationManager; @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http.csrf().disable() .authorizeExchange() .pathMatchers("/login").permitAll() .anyExchange().authenticated() .and() .addFilterAt(new JwtAuthenticationFilter(jwtAuthenticationManager), SecurityWebFiltersOrder.AUTHENTICATION) .build(); } } ``` 在上面的配置中,首先禁用了CSRF防护,然后配置了登录接口不需要认证,其它接口都需要认证。最后添加了一个JWT认证过滤器。 3. 配置JWT 在Spring Cloud Gateway的配置类中,配置JWT: ``` @Configuration public class JwtConfig { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; @Bean public JwtAuthenticationManager jwtAuthenticationManager() { return new JwtAuthenticationManager(secret); } @Bean public JwtTokenGenerator jwtTokenGenerator() { return new JwtTokenGenerator(secret, expiration); } } ``` 在上面的配置中,配置了JWT的密钥和过期时间,并创建了JWT的管理器和生成器。 4. 实现登录接口 实现登录接口,生成JWT并返回给客户端: ``` @RestController public class LoginController { @Autowired private JwtTokenGenerator jwtTokenGenerator; @PostMapping("/login") public Mono<ResponseEntity<Map<String, String>>> login(@RequestBody LoginRequest loginRequest) { // 验证用户名和密码 if (validateUsernameAndPassword(loginRequest)) { // 生成JWT String token = jwtTokenGenerator.generateToken(loginRequest.getUsername()); // 返回JWT Map<String, String> responseBody = new HashMap<>(); responseBody.put("token", token); return Mono.just(ResponseEntity.ok(responseBody)); } else { return Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()); } } private boolean validateUsernameAndPassword(LoginRequest loginRequest) { // 验证用户名和密码逻辑 } } ``` 在上面的代码中,先验证用户名和密码是否正确,如果正确则生成JWT并返回给客户端,否则返回401未授权状态码。 5. 实现JWT认证过滤器 实现JWT认证过滤器,从请求头中获取JWT并验证: ``` public class JwtAuthenticationFilter extends AuthenticationWebFilter { public JwtAuthenticationFilter(JwtAuthenticationManager jwtAuthenticationManager) { super(jwtAuthenticationManager); } @Override protected Mono<Void> onAuthSuccess(Authentication authentication, ServerWebExchange exchange) { return super.onAuthSuccess(authentication, exchange); } @Override protected Mono<Void> onAuthFailure(AuthenticationException e, ServerWebExchange exchange) { return super.onAuthFailure(e, exchange); } @Override public Mono<Void> filter(ServerWebExchange exchange, AuthenticationFilterChain chain) { String token = extractToken(exchange.getRequest().getHeaders().getFirst("Authorization")); if (StringUtils.isEmpty(token)) { return chain.filter(exchange); } else { JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(token); return super.filter(exchange, chain) .subscriberContext(ReactiveSecurityContextHolder.withAuthentication(jwtAuthenticationToken)); } } private String extractToken(String header) { // 从Authorization头中提取JWT } } ``` 在上面的代码中,先从请求头中提取JWT,如果JWT为空则直接调用下一个过滤器,否则创建JwtAuthenticationToken并将其设置到SecurityContext中。 6. 实现JWT认证管理器 实现JWT认证管理器,验证JWT是否正确: ``` public class JwtAuthenticationManager implements ReactiveAuthenticationManager { private final String secret; public JwtAuthenticationManager(String secret) { this.secret = secret; } @Override public Mono<Authentication> authenticate(Authentication authentication) { String token = authentication.getCredentials().toString(); try { Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secret).parseClaimsJws(token); String username = claimsJws.getBody().getSubject(); return Mono.just(new JwtAuthenticationToken(username, token)); } catch (JwtException e) { return Mono.error(e); } } } ``` 在上面的代码中,使用JWT解析器解析JWT,并验证签名和过期时间,如果验证通过则创建JwtAuthenticationToken。 7. 实现JWT认证令牌 实现JWT认证令牌: ``` public class JwtAuthenticationToken extends AbstractAuthenticationToken { private final String token; private final String username; public JwtAuthenticationToken(String token) { super(Collections.emptyList()); this.token = token; this.username = null; } public JwtAuthenticationToken(String username, String token) { super(Collections.emptyList()); this.token = token; this.username = username; setAuthenticated(true); } @Override public Object getCredentials() { return token; } @Override public Object getPrincipal() { return username; } } ``` 在上面的代码中,实现了AbstractAuthenticationToken的两个抽象方法,并添加了一个token和username属性。 8. 配置路由规则 最后,配置路由规则,启用Spring Cloud Gateway: ``` @Configuration public class GatewayConfig { @Autowired private JwtTokenGenerator jwtTokenGenerator; @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("login", r -> r.path("/login") .uri("http://localhost:8080/login")) .route("hello", r -> r.path("/hello") .filters(f -> f.requestHeader("Authorization", "Bearer " + jwtTokenGenerator.generateToken("user"))) .uri("http://localhost:8081/hello")) .build(); } } ``` 在上面的配置中,配置了两个路由规则,一个是登录接口,另一个是hello接口,hello接口需要通过JWT认证才能访问。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

碩果

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值