Feign Client Throwing Unauthorized Exception for Url, where authentication is not needed

Viewed 11816

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

3 Answers

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

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

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

Related