Background introduction
The technology stack used in the project is spring cloud. There are two functional requirements:
In terms of business, the service of spring cloud gateway module has been able to obtain the token, and the identity information has been obtained from the token after authentication;
Now I want to fill the identity information into the request parameter (here, multiple data are encapsulated into a basedto object for extension).
The microservice module for subsequent processing of specific business passes parameters in the method of the controller layer. As long as it inherits the basedto object, it can directly obtain the identity information for business logic processing.
Problem description
Simply put, the problem is how spring cloud gateway dynamically adds request parameters.
Spring Cloud Gateway Add Request Parameter
- Check the official documents and provide the following examples:
docs.spring.io/spring-clou…

However, it is stated in the configuration file. It seems that it can only be a fixed value.
- I saw someone ask similar questions on GitHub,
github.com/spring-clou…

But the effect is similar to that of the configuration file.
- Similar answers were found on stackoverflow:
stackoverflow.com/questions/6…

Maybe the idea has a direction.
Solution
These two classes addrequestparametergatewayfilterfactory and modifyrequestbodygatewayfilterfactory are found on the spring cloud gateway source code

The code is as follows:
AddRequestParameterGatewayFilterFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.filter.factory;
import java.net.URI;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
public class AddRequestParameterGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public AddRequestParameterGatewayFilterFactory() {
}
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
URI uri = exchange.getRequest().getURI();
StringBuilder query = new StringBuilder();
String originalQuery = uri.getRawQuery();
if (StringUtils.hasText(originalQuery)) {
query.append(originalQuery);
if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
query.append('&');
}
}
query.append(config.getName());
query.append('=');
query.append(config.getValue());
try {
URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri();
ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
return chain.filter(exchange.mutate().request(request).build());
} catch (RuntimeException var8) {
throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");
}
};
}
}
Copy code
ModifyRequestBodyGatewayFilterFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.filter.factory.rewrite;
import java.util.Map;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class ModifyRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<ModifyRequestBodyGatewayFilterFactory.Config> {
public ModifyRequestBodyGatewayFilterFactory() {
super(ModifyRequestBodyGatewayFilterFactory.Config.class);
}
/** @deprecated */
@Deprecated
public ModifyRequestBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
this();
}
public GatewayFilter apply(ModifyRequestBodyGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
Class inClass = config.getInClass();
ServerRequest serverRequest = new DefaultServerRequest(exchange);
Mono<?> modifiedBody = serverRequest.bodyToMono(inClass).flatMap((o) -> {
return config.rewriteFunction.apply(exchange, o);
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, config.getOutClass());
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getRequest().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
httpHeaders.set("Transfer-Encoding", "chunked");
return httpHeaders;
}
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}));
};
}
public static class Config {
private Class inClass;
private Class outClass;
private Map<String, Object> inHints;
private Map<String, Object> outHints;
private RewriteFunction rewriteFunction;
public Config() {
}
public Class getInClass() {
return this.inClass;
}
public ModifyRequestBodyGatewayFilterFactory.Config setInClass(Class inClass) {
this.inClass = inClass;
return this;
}
public Class getOutClass() {
return this.outClass;
}
public ModifyRequestBodyGatewayFilterFactory.Config setOutClass(Class outClass) {
this.outClass = outClass;
return this;
}
public Map<String, Object> getInHints() {
return this.inHints;
}
public ModifyRequestBodyGatewayFilterFactory.Config setInHints(Map<String, Object> inHints) {
this.inHints = inHints;
return this;
}
public Map<String, Object> getOutHints() {
return this.outHints;
}
public ModifyRequestBodyGatewayFilterFactory.Config setOutHints(Map<String, Object> outHints) {
this.outHints = outHints;
return this;
}
public RewriteFunction getRewriteFunction() {
return this.rewriteFunction;
}
public <T, R> ModifyRequestBodyGatewayFilterFactory.Config setRewriteFunction(Class<T> inClass, Class<R> outClass, RewriteFunction<T, R> rewriteFunction) {
this.setInClass(inClass);
this.setOutClass(outClass);
this.setRewriteFunction(rewriteFunction);
return this;
}
public ModifyRequestBodyGatewayFilterFactory.Config setRewriteFunction(RewriteFunction rewriteFunction) {
this.rewriteFunction = rewriteFunction;
return this;
}
}
}
Copy code
In fact, it can be used as an official reference example.
According to similar contents, we can draw gourds as usual and realize the function of adding parameters on our own gateway filter.
Implementation code
Main processing flow of authentication filter
@Component
public class AuthFilter implements GlobalFilter, Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthFilter.class);
private static AntPathMatcher antPathMatcher;
static {
antPathMatcher = new AntPathMatcher();
}
@Override
public int getOrder() {
return FilterOrderConstant.getOrder(this.getClass().getName());
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
String url = uri.getPath();
String host = uri.getHost();
//Skip paths that do not require validation
Stream<String> skipAuthUrls = UrlConstant.skipAuthUrls.stream();
if(skipAuthUrls.anyMatch(path -> antPathMatcher.match(path, url))){
//Direct return
ServerHttpRequest.Builder builder = request.mutate();
return chain.filter(exchange.mutate().request(builder.build()).build());
}
//Take the token from the request header
String token = request.getHeaders().getFirst("Authorization");
//Take out the identity information contained in the token
//The verification token logic is no longer described
BaseDTO baseDTO = getClaim(token);
if(null == baseDTO){
//Authentication failed and identity information could not be obtained
return illegalResponse(exchange, "{\"code\": \"401\",\"msg\": \"unauthorized.\"}");
}
//Add the current identity information to the current request
ServerHttpRequest.Builder builder = request.mutate();
Stream<String> addRequestParameterUrls = UrlConstant.addRequestParameterUrls.stream();
if (addRequestParameterUrls.anyMatch(path -> antPathMatcher.match(path, url))){
//Request parameters need to be added
if(request.getMethod() == HttpMethod.GET){
//Get request processing parameters
return addParameterForGetMethod(exchange, chain, uri, baseDTO, builder);
}
if(request.getMethod() == HttpMethod.POST){
//Post request processing parameters
MediaType contentType = request.getHeaders().getContentType();
if(MediaType.APPLICATION_JSON.equals(contentType)
|| MediaType.APPLICATION_JSON_UTF8.equals(contentType)){
//The request content is application JSON
return addParameterForPostMethod(exchange, chain, baseDTO);
}
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
//The request content is form data
return addParameterForFormData(exchange, chain, baseDTO, builder);
}
}
if(request.getMethod() == HttpMethod.PUT){
//Put request processing parameters
//Follow the post request process
return addParameterForPostMethod(exchange, chain, baseDTO);
}
if(request.getMethod() == HttpMethod.DELETE){
//Delete request processing parameters
//Go through the get request process
return addParameterForGetMethod(exchange, chain, uri, baseDTO, builder);
}
}
//End of current filter execution
return chain.filter(exchange.mutate().request(builder.build()).build());
}
}
Copy code
Get request to add parameters
/**
*Get request, adding parameters
* @param exchange
* @param chain
* @param uri
* @param baseDTO
* @param builder
* @return
*/
private Mono<Void> addParameterForGetMethod(ServerWebExchange exchange, GatewayFilterChain chain, URI uri, BaseDTO baseDTO, ServerHttpRequest.Builder builder) {
StringBuilder query = new StringBuilder();
String originalQuery = uri.getQuery();
if (StringUtils.hasText(originalQuery)) {
query.append(originalQuery);
if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
query.append('&');
}
}
query.append("userId").append("=").append(baseDTO.getUserId())
.append("&").append("userName").append("=").append(baseDTO.getUserName())
;
try {
URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build().encode().toUri();
ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
return chain.filter(exchange.mutate().request(request).build());
} catch (Exception e) {
LOGGER.error("Invalid URI query: " + query.toString(), e);
//End of current filter execution
return chain.filter(exchange.mutate().request(builder.build()).build());
}
}
Copy code
Post requests adding parameters
The request content is application JSON
/**
*Post request, adding parameters
* @param exchange
* @param chain
* @param baseDTO
* @return
*/
private Mono<Void> addParameterForPostMethod(ServerWebExchange exchange, GatewayFilterChain chain, BaseDTO baseDTO) {
ServerRequest serverRequest = new DefaultServerRequest(exchange);
AtomicBoolean flag = new AtomicBoolean(false);
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap((o) -> {
if(o.startsWith("[")){
//The body content is an array and is returned directly
return Mono.just(o);
}
ObjectMapper objectMapper = new ObjectMapper();
try {
Map map = objectMapper.readValue(o, Map.class);
map.put("userId", baseDTO.getUserId());
map.put("userName", baseDTO.getUserName());
String json = objectMapper.writeValueAsString(map);
LOGGER.info("addParameterForPostMethod -> json = {}", json);
return Mono.just(json);
}catch (Exception e){
e.printStackTrace();
return Mono.just(o);
}
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getRequest().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
httpHeaders.set("Transfer-Encoding", "chunked");
return httpHeaders;
}
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}));
}
Copy code
The request content is form data
/**
*Post request, add parameters to form data
* @param exchange
* @param chain
* @param baseDTO
* @param builder
* @return
*/
private Mono<Void> addParameterForFormData(ServerWebExchange exchange, GatewayFilterChain chain, BaseDTO baseDTO, ServerHttpRequest.Builder builder) {
builder.header("userId", String.valueOf(baseDTO.getUserId()));
try {
builder.header("userName", URLEncoder.encode(String.valueOf(baseDTO.getUserName()), "UTF-8"));
} catch (UnsupportedEncodingException e) {
builder.header("userName", String.valueOf(baseDTO.getUserName()));
}
ServerHttpRequest serverHttpRequest = builder.build();
HttpHeaders headers = serverHttpRequest.getHeaders();
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
}
Copy code
Return data processing
/**
*Return message
* @param exchange
* @param data
* @return
*/
private Mono<Void> illegalResponse(ServerWebExchange exchange, String data) {
ServerHttpResponse originalResponse = exchange.getResponse();
originalResponse.setStatusCode(HttpStatus.OK);
originalResponse.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
byte[] response = data.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
return originalResponse.writeWith(Flux.just(buffer));
}
Copy code
Final effect
As described above, the userid and username attributes have been written into the request parameter.
In the service module of specific business processing, as long as the controller layer inherits the basedto class containing userid and username attributes, this information can be obtained for the actual business process.
If you want to know more about your friends, scan the QR code below to follow the official account "W's programming diary" for more free information
! [wechat picture _20211124131235. JPG]( https://upload-images.jianshu.io/upload_images/27369920-74ced392da13af72.jpg?imageMogr2/auto -orient/strip%7CimageView2/2/w/1240)