【问题标题】:Error 403 when feign client makes POST/PUT request to micro-servicefeign 客户端向微服务发起 POST/PUT 请求时出现错误 403
【发布时间】:2019-06-21 10:37:51
【问题描述】:

我有一个不能使用 Spring Cloud 的 Java 遗留应用程序。它使用 feign 客户端通过网关访问微服务。

网关和服务由 jhipster 5.7.2 生成,带有 OAuth2/OIDC 选项。

在我的客户端中,RequestInterceptor 调用 keycloak 以获取令牌(直接访问授权)并将其注入到标头中。

当我发出 GET 请求时没问题,但我在 POST 或 PUT 请求后收到 403。

在网关上启用了 CORS(但未使用,因为请求不是 cors 请求)。我在开发模式下运行它。 Zuul路线似乎还可以。 我没有在网关和服务上更改配置。

有人有想法吗?

在我的假客户下面:

public interface SmartDocumentClient {

@RequestLine("GET /api/ebox/test")
//@Headers("Content-Type: application/json")
public ResponseEntity<HasEboxResponse> test();

@RequestLine("POST /api/ebox/test")
@Headers("Content-Type: application/json")
public ResponseEntity<HasEboxResponse> testPost(HasEboxRequest request);

@RequestLine("PUT /api/ebox/test")
@Headers("Content-Type: application/json")
public ResponseEntity<HasEboxResponse> testPut(HasEboxRequest request); }

我的客户端配置:

T client = Feign.builder()
            .contract(new feign.Contract.Default()) //annotation openfeign pour éviter bug d'upload avec SpringMvc
            .client(new OkHttpClient())
            .encoder(new FormEncoder(new GsonEncoder())) //pour gérer le formData
            .decoder(new ResponseEntityDecoder(new ResponseEntityDecoder(new CustomFileDecoder(new CustomGsonDecoder()))))
            .requestInterceptor(interceptor)
            .options(new Request.Options(timeout, timeout))
            .target(SmartDocumentClient, url);

拦截器:

public class GedRequestInterceptor implements RequestInterceptor {

public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer";

private String authUrl;
private String user;
private String password;
private String clientId;
private String clientSecret;

private RestTemplate restTemplate;
private CustomOAuth2ClientContext oAuth2ClientContext;

public GedRequestInterceptor(String authUrl, String user, String password, String clientId, String clientSecret) {
    super();
    this.authUrl = authUrl;
    this.user = user;
    this.password = password;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    restTemplate = new RestTemplate();
    //oAuth2ClientContext = new DefaultOAuth2ClientContext();
}

@Override
public void apply(RequestTemplate template) {
    // demander un token à keycloak et le joindre à la request
    Optional<String> token = getToken();
    if (token.isPresent()) {
        template.header(HttpHeaders.ORIGIN, "localhost");
        template.header(AUTHORIZATION, String.format("%s %s", BEARER, token.get()));
    }
}

private Optional<String> getToken() {
    if (oAuth2ClientContext.getAccessToken() == null || oAuth2ClientContext.getAccessToken().isExpired()) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("client_id", this.clientId);
        map.add("client_secret", this.clientSecret);
        map.add("grant_type", "password"); // client_credentials //password
        map.add("username", this.user);
        map.add("password", this.password);
        oAuth2ClientContext.setAccessToken(askToken(map));
    } 
    
    if (oAuth2ClientContext.getAccessToken() != null){
        return Optional.ofNullable(oAuth2ClientContext.getAccessToken().getValue());
    } else {
        return Optional.empty();
    }
}

private CustomOAuth2AccessToken askToken( MultiValueMap<String, String> map) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

        ResponseEntity<CustomOAuth2AccessToken> response = restTemplate.postForEntity(
                this.authUrl, request, CustomOAuth2AccessToken.class);
        
        if (response != null && response.hasBody()) {
            return response.getBody();
        } else {
            return null;
        }
}

}

最后是资源:

    @RestController
@RequestMapping("/api")
public class DocumentResource {

        private static String TMP_FILE_PREFIX = "smartdoc_tmp";

        public DocumentResource() {
        }

        @GetMapping("/ebox/test")
        public ResponseEntity<HasEboxResponse> test() {
                return ResponseEntity.ok(new HasEboxResponse());
        }

        @PostMapping("/ebox/test")
        public ResponseEntity<HasEboxResponse> testPost(@RequestBody HasEboxRequest request) {
                return ResponseEntity.ok(new HasEboxResponse());
        }

        @PutMapping("/ebox/test")
        public ResponseEntity<HasEboxResponse> testPut(@RequestBody HasEboxRequest request) {
                return ResponseEntity.ok(new HasEboxResponse());
        }

}

谢谢!

【问题讨论】:

  • 您似乎正在尝试使用标头中的令牌发出请求。 JHipster 的 OAuth2 实现默认使用 cookie(启用 xsrf 保护)。如果要启用使用授权请求标头,可以添加类似github.com/ruddell/ignite-jhipster/blob/… 的配置
  • 谢谢乔恩,我把我的解决方案放在下面。我不想更改 jhipster 默认值,因为我也在使用网关的 UI 和服务。但我还需要允许来自带有标头中令牌的遗留应用程序的调用。所以我不更改默认值,只是让 zuul 将调用重定向到服务。
  • 您可以在服务之间共享标头:cloud.spring.io/spring-cloud-netflix/single/…
  • @redoff,调用者不是服务,是遗留应用,不能使用spring cloud,注册到eureka等...
  • 调用者必须是 Spring Cloud 服务不是强制性的,您可以尝试类似:LOGIN:PASSWORD@YOURZUULSERVICE.com/YOURENDPOINT

标签: oauth-2.0 jhipster http-status-code-403 netflix-zuul


【解决方案1】:

问题出在 spring 安全配置中。 WebSecurity 不允许在未经身份验证的情况下调用诸如“[SERVICE_NAME]/api”之类的 URL。我添加了一条规则以允许访问某些 url。如果header中有access token,会被zuul转发给服务。

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
        .antMatchers("/ext/*/api/**") // allow calls to services, redirect by zuul
        .antMatchers(HttpMethod.OPTIONS, "/**")
        .antMatchers("/app/**/*.{js,html}")
        .antMatchers("/i18n/**")
        .antMatchers("/content/**")
        .antMatchers("/swagger-ui/index.html")
        .antMatchers("/test/**");
}

为了通过 UI 调用其他服务并让网关注入访问令牌,我在我的 zuul config 中定义了两组路由,

routes:
    myservice: 
        path: /myservice/**
        serviceId: myservice
    myservice_ext: 
        path: /ext/myservice/**
        serviceId: myservice 
  • /ext/myService... : 引用服务,不要被 spring secu 忽略
  • /myService... : 引用服务但由 spring secu 处理

【讨论】:

    猜你喜欢
    • 2019-01-13
    • 1970-01-01
    • 2021-07-15
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 2021-07-21
    • 1970-01-01
    相关资源
    最近更新 更多