提问者:小点点

Spring Cloud Gateway更改表单数据不起作用


我定义了这个网关过滤器:

编辑更多上下文信息:

我想实现的是避免客户端提供其凭据以从授权服务器获取访问令牌。客户端使用用户的凭据(用户名/密码)发送 POST 请求,网关添加所有补充信息,如范围client_idgrant_type等......在将请求转发到授权服务器之前。

@Component
public class OAuth2CredentialsAppenderGatewayFilterFactory extends AbstractGatewayFilterFactory<OAuth2CredentialsAppenderGatewayFilterFactory.Config> {

    public OAuth2CredentialsAppenderGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            
            ServerHttpRequest request = exchange.getRequest();

            ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();

            if ("x-www-form-urlencoded".equals(request.getHeaders().getContentType().getSubtype())) {

               //This code is not executed, the call of formData.put does not do anything, even a breakpoint is not reached!
                if (request.getMethod().equals(HttpMethod.POST)) {
                      exchange.getFormData().map(formData -> { 
                        formData.put("key1", List.of("value1")); 
                        formData.put("key2", List.of("value2"));
                        formData.put("key3", List.of("value3"));
                        return formData;
                    });
                }

            //This part of code works well, the header is added to the forwarded request 
              requestBuilder.header(HttpHeaders.AUTHORIZATION,
                            "Basic " + Base64Utils.encodeToString((this.uiClientId + ":" + this.uiClientSecret).getBytes()));
            }

            return chain.filter(exchange.mutate().request(requestBuilder.build()).build());
        };
    }
}

我像这样使用过滤器:

  - id: keycloak_token_route
    uri: http://localhost:8180
    predicates:
    - Path=/kc/token  
    filters:
    - OAuth2CredentialsAppender
    - SetPath=/auth/realms/main/protocol/openid-connect/token
    - name: RequestRateLimiter 
      args:
        key-resolver: "#{@userIpKeyResolver}"
        redis-rate-limiter.replenishRate: 20 
        redis-rate-limiter.burstCapacity: 30 
        denyEmptyKey: false

过滤器调用良好,但更改传入请求正文不起作用。我是反应式世界的新手,所以我有点困惑,任何帮助都将不胜感激。


共1个答案

匿名用户

对于那些想做同样事情的人来说,这就是我解决问题的方法。同样,我不是反应式编程的专家,我仍在学习,所以这可能是一个更好的答案。

@Component
public class OAuth2CredentialsAppenderGatewayFilterFactory extends AbstractGatewayFilterFactory<OAuth2CredentialsAppenderGatewayFilterFactory.Config> {
    @Value("${uiservice.clientId}")
    private String uiClientId;

    @Value("${uiservice.clientSecret}")
    private String uiClientSecret;

    public OAuth2CredentialsAppenderGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {

        return (ServerWebExchange exchange, GatewayFilterChain chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();

            if (nonNull(request.getHeaders().getContentType()) && request.getHeaders().getContentType().equals(MediaType.APPLICATION_FORM_URLENCODED)) {

                if (requireNonNull(request.getMethod()).equals(HttpMethod.POST)) {
                   //Use this filter to modify the request body
                    ModifyRequestBodyGatewayFilterFactory.Config requestConf = new ModifyRequestBodyGatewayFilterFactory.Config()
                            .setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
                            .setRewriteFunction(String.class, String.class, this.completeRequestBody());

                    requestBuilder.header(HttpHeaders.AUTHORIZATION, base64Encoding(this.uiClientId, this.uiClientSecret));

                    return new ModifyRequestBodyGatewayFilterFactory().apply(requestConf).filter(exchange.mutate().request(requestBuilder.build()).build(), chain);
                }
            }

            return chain.filter(exchange.mutate().request(requestBuilder.build()).build());
        };
    }


    /** Add some config params if needed */
    public static class Config {
    }

    /** Complete request by adding required information to get the access token. Here we can get 2 type of token: client_credentials or password. If the param client_only=true we should get a client_credentials token */
    private RewriteFunction<String, String> completeRequestBody() {

        return (ServerWebExchange ex, String requestBody) -> {
            requireNonNull(requestBody, "Body is required");
            //if body contains only this, we should get a client_credentials token
            var idForClientCredentialsOnly = "client=ui&client_only=true";

            String finalRequestBody;
            var joiner = new StringJoiner("");

            if (idForClientCredentialsOnly.equalsIgnoreCase(requestBody)) {
                joiner.add("grant_type=").add("client_credentials");
            }
            else {
                joiner.add(requestBody);
                if (!containsIgnoreCase(requestBody, "grant_type")) {
                    joiner.add("&grant_type=").add("password");
                }
            }
            if (!containsIgnoreCase(requestBody, "scope")) {
                joiner.add("&scope=").add("uiclient");//I use Keycloak so I specify the scope to get some extra information
            }
            finalRequestBody = joiner.toString();

            return Mono.just(isBlank(finalRequestBody) ? requestBody : finalRequestBody);
        };
    }
}