6

I have followed this blog and have created few microservices: Eureka-server,Auth-service,Zuul-service,Gallery-service,Image-service. From the gallery service I wanted to invoke auth-service API using Feign-Client The url doesn't require authentication but the client throws FeignException$Unauthorized I'm using JWT tokens for authentication.

//AuthServerProxy.java

@FeignClient(name = "auth-service")
@RibbonClient(name = "auth-service")
public interface AuthServiceProxy {

    @PostMapping("/auth/authenticate")
    public ResponseEntity<?> authenticate(@RequestBody UserEntity userEntity);

    @GetMapping("/auth/register")
    public String test();
}

Controller - Gallery Service

@Autowired
    AuthServiceProxy authServiceProxy;
    @GetMapping("/test")
    public String test(){
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername("admin");
        userEntity.setPassword("admin");
        ResponseEntity<?> responseEntity = authServiceProxy.authenticate(userEntity);
        System.out.println(responseEntity.getStatusCode());
        return responseEntity.toString();

    }

    @GetMapping("/test/str")
    public String testStr(){
        return authServiceProxy.test();
    }

Security Config - ZuulServer, Auth-Service

.antMatchers(HttpMethod.POST, "/auth/authenticate").permitAll()

This is the error log

ERROR 1123 --- [nio-8100-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$Unauthorized: status 401 reading AuthServiceProxy#authenticate(UserEntity)] with root cause

feign.FeignException$Unauthorized: status 401 reading AuthServiceProxy#authenticate(UserEntity)
at feign.FeignException.errorStatus(FeignException.java:94) ~[feign-core-10.2.3.jar:na]
    at feign.FeignException.errorStatus(FeignException.java:86) ~[feign-core-10.2.3.jar:na]
    at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:93) ~[feign-core-10.2.3.jar:na]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:149) ~[feign-core-10.2.3.jar:na]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78) ~[feign-core-10.2.3.jar:na]
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-10.2.3.jar:na]
    at com.sun.proxy.$Proxy101.authenticate(Unknown Source) ~[na:na]
    at com.test.gallery.Controller.test(Controller.java:47) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
...

Any help much appreciated. TIA

Harsha Sridhar
  • 99
  • 1
  • 2
  • 9
  • Are you sure Feign calls correct URL? Can you turn on logs and check that? Can you access to this URL via web browser or Postman client? – Piszu Oct 29 '19 at 13:39
  • Yeah I can access the url via Advanced Rest Client, but it fails when accessed via the Feign Client – Harsha Sridhar Oct 30 '19 at 07:46

3 Answers3

4

Feign is not aware of the Authorization that should be passed to the target service .Unfortunately, you need to handle this yourself.Below is a java class that can help

@Component
public class FeignClientInterceptor implements RequestInterceptor {

     private static final String AUTHORIZATION_HEADER = "Authorization";
       private static final String BEARER_TOKEN_TYPE = "Bearer";
    
    
       @Override
        public void apply(RequestTemplate template) {
            SecurityContext securityContext = SecurityContextHolder.getContext();
            Authentication authentication = securityContext.getAuthentication();

            if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
                OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
                template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));
            }
        }
ayman.mostafa
  • 451
  • 6
  • 5
2

Looks like the Authentication header is not passing with FeignClient

try to add this config:

@Bean
public RequestInterceptor requestInterceptor() {

    return requestTemplate -> {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
            OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
            requestTemplate.header(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", details.getTokenValue()));
        }
    };
}
Jay Ehsaniara
  • 1,421
  • 17
  • 24
1

It sounds like the problem could be that you don't have the @EnableResourceServer attached to your Auth-Service.

Without that annotation any endpoint that isn't apart of the spring security package (eg. /oauth/token, /oauth/check_token) will automatically require Authorization.

Furthermore you may need to add in a ResourceServerConfigurerAdapter similar to this to make sure that the resource endpoints are configured to permit all like so:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private final TokenStore tokenStore;

    public ResourceServerConfig(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers(HttpMethod.POST).permitAll()
                .and()
                .logout().disable()
                .csrf().disable();
    }
}

*******EDIT*********

If you’re able to get an ok response from a request in the browser but not feign then your problem most likely is that your Feign client isn’t pointing to the correct endpoint. Normally you would expect a 404 error but since the API is secured you get a 401 because it doesn’t even allow you to know what’s a valid endpoint unless you’re authenticated or it’s an unsecured endpoint

If you have your AuthServiceProxy feign client use your zuul-server instead of the auth-service, you can then add logging to your zuul filter to see what both successful and unsuccessful requests looks like. From there make the necessary changes to have your proxy request match the request you made from the browser and you should be good to go

  • I don't have a resource server, and I guess some dependencies need to be added for it. This is the link to the GitHub repo: https://github.com/harshasridhar/microservice-example Please do suggest me the changes that need to be made I've made another Url GET /auth/register in auth-service, when accessed from browser without token it works, but from Feign Client, it still throws Unauthorised Exception – Harsha Sridhar Oct 30 '19 at 07:51
  • I changed the proxy feign client to use the zuul-server and the requests flow is as shown in the image ![Image](https://drive.google.com/file/d/1DEpvx5AZRbsVPMp9Cvk65O5LDLmL0rYW/view?usp=sharing) No idea why the zuul-server redirects to /error of auth-service – Harsha Sridhar Nov 04 '19 at 10:28
  • Even when the proxy uses the auth-service, the call is made to /error – Harsha Sridhar Nov 04 '19 at 10:39
  • Do you have a eureka server running? I ask because there is no naming server in your git repo and your gallery service application.properties expects there to be one at http://localhost:8761 – magicjedi90 Nov 05 '19 at 13:54
  • Yeah i have the eureka server running. I didn't add the server code to the repo as i was reusing the code from another project, just for the eureka server – Harsha Sridhar Nov 05 '19 at 19:13
  • After some debugging, I found out that the request /auth/register did reach the auth-service and in between the exception "java.io.IOException: Broken pipe" is thrown which then redirects the request to /error. No clue why this is happening. – Harsha Sridhar Nov 06 '19 at 08:24
  • So I’m not sure what’s going wrong but try using this tutorial instead: https://itnext.io/microservices-with-spring-boot-and-spring-cloud-16d2c056ba12 It uses a token store instead of jwt but it should help you get your microservices communicating at the very least – magicjedi90 Nov 07 '19 at 01:49