You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swagger生成Feign客户端OAuth流程报错:URL不能为绝对路径

问题:Swagger生成Feign OAuth客户端抛出"url values must be not be absolute"错误

我通过Swagger 2结合Feign库生成Java客户端代码调用REST API,生成的OAuth RequestInterceptor在OAuth认证时抛出错误:url values must be not be absolute。

报错信息

feign.RetryableException: url values must be not be absolute.
    at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:95)
    at com.sam.feign.auth.OAuth.apply(OAuth.java:83)
    at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:161)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:110)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
    at com.sun.proxy.$Proxy9.getUser(Unknown Source)
    at com.sam.feign.clients.UserApiTest.getUserTest(UserApiTest.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.IllegalArgumentException: url values must be not be absolute.
    at feign.RequestTemplate.uri(RequestTemplate.java:434)
    at feign.RequestTemplate.uri(RequestTemplate.java:421)
    at feign.RequestTemplate.append(RequestTemplate.java:388)
    at com.sam.feign.auth.OAuth$OAuthFeignClient.execute(OAuth.java:163)
    at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:65)
    at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:55)
    at org.apache.oltu.oauth2.client.OAuthClient.accessToken(OAuthClient.java:71)
    at com.sam.feign.auth.OAuth.updateAccessToken(OAuth.java:93)
    ... 34 more

Swagger生成的OAuth支持文件

public class OAuth implements RequestInterceptor {

    static final int MILLIS_PER_SECOND = 1000;

    public interface AccessTokenListener {
        void notify(BasicOAuthToken token);
    }

    private volatile String accessToken;
    private Long expirationTimeMillis;
    private OAuthClient oauthClient;
    private TokenRequestBuilder tokenRequestBuilder;
    private AuthenticationRequestBuilder authenticationRequestBuilder;
    private AccessTokenListener accessTokenListener;

    public OAuth(Client client, TokenRequestBuilder requestBuilder) {
        this.oauthClient = new OAuthClient(new OAuthFeignClient(client));
        this.tokenRequestBuilder = requestBuilder;
    }

    public OAuth(Client client, OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
        this(client, OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes));

        switch(flow) {
        case accessCode:
        case implicit:
            tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
            break;
        case password:
            tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
            break;
        case application:
            tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
            break;
        default:
            break;
        }
        authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl);
    }

    public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
        this(new Client.Default(null, null), flow, authorizationUrl, tokenUrl, scopes);
    }

    @Override
    public void apply(RequestTemplate template) {
        // If the request already have an authorization (eg. Basic auth), do nothing
        if (template.headers().containsKey("Authorization")) {
            return;
        }
        // If first time, get the token
        if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) {
            updateAccessToken(template);
        }
        if (getAccessToken() != null) {
            template.header("Authorization", "Bearer " + getAccessToken());
        }
    }

    public synchronized void updateAccessToken(RequestTemplate template) {
        OAuthJSONAccessTokenResponse accessTokenResponse;
        try {
            accessTokenResponse = oauthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
        } catch (Exception e) {
            throw new RetryableException(400, e.getMessage(), template.request().httpMethod(), e, null, template.request());
        }
        if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) {
            setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn());
            if (accessTokenListener != null) {
                accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken());
            }
        }
    }

    public synchronized void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
        this.accessTokenListener = accessTokenListener;
    }

    public synchronized String getAccessToken() {
        return accessToken;
    }

    public synchronized void setAccessToken(String accessToken, Long expiresIn) {
        this.accessToken = accessToken;
        this.expirationTimeMillis = System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND;
    }

    public TokenRequestBuilder getTokenRequestBuilder() {
        return tokenRequestBuilder;
    }

    public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
        this.tokenRequestBuilder = tokenRequestBuilder;
    }

    public AuthenticationRequestBuilder getAuthenticationRequestBuilder() {
        return authenticationRequestBuilder;
    }

    public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) {
        this.authenticationRequestBuilder = authenticationRequestBuilder;
    }

    public OAuthClient getOauthClient() {
        return oauthClient;
    }

    public void setOauthClient(OAuthClient oauthClient) {
        this.oauthClient = oauthClient;
    }

    public void setOauthClient(Client client) {
        this.oauthClient = new OAuthClient( new OAuthFeignClient(client));
    }

    public static class OAuthFeignClient implements HttpClient {

        private Client client;

        public OAuthFeignClient() {
            this.client = new Client.Default(null, null);
        }

        public OAuthFeignClient(Client client) {
            this.client = client;
        }

        public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
                String requestMethod, Class<T> responseClass)
                        throws OAuthSystemException, OAuthProblemException {

            RequestTemplate req = new RequestTemplate()
                    .append(request.getLocationUri())
                    .method(requestMethod)
                    .body(request.getBody());

            for (Entry<String, String> entry : headers.entrySet()) {
                req.header(entry.getKey(), entry.getValue());
            }
            Response feignResponse;
            String body = "";
            try {
                feignResponse = client.execute(req.request(), new Options());
                body = Util.toString(feignResponse.body().asReader());
            } catch (IOException e) {
                throw new OAuthSystemException(e);
            }

            String contentType = null;
            Collection<String> contentTypeHeader =  feignResponse.headers().get("Content-Type");
            if(contentTypeHeader != null) {
                contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
            }

            return OAuthClientResponseFactory.createCustomResponse(
                    body,
                    contentType,
                    feignResponse.status(),
                    responseClass
            );
        }

        public void shutdown() {
            // Nothing to do here
        }
    }
}

ApiClient.java中的绝对URL配置(来自Swagger spec)

public ApiClient() {
    objectMapper = createObjectMapper();
    apiAuthorizations = new LinkedHashMap<String, RequestInterceptor>();
    feignBuilder = Feign.builder()
                .encoder(new FormEncoder(new JacksonEncoder(objectMapper)))
                .decoder(new JacksonDecoder(objectMapper))
                .logger(new Slf4jLogger());
  }

  public ApiClient(String[] authNames) {
    this();
    for(String authName : authNames) {
      RequestInterceptor auth = null;
      if ("client-credentils-oauth2".equals(authName)) {
        auth = new OAuth(OAuthFlow.application, "", "http://localhost:8080/app/oauth/token", "user.create");
      } else if ("password-oauth2".equals(authName)) {
        auth = new OAuth(OAuthFlow.password, "", "http://localhost:8080/app/oauth/token", "openid");
      } else {
        throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names");
      }
      addAuthorization(authName, auth);
    }
  }

使用的依赖

swagger-codegen-maven-plugin v2.4.28
feign-version 11.6
feign-form-version 3.8.0
oltu-version 1.0.1
Java 8

客户端调用代码

UserApi api = new ApiClient("client-credentils-oauth2","admin", "admin", null, null).buildClient(UserApi.class);
api.getUser(login, tenant)

手动修复方案

我修改了OAuth.java中OAuthFeignClient的execute方法后,代码可以正常运行,但希望生成的客户端代码无需手动修改,寻求解决方案。

修改后的execute方法:

public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
                String requestMethod, Class<T> responseClass)
                        throws OAuthSystemException, OAuthProblemException {
            // Added the below 3 lines
            URI targetUri = URI.create(request.getLocationUri());
            String target = targetUri.getScheme() + "://" + targetUri.getAuthority() ;
            String path  = targetUri.getPath(); 
            
            RequestTemplate req = new RequestTemplate()
                    .uri(path) 
                    .method(requestMethod)
                    .body(request.getBody())
                    .target(target);  // Added this line

            for (Entry<String, String> entry : headers.entrySet()) {
                req.header(entry.getKey(), entry.getValue());
            }
            req = req.resolve(new HashMap<String, Object>()); // Added this line
            Response feignResponse;
            String body = "";
            try {
                feignResponse = client.execute(req.request(), new Options());
                body = Util.toString(feignResponse.body().asReader());
            } catch (IOException e) {
                throw new OAuthSystemException(e);
            }

            String contentType = null;
            Collection<String> contentTypeHeader =  feignResponse.headers().get("Content-Type");
            if(contentTypeHeader != null) {
                contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";");
            }

            return OAuthClientResponseFactory.createCustomResponse(
                    body,
                    contentType,
                    feignResponse.status(),
                    responseClass
            );
        }

内容的提问来源于stack exchange,提问作者Sam Development

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 02:30:39