Ошибка 403, когда клиент симулирует запрос POST / PUT для микро-сервиса - PullRequest
0 голосов
/ 28 января 2019

У меня есть старое приложение в Java, которое не может использовать Spring Cloud.Он использует симулированный клиент для доступа к микросервису через шлюз.

Шлюз и сервис генерируются jhipster 5.7.2 с опцией OAuth2 / OIDC.

В моем клиенте RequestInterceptor вызывает keycloakчтобы получить токен (разрешение прямого доступа) и внедрить его в заголовок.

Это нормально, когда я делаю запрос GET, но я получаю 403 после запроса POST или PUT.

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); }

Мой клиентconfig:

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") открытый класс 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());
    }

}

Спасибо!

1 Ответ

0 голосов
/ 28 января 2019

Проблема была в конфигурации безопасности весны.WebSecurity не разрешал вызывать URL-адреса типа «[SERVICE_NAME] / api» без аутентификации.Я добавил правило, разрешающее доступ к некоторым URL-адресам.Если токен доступа находится в заголовке, он будет перенаправлен в сервис по 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/**");
}

Чтобы вызывать другие сервисы через пользовательский интерфейс и разрешать вводить токен доступа через шлюз, я определил две группы:маршруты в моем конфиге zuul,

routes:
    myservice: 
        path: /myservice/**
        serviceId: myservice
    myservice_ext: 
        path: /ext/myservice/**
        serviceId: myservice 
  • / ext / myService ...: ссылки на сервисы, а не игнорирование с помощью Spring secu
  • / myService ...: обращениеуслуги, но обрабатываются Spring Secu
...