Skip to content

@EnableOAuth2Sso breaks Google App Engine #10553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alexmitic opened this issue Oct 8, 2017 · 12 comments
Closed

@EnableOAuth2Sso breaks Google App Engine #10553

alexmitic opened this issue Oct 8, 2017 · 12 comments
Assignees
Labels
for: external-project For an external project and not something we can fix

Comments

@alexmitic
Copy link

Summary

I have added OAuth2 support to my application by following this tutorial https://spring.io/guides/tutorials/spring-boot-oauth2/, that uses spring-boot-starter-security and spring-security-oauth2. With the help of the @EnableOAuth2Sso annotation I get the app to authenticate with gmail. When I run this app locally it works without any problems but when I deploy to Google App Engine I get Server Error 500. If I remove the authentication the app works when deployed. I've tried authenticating both with Facebook and Google, both give a server error when deployed. I initially posted this issue to spring-security but was told this was most likely related to Boot and told to post here.

Actual Behavior

On the server I get the following stack trace

o.s.core.annotation.AnnotationUtils      : Failed to introspect annotations on [class org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration$RemoteTokenServicesConfiguration$SocialTokenServicesConfiguration]: java.lang.IllegalStateException: Could not obtain annotation attribute value for public abstract java.lang.Class[] org.springframework.boot.autoconfigure.condition.ConditionalOnClass.value()

o.s.boot.SpringApplication               : Application startup failed

java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration$RemoteTokenServicesConfiguration$SocialTokenServicesConfiguration.socialTokenServices
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:64) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:102) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:178) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:140) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:116) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:320) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:228) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:270) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:93) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:687) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:525) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) ~[spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) ~[spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) ~[spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.web.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:151) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.web.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:131) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.web.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:86) [spring-boot-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:169) [spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.eclipse.jetty.plus.annotation.ContainerInitializer.callStartup(ContainerInitializer.java:140) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.annotations.ServletContainerInitializersStarter.doStart(ServletContainerInitializersStarter.java:63) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:330) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1406) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1368) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:778) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:262) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:522) [runtime-impl-third-party.jar:na]
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) [runtime-impl-third-party.jar:na]
    at com.google.apphosting.runtime.jetty9.AppVersionHandlerMap.createHandler(AppVersionHandlerMap.java:244) [runtime-impl.jar:na]
    at com.google.apphosting.runtime.jetty9.AppVersionHandlerMap.getHandler(AppVersionHandlerMap.java:182) [runtime-impl.jar:na]
    at com.google.apphosting.runtime.jetty9.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:97) [runtime-impl.jar:na]
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchServletRequest(JavaRuntime.java:650) [runtime-impl.jar:na]
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchRequest(JavaRuntime.java:612) [runtime-impl.jar:na]
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.run(JavaRuntime.java:582) [runtime-impl.jar:na]
    at com.google.apphosting.runtime.JavaRuntime$NullSandboxRequestRunnable.run(JavaRuntime.java:776) [runtime-impl.jar:na]
    at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:263) [runtime-impl.jar:na]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_112-google-v7]

Caused by: java.lang.NullPointerException: null
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition$BeanSearchSpec.collect(OnBeanCondition.java:329) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition$BeanSearchSpec.<init>(OnBeanCondition.java:282) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnBeanCondition.getMatchOutcome(OnBeanCondition.java:76) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    ... 37 common frames omitted 

Expected Behavior

When a user reaches a part of the domain, they should be guided to a google sign-in form. This happens locally but not when deployed to server.

Configuration

In my application properties I have

security:
  oauth2:
    client:
      clientId: My ID
      clientSecret: My secret
      accessTokenUri: https://www.googleapis.com/oauth2/v4/token
      userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
      clientAuthenticationScheme: form
      scope:
        - openid
        - email
        - profile
    resource:
      userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
      preferTokenInfo: true

And I configure the authentication

@Override
protected void configure(HttpSecurity http) throws Exception {
      http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
      .and()
          .authorizeRequests()
              .antMatchers("/") // Start page
                  .permitAll()
              .anyRequest() // Everything that is not start-page is authenticated
                  .authenticated();
 }

Version

spring-boot 1.5.6.RELEASE

spring-boot-starter-security 1.5.6.RELEASE

spring-security-oauth2 2.1.1.RELEASE

appengine-api-1.0-sdk 1.9.54

Here is my whole pom.xml if it adds some clarity

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- Exclude Tomcat so that it doesn't conflict w/ Jetty server -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>

    </dependency>

    <dependency>
        <groupId>com.google.appengine</groupId>
        <artifactId>appengine-api-1.0-sdk</artifactId>
        <version>1.9.54</version>
    </dependency>

    <dependency>                        <!-- Google Core Libraries for Java -->
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>  <!-- https://github.com/google/guava/wiki -->
        <!-- Guava v21.0 doesn't support Java7 -->
        <version>20.0</version>
        <scope>compile</scope>
    </dependency>

    <dependency>                        <!-- Google Cloud Client Library for Java -->
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-storage</artifactId>
        <version>1.3.1</version>
    </dependency>

    <!-- Exclude any jul-to-slf4j -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jul-to-slf4j</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- Include Servlet API -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>


    <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-datastore</artifactId>
        <version>1.4.0</version>
    </dependency>

    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.1</version>
     </dependency>

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-security</artifactId>
        <version>1.5.6.RELEASE</version>
    </dependency>

    <dependency>
    	<groupId>org.springframework.security.oauth</groupId>
    	<artifactId>spring-security-oauth2</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
</dependencies>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Oct 8, 2017
@mbhave
Copy link
Contributor

mbhave commented Oct 13, 2017

@alexmitic It's not clear from the logs why there is a failure when processing a ConditionalOnClass condition. Could you please provide a minimalistic sample that reproduces this issue?

@mbhave mbhave added the status: waiting-for-feedback We need additional information before we can continue label Oct 13, 2017
@alexmitic
Copy link
Author

alexmitic commented Oct 16, 2017

@mbhave I simply followed this example application that google has for spring-boot and App Engine Standard environment. I've added OAuth 2 by adding

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

to my dependencies and @EnableOAuth2Sso.

This is the minimal example

pom.xml

<dependencies>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>${spring.boot.version}</version>
        <!-- Exclude Tomcat so that it doesn't conflict w/ Jetty server -->
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<!-- Exclude any jul-to-slf4j -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jul-to-slf4j</artifactId>
  <scope>provided</scope>
</dependency>

<!-- Include Servlet API -->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <version>${spring.boot.version}</version>
  <scope>test</scope>
</dependency>
</dependencies>

ServletInitializer.java

public class ServletInitializer extends SpringBootServletInitializer {

  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(SpringBootExampleApplication.class);
  }

}

SpringBootExampleApplication.java

@SpringBootApplication
@EnableOAuth2Sso
public class SpringBootExampleApplication {

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

appengine-web.xml

<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <threadsafe>true</threadsafe>
  <runtime>java8</runtime>
  <sessions-enabled>true</sessions-enabled>
</appengine-web-app>

application.yml

security:
 oauth2:
   client:
     clientId: MyID
     clientSecret: MySecret
     accessTokenUri: https://www.googleapis.com/oauth2/v4/token
     userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
     clientAuthenticationScheme: form
     scope:
       - openid
       - email
       - profile
   resource:
     userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
     preferTokenInfo: true

I am able to deploy this but when I try to access the page I get the same error as before. This is really weird because everything else works just as expected when deployed. Its just the OAuth2 that works locally but not when deployed.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 16, 2017
@mbhave
Copy link
Contributor

mbhave commented Oct 17, 2017

I was able to reproduce this on Google App Engine. My guess is the underlying exception for the @ConditionalOnClass(OAuth2ConnectionFactory.class) failure is a TypeNotPresentException. I don't know why it occurs on GAE though. Doing a @ConditionalOnClass(name = "org.springframework.social.connect.support.OAuth2ConnectionFactory") works.

@mbhave mbhave added for: team-attention An issue we'd like other members of the team to review and removed status: feedback-provided Feedback has been provided labels Oct 17, 2017
@wilkinsona
Copy link
Member

@mbhave Is it possible to get the stack trace when this is logged:

o.s.core.annotation.AnnotationUtils      : Failed to introspect annotations on [class org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration$RemoteTokenServicesConfiguration$SocialTokenServicesConfiguration]: java.lang.IllegalStateException: Could not obtain annotation attribute value for public abstract java.lang.Class[] org.springframework.boot.autoconfigure.condition.ConditionalOnClass.value()

I, like you, don't know why this only occurs on GAE. It might help us to figure it out if we knew a bit more about when things are starting to go wrong.

@alexmitic
Copy link
Author

alexmitic commented Oct 19, 2017

@mbhave @wilkinsona Is there some temporary fix I can implement that will enable this functionality?

@mbhave
Copy link
Contributor

mbhave commented Oct 19, 2017

After investigating this a bit further, we've found that the underlying error occurs because Spring Framework decides to use StandardAnnotationMetadata instead of AnnotationMetadataReadingVisitor for SocialTokenServicesConfiguration on GAE. Since the exception is swallowed, there is no way for us to know whether there was an actual error or the annotation was not found. After discussing with @wilkinsona, we might need to split the condition implementation for @ConditionalOnClass and @ConditionalOnMissingClass to be able to accurately decide if there was an actual error while processing the annotation. Before proceeding with that, it would be good to see what creates StandardAnnotationMetadata.

@alexmitic a possible workaround that I can think of for now is to add spring-social-core as a dependency.

@lilianaziolek
Copy link

@alexmitic I managed to get OAuth2 working with Spring Boot & GAE standard but I'm not using the SSO annotation directly - I'm using @EnableOAuth2Client and manually constructed Filter, as described in one of the phases of boot tutorial ( https://spring.io/guides/tutorials/spring-boot-oauth2/#_social_login_manual ).

Just to be clear though, I was not attempting to use sso annotation (for non-GAE related reasons) so I'm not sure if that would work if I tried, or would I face same issue as yourself. Try decomposing the SSO as described and that should work. If it still doesn't, you might have a different problem.

One other thing to note is that there is some weird problem happening related to GAE not storing the session data correctly (happening on real GAE only, not when run locally). I've had to implement my own SessionRepository, as described here: https://stackoverflow.com/questions/45217234/issue-with-using-spring-oauth-on-java8-standard-environment

@alexmitic
Copy link
Author

alexmitic commented Oct 19, 2017

@lilianaziolek I saw on that stackoverflow-question you posted that you had converted the SessionRepository Kotilin code to Java and made it work. Would you mind sharing that piece of code? I'm having some trouble with it as I'm quite new to spring and GAE.

@lilianaziolek
Copy link

@alexmitic I don't have time right now to prepare a full package of the whole oauth config, but just for the memcache session repo stuff I've put the code involved in this gist: https://gist.github.com/lilianaziolek/6851c2f81be81eae2207ec863f41f484

@alexmitic
Copy link
Author

@lilianaziolek That was enough! I managed to get it to work. Thank you very much!
@mbhave @wilkinsona Thanks for the help! For now I've just added spring-social-core as a dependency as a temporary fix.

@mbhave mbhave removed the for: team-attention An issue we'd like other members of the team to review label Oct 25, 2017
@mbhave mbhave self-assigned this Oct 27, 2017
@mbhave mbhave added type: enhancement A general enhancement priority: low and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 9, 2018
@mbhave
Copy link
Contributor

mbhave commented Mar 9, 2018

See SPR-16564. @philwebb suggested checking if OnClassCondition can use a method that throws the exception in which case we could do ASM reading as a fallback.

@mbhave
Copy link
Contributor

mbhave commented Mar 12, 2018

The underlying issue has been fixed in Spring Framework so this should work as expected now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: external-project For an external project and not something we can fix
Projects
None yet
Development

No branches or pull requests

6 participants