jhipsterjhipster-registry

JHipster: Rest service calls return "401 Unauthorized" when routing through the gateway


I am new to micro services and JHipster, so please be patient and help me along where necessary.

I have what I believe to be a configuration issue, but I cannot seem to find it. Here are some details:

We are running a JHipster gateway with Keycloak. Environment is Docker compose and as far as I can tell we have done the requisite work as stipulated in the JHipster documentation for docker.

We are using oauth2 as authentication type.

Deployed is a Rest only resource. Simplest case with two calls: /api/hello -> should return "I say Hello" /free/hello -> should return "I am free!"

The /api/hello call should be secured and the /free/hello call obviously not.

When I hit the /free/hello service via the gateway i.e. http://gatewayip:port/helloapp/free/hello, I get the expected response "I am free!"

So I expect the gateway is up and running and routing traffic.

For the secured service I use postman to first get a JWT token.
When I hit the service directly i.e. serverip:appPort/api/hello I get the response I expect

This to me indicates that the service is running and spring security can use my JWT token.

Now the trouble starts when I try and route to the secured service via the gateway. I use the same token via postman. http://gatewayip:port/helloapp/api/hello

This now gives me the response: type "https://www.jhipster.tech/problem/problem-with-message" title "Unauthorized" status 401 detail "Full authentication is required to access this resource" path "/api/hello" message "error.http.401"

Is there an FAQ or a checklist somewhere I can follow to try and troubleshoot?

Please let me know what information I can add to help.

Edit:

SecurityConfiguration:

@EnableWebSecurity
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Value("${spring.security.oauth2.client.provider.oidc.issuer-uri}")
private String issuerUri;

private final JHipsterProperties jHipsterProperties;
private final JwtAuthorityExtractor jwtAuthorityExtractor;
private final SecurityProblemSupport problemSupport;

public SecurityConfiguration(JwtAuthorityExtractor jwtAuthorityExtractor, JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport) {
    this.problemSupport = problemSupport;
    this.jwtAuthorityExtractor = jwtAuthorityExtractor;
    this.jHipsterProperties = jHipsterProperties;
}

@Override
public void configure(HttpSecurity http) throws Exception {
    // @formatter:off
    http
        .csrf()
        .disable()
        .exceptionHandling()
            .authenticationEntryPoint(problemSupport)
            .accessDeniedHandler(problemSupport)
    .and()
        .headers()
        .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:")
    .and()
        .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
    .and()
        .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'")
    .and()
        .frameOptions()
        .deny()
    .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
        .authorizeRequests()
        .antMatchers("/api/auth-info").permitAll()
        .antMatchers("/api/**").authenticated()//hasAuthority(AuthoritiesConstants.USER)//permitAll()
        .antMatchers("/management/health").permitAll()
        .antMatchers("/management/info").permitAll()
        .antMatchers("/management/prometheus").permitAll()
        .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
    .and()
        .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter(jwtAuthorityExtractor)
            .and()
        .and()
            .oauth2Client();
    // @formatter:on
}

Edit 2: Application.yml

security:
oauth2:
    client:
        access-token-uri: http://xxx.xxx.xxx.xxx:30080/auth/realms/test/protocol/openid-connect/token
        user-authorization-uri: http://xxx.xxx.xxx.xxx:30080/auth/realms/test/protocol/openid-connect/auth
        client-id: web_app
        client-secret: web_app
        scope: openid profile email

    resource:
        user-info-uri: http://xxx.xxx.xxx.xxx:30080/auth/realms/test/protocol/openid-connect/userinfo         

server:
 port: 40404

Edit 3: This question is similar to mine:

JHipster - How to add route to external microservices in application.yml

There were no answers given and the solution the poster got to was to just permitAll() on the /api/** path. This is not a great option as it leaves the end point unsecured.

Another similar question is here:

Jhipster OAuth 2.0 / OIDC Authentication Authorization header with bearer token

This received some answers to use the @EnableResourceServer. This is an older post and I am under the impression that the newer version of the Jhipster app I am running caters for this scenario just fine - am I wrong in stating this?


Solution

  • After switching on logging in SpringSecurity by adding

    @Override
        public void configure(WebSecurity web){
            web.debug(true);
        }
    

    In the SecurityConfiguration.java class we could see that the security headers were not forwarded by the gateway.

    This led us to this page: https://github.com/spring-cloud/spring-cloud-netflix/issues/3126

    That explains why.

    When one changes the zuul parameters in the configuration (gateway.yml/jhipster-registry.yml):

        zuul: # those values must be configured depending on the application specific needs
        .
        .
            ignore-security-headers: false
            ignored-headers: cookie,set-cookie
            sensitiveHeaders: Cookie,Set-Cookie
        .
    

    Be warned that the default config excludes forwarding of sensitive headers for security reasons so make sure that you understand what you are doing if you change this.