spring-cloud / spring-cloud-netflix

Integration with Netflix OSS components
http://cloud.spring.io/spring-cloud-netflix/
Apache License 2.0
4.87k stars 2.44k forks source link

Spring Cloud Zuul behind Azure B2C, could not obtain access token. #3309

Closed maxiwu closed 5 years ago

maxiwu commented 5 years ago

I've been working on this for months now. I think I am really close. My Goal: Use a Zuul gateway to serve micro services. Put a OAuth2 filter on the Zuul gateway to protect micro services. The OAuth2 provider is Azure B2C. I think I am using OIDC and JWT. All my Zuul and services are run on the same machine, different ports.
P.S. I am planning to use Spring Session or Redis to share session across all micro services if necessary.

When I send a request to my Zuul, which is localhost:8762/res/images. I got redirected to the B2C login page. After I login with my username password. I am redirected back to localhost:8762/login. with a very long code attribute. I am guessing it is either a auth_code or the JWT token. Also there is an attribute state=IuvHq6. Not sure what that is. The web page gives me status 401, could not obtain access token.

The gateway
Zuul gateway POM gist
spring cloud Edgware.SR4
spring boot starter web 1.5.13
spring cloud starter eureka 1.4.5
spring cloud starter zuul 1.4.5
spring security oauth2 2.0.15
spring security jwt 1.0.9

Zuul gateway Java configuration

@SpringBootApplication
@EnableEurekaClient // It acts as a eureka client
@EnableZuulProxy // Enable Zuul
public class ZuulgatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulgatewayApplication.class, args);
    }

}

Security configuration

@Configuration
@EnableOAuth2Sso
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {// OAuth2SsoConfigurerAdapter
                                                                        // {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().cors().disable().logout().and().antMatcher("/**").authorizeRequests()
                .antMatchers("/index.html", "/", "/login").permitAll().anyRequest().authenticated().and()
                .csrf().disable();
    }
}

application settings gist

Eureka micro service
Eureka service POM gist
spring boot starter data rest 2.0.5
spring cloud starter netflix eureka client 2.0.1
spring boot starter web 2.0.5
spring cloud security 1.2.3
spring security oauth2 autoconfigure 2.1.1

Eureka service configuration

@SpringBootApplication
@EnableEurekaClient
public class ClusterdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ClusterdemoApplication.class, args);
    }
}

Res Server configuration

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Bean("resourceServerRequestMatcher")
    public RequestMatcher resources() {
        return new AntPathRequestMatcher("/images/**");
    }

    @Override
    public void configure(final HttpSecurity http) throws Exception {
        http
            .requestMatcher(resources()).authorizeRequests()
            .anyRequest().authenticated();
    }
}

the RESTful API

@RestController
@RequestMapping("/")
public class HomeController {
    @Autowired
    private Environment env;

    @RequestMapping("/")  //use with context-path
    // @RequestMapping("/images")
    public List<Image> getImages() {
        List<Image> images = Arrays.asList(
            new Image(1, "Treehouse of Horror V", "https://www.imdb.com/title/tt0096697/mediaviewer/rm3842005760"),
            new Image(2, "The Town", "https://www.imdb.com/title/tt0096697/mediaviewer/rm3698134272"),
            new Image(3, "The Last Traction Hero", "https://www.imdb.com/title/tt0096697/mediaviewer/rm1445594112"));
        return images;
    }
}

application configuration gist

Eureka server
Eureka server POM gist
spring boot starter web 2.0.5
spring cloud starter netflix eureka server 2.0.1

Eureka server configuration

@SpringBootApplication
@EnableEurekaServer
public class ClusterdemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(ClusterdemoApplication.class, args);
  }
}

application yml

spring:
  application:
    name: eureka-server
server:
  port: 8761
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

Testing Flow:

  1. send request to Zuul gateway. GET http://localhost:8762/res/images
  2. redirected to B2C login screen
  3. login expected result: display a json of image links test result: redirect to micro service http://localhost:8762/login, with Could not obtain access token error

a full sample of result URL

http://localhost:8762/login?state=3kJ31R&code=eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAifQ..5ofS501Kavlbcrtn.c0Wq9m7ZQVwewQD1a_cmnTz4UhvNA50kqGSsF6UOTYhUvMVBv5qsNmHLkqopxQKbNhKImqUDNLqA7t8qgEYIN59juuFCou0kUkDDFmNspSYCLCebhIEeu9EgkCcUH-cqeVom4LjIJyw9QdeHcz8c-89ryLOz1zD9ISS-xGhHHt1DHd8cwtfy-WiVFOaFsvt2L-kSU8iA_cGY4ikT9rf5NdnZ5DSJjvRfNCZnsuKn6dS21McV-M9W1q3Ndyna2Gzf6xldj1SYACb-TxGG-lcOJWnlyf-U2SzZOK_F-r41ZHwxT1cw2iyFHr8qrCaaNRWGJstQ4c2Bs9Vsx0eIi9t_7JXhr4OqfnwAL7vqxlX8pIpaSgAbpQpEa3O7Y0ScNUKXr9HvNpVx9w--ebhkfdjYdBo0QE3RwB3jbKl0u-QGwRqNNUzMO1-DGd_tLhPeX-yLEFMsRSQhQCaeaNT_ZRdmyf63NsGxaOxSEpf3Z0eKJCY0A5knTHmH2A.7BkuNnBwxVL237E6vqMeAw

output from zuul gateway debug console

2018-12-06 16:14:18.134 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2018-12-06 16:14:18.135 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2018-12-06 16:14:18.138 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2018-12-06 16:14:18.138 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2018-12-06 16:14:18.139 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'
2018-12-06 16:14:18.139 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2018-12-06 16:14:18.139 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2018-12-06 16:14:18.140 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2018-12-06 16:14:18.141 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
2018-12-06 16:14:18.142 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2018-12-06 16:14:18.142 DEBUG 7856 --- [nio-8762-exec-1] o.s.security.web.FilterChainProxy        : /res/images at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2018-12-06 16:14:18.169 DEBUG 7856 --- [nio-8762-exec-2] o.s.security.web.FilterChainProxy        : /login at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2018-12-06 16:14:18.169 DEBUG 7856 --- [nio-8762-exec-2] o.s.security.web.FilterChainProxy        : /login at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2018-12-06 16:14:18.169 DEBUG 7856 --- [nio-8762-exec-2] o.s.security.web.FilterChainProxy        : /login at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2018-12-06 16:14:18.169 DEBUG 7856 --- [nio-8762-exec-2] o.s.security.web.FilterChainProxy        : /login at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2018-12-06 16:14:18.169 DEBUG 7856 --- [nio-8762-exec-2] o.s.security.web.FilterChainProxy        : /login at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'
2018-12-06 16:14:26.766 DEBUG 7856 --- [nio-8762-exec-3] o.s.security.web.FilterChainProxy        : /login?state=3kJ31R&code=eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAifQ..5ofS501Kavlbcrtn.c0Wq9m7ZQVwewQD1a_cmnTz4UhvNA50kqGSsF6UOTYhUvMVBv5qsNmHLkqopxQKbNhKImqUDNLqA7t8qgEYIN59juuFCou0kUkDDFmNspSYCLCebhIEeu9EgkCcUH-cqeVom4LjIJyw9QdeHcz8c-89ryLOz1zD9ISS-xGhHHt1DHd8cwtfy-WiVFOaFsvt2L-kSU8iA_cGY4ikT9rf5NdnZ5DSJjvRfNCZnsuKn6dS21McV-M9W1q3Ndyna2Gzf6xldj1SYACb-TxGG-lcOJWnlyf-U2SzZOK_F-r41ZHwxT1cw2iyFHr8qrCaaNRWGJstQ4c2Bs9Vsx0eIi9t_7JXhr4OqfnwAL7vqxlX8pIpaSgAbpQpEa3O7Y0ScNUKXr9HvNpVx9w--ebhkfdjYdBo0QE3RwB3jbKl0u-QGwRqNNUzMO1-DGd_tLhPeX-yLEFMsRSQhQCaeaNT_ZRdmyf63NsGxaOxSEpf3Z0eKJCY0A5knTHmH2A.7BkuNnBwxVL237E6vqMeAw at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2018-12-06 16:14:26.767 DEBUG 7856 --- [nio-8762-exec-3] o.s.security.web.FilterChainProxy        : /login?state=3kJ31R&code=eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAifQ..5ofS501Kavlbcrtn.c0Wq9m7ZQVwewQD1a_cmnTz4UhvNA50kqGSsF6UOTYhUvMVBv5qsNmHLkqopxQKbNhKImqUDNLqA7t8qgEYIN59juuFCou0kUkDDFmNspSYCLCebhIEeu9EgkCcUH-cqeVom4LjIJyw9QdeHcz8c-89ryLOz1zD9ISS-xGhHHt1DHd8cwtfy-WiVFOaFsvt2L-kSU8iA_cGY4ikT9rf5NdnZ5DSJjvRfNCZnsuKn6dS21McV-M9W1q3Ndyna2Gzf6xldj1SYACb-TxGG-lcOJWnlyf-U2SzZOK_F-r41ZHwxT1cw2iyFHr8qrCaaNRWGJstQ4c2Bs9Vsx0eIi9t_7JXhr4OqfnwAL7vqxlX8pIpaSgAbpQpEa3O7Y0ScNUKXr9HvNpVx9w--ebhkfdjYdBo0QE3RwB3jbKl0u-QGwRqNNUzMO1-DGd_tLhPeX-yLEFMsRSQhQCaeaNT_ZRdmyf63NsGxaOxSEpf3Z0eKJCY0A5knTHmH2A.7BkuNnBwxVL237E6vqMeAw at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2018-12-06 16:14:26.767 DEBUG 7856 --- [nio-8762-exec-3] o.s.security.web.FilterChainProxy        : /login?state=3kJ31R&code=eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAifQ..5ofS501Kavlbcrtn.c0Wq9m7ZQVwewQD1a_cmnTz4UhvNA50kqGSsF6UOTYhUvMVBv5qsNmHLkqopxQKbNhKImqUDNLqA7t8qgEYIN59juuFCou0kUkDDFmNspSYCLCebhIEeu9EgkCcUH-cqeVom4LjIJyw9QdeHcz8c-89ryLOz1zD9ISS-xGhHHt1DHd8cwtfy-WiVFOaFsvt2L-kSU8iA_cGY4ikT9rf5NdnZ5DSJjvRfNCZnsuKn6dS21McV-M9W1q3Ndyna2Gzf6xldj1SYACb-TxGG-lcOJWnlyf-U2SzZOK_F-r41ZHwxT1cw2iyFHr8qrCaaNRWGJstQ4c2Bs9Vsx0eIi9t_7JXhr4OqfnwAL7vqxlX8pIpaSgAbpQpEa3O7Y0ScNUKXr9HvNpVx9w--ebhkfdjYdBo0QE3RwB3jbKl0u-QGwRqNNUzMO1-DGd_tLhPeX-yLEFMsRSQhQCaeaNT_ZRdmyf63NsGxaOxSEpf3Z0eKJCY0A5knTHmH2A.7BkuNnBwxVL237E6vqMeAw at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2018-12-06 16:14:26.767 DEBUG 7856 --- [nio-8762-exec-3] o.s.security.web.FilterChainProxy        : /login?state=3kJ31R&code=eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAifQ..5ofS501Kavlbcrtn.c0Wq9m7ZQVwewQD1a_cmnTz4UhvNA50kqGSsF6UOTYhUvMVBv5qsNmHLkqopxQKbNhKImqUDNLqA7t8qgEYIN59juuFCou0kUkDDFmNspSYCLCebhIEeu9EgkCcUH-cqeVom4LjIJyw9QdeHcz8c-89ryLOz1zD9ISS-xGhHHt1DHd8cwtfy-WiVFOaFsvt2L-kSU8iA_cGY4ikT9rf5NdnZ5DSJjvRfNCZnsuKn6dS21McV-M9W1q3Ndyna2Gzf6xldj1SYACb-TxGG-lcOJWnlyf-U2SzZOK_F-r41ZHwxT1cw2iyFHr8qrCaaNRWGJstQ4c2Bs9Vsx0eIi9t_7JXhr4OqfnwAL7vqxlX8pIpaSgAbpQpEa3O7Y0ScNUKXr9HvNpVx9w--ebhkfdjYdBo0QE3RwB3jbKl0u-QGwRqNNUzMO1-DGd_tLhPeX-yLEFMsRSQhQCaeaNT_ZRdmyf63NsGxaOxSEpf3Z0eKJCY0A5knTHmH2A.7BkuNnBwxVL237E6vqMeAw at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2018-12-06 16:14:26.767 DEBUG 7856 --- [nio-8762-exec-3] o.s.security.web.FilterChainProxy        : /login?state=3kJ31R&code=eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAifQ..5ofS501Kavlbcrtn.c0Wq9m7ZQVwewQD1a_cmnTz4UhvNA50kqGSsF6UOTYhUvMVBv5qsNmHLkqopxQKbNhKImqUDNLqA7t8qgEYIN59juuFCou0kUkDDFmNspSYCLCebhIEeu9EgkCcUH-cqeVom4LjIJyw9QdeHcz8c-89ryLOz1zD9ISS-xGhHHt1DHd8cwtfy-WiVFOaFsvt2L-kSU8iA_cGY4ikT9rf5NdnZ5DSJjvRfNCZnsuKn6dS21McV-M9W1q3Ndyna2Gzf6xldj1SYACb-TxGG-lcOJWnlyf-U2SzZOK_F-r41ZHwxT1cw2iyFHr8qrCaaNRWGJstQ4c2Bs9Vsx0eIi9t_7JXhr4OqfnwAL7vqxlX8pIpaSgAbpQpEa3O7Y0ScNUKXr9HvNpVx9w--ebhkfdjYdBo0QE3RwB3jbKl0u-QGwRqNNUzMO1-DGd_tLhPeX-yLEFMsRSQhQCaeaNT_ZRdmyf63NsGxaOxSEpf3Z0eKJCY0A5knTHmH2A.7BkuNnBwxVL237E6vqMeAw at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'

Nothing shows up on the micro service debug console.
I am guessing, the Zuul gateway didn't handle the token correctly. Therefore no access token is relay to the actually resource server. That is the micro service.

How do I fix this? what am I missing?

I've already try context-path, same error stackoverflow link

maxiwu commented 5 years ago

I find out my .yml seems incorrect. If I want to use jwk. I should set prefer-token-info to true. After I changed this. It has a class not found exception java.lang.ClassNotFoundException: org.springframework.security.oauth2.provider.token.TokenStore

but, I can find the class in spring-security-oauth2, both 2.0.12 and 2.3.4 has it.

maxiwu commented 5 years ago

After trying many combination of spring boot and spring cloud. The closest one is Spring boot 1.5.16.RELEASE + spring cloud Edgware.SR5. But this combination gives me java.lang.ClassNotFoundException: org.springframework.security.oauth2.provider.token.RemoteTokenServices If I try to fix it forcing spring-security-oauth2 version to 2.0.15.RELEASE. It yields java.lang.ClassNotFoundException: org.springframework.boot.context.properties.bind.Bindable

I finally managed to make it run, without knowing the reason. the effective POM https://gist.github.com/maxiwu/6ed192adcfaad6866e7a3958d4ef8744

But then I decide to use SCG, spring 2.0.5 and Finchley.SR1. So that it is more consistent with the resource server. spring cloud gateway configuration:

spring.cloud.gateway:
  routes:
  - id: test
    uri: lb://image-service
    predicates:
    - Path=/res/images/**

The final result is that the gateway is using client_id, client_secret with the auth code. Send a request to Azure B2C /token endpoint to try retrieving an access token. And got null

java.lang.IllegalStateException: Access token provider returned a null access token, which is illegal according to the contract.
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:223) ~[spring-security-oauth2-2.1.0.RELEASE.jar:na]
Access token provider returned a null access token, which is illegal according to the contract.

https://gist.github.com/maxiwu/420901af6e25db95ecc8583fe5975ed3 complete error log

How do I debug this?
How do I log the RestTemplate request that sends out (header, form, parameters)and response from Azure B2C?

maxiwu commented 5 years ago

update B2C settings in yml

security:
  basic:
    enabled: false
  oauth2:
    sso:
      loginPath: /login
    client:
      clientId: 111ddde5-38af-4c31-a540-f204d64ccc5e
      clientSecret: [protected]
      access-token-uri: https://login.microsoftonline.com/te/umediax.onmicrosoft.com/b2c_1_signinup/oauth2/v2.0/token
      user-authorization-uri: https://login.microsoftonline.com/te/umediax.onmicrosoft.com/b2c_1_signinup/oauth2/v2.0/authorize
      scope: openid
      auto-approve-scopes: true
      authorizedGrantTypes: authorization_code
      clientAuthenticationScheme: form
    resource:    
      jwk:
        key-set-uri: https://login.microsoftonline.com/te/umediax.onmicrosoft.com/b2c_1_signinup/discovery/v2.0/keys
      prefer-token-info: true
ryanjbaxter commented 5 years ago

Is there still an issue?

maxiwu commented 5 years ago

@ryanjbaxter Yes, I still can't. spring didn't resolve that token in the URL.

After the RestTemplate send a POST request to B2C token endpoint. with client_id and client_secret in "query". and also the auth_code. org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:223) ~[spring-security-oauth2-2.1.0.RELEASE.jar:na] Access token provider returned a null access token, which is illegal according to the contract.

the error stack trace https://gist.github.com/maxiwu/420901af6e25db95ecc8583fe5975ed3

ryanjbaxter commented 5 years ago

To help we need a sample to reproduce the problem.

spring-projects-issues commented 5 years ago

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

maxiwu commented 5 years ago

uploaded all three projects on my google drive https://drive.google.com/open?id=1cMwYe6zjSWobuVQNSHN2yMp3wEEGipV8

ryanjbaxter commented 5 years ago

Can you provide details of how to reproduce the problem using the sample?

maxiwu commented 5 years ago

yes.

  1. start the clusterdemo project. which is the Eureka server
  2. start the clusterdemo_client project. which is the micro service
  3. start the zuulgateway project.

open up a browser, type in the url http://localhost:8762/res/images you should be redirected to Azure B2C login screen. You can register a new account and login. after login, you will see the error screen and the code value is in the url.

ryanjbaxter commented 5 years ago

I just looked at your Zuul Gateway application and actually its not even using Zuul nor is it using Spring Security, so this has nothing to do with Spring Cloud Netflix, or Spring Cloud in general.

maxiwu commented 5 years ago

Sorry for that late response, has been caught up with other projects.

I've revert it back to zuul + spring security + spring 1.5 https://drive.google.com/open?id=1DiI-1d3PVAzfPvn9ouaZdNI8xCpxWLTs

The Zuul and security setup are @EnableZuulProxy with @EnableOAuth2Sso. With the application.yml which has zuul.routes properties.

Please take a look, I really want to know what I was doing wrong and want to make this setup work.

ryanjbaxter commented 5 years ago

That is only zuul please provide a single zip that contains EVERYTHING I need to reproduce the issue

maxiwu commented 5 years ago

Glad to hear from you.

upload zip with all projects and files. google drive