Page 1 of 3 123 LastLast
Results 1 to 10 of 22

Thread: Spring OAuth2 M6 and Google Analytics: No OAuth 2 Security Context

  1. #1
    Join Date
    Dec 2010
    Posts
    315

    Exclamation Spring OAuth2 M6 and Google Analytics: No OAuth 2 Security Context

    I'm trying to write a service that will pull data from Google Analytics via OAuth2. I've read the docs from Google and Spring and search the net. But this one has given me a headache.

    I've setup the necessary Google API console for the client ID, client secret, redirect URI and etc.

    Basically, I have an MVC app. To test a sample pulling of data from Google Analytics, I have the following URL: http://localhost:8080/app/google/test (see the controller below).

    When going to this URL, I'm presented to the login page. So I have to login first then I'm redirected to the Google Auth permission. So I granted permission to the app. Then it's redirected to http://localhost:8080/app/google/test again (This is the same redirect URI I declared in the Google Console API).

    However, this time two parameters are added the state and the code. Why am I given these two parameters? Isn't OAuth2RestTemplate supposed to handle the OAuth dance for me?

    Am I missing something?

    I'm receiving the following exception:

    Code:
    org.springframework.security.oauth2.client.http.AccessTokenRequiredException: No OAuth 2 security context has been established. Unable to access resource 'oauth-resource'.
    	at org.springframework.security.oauth2.client.http.OAuth2ClientHttpRequestFactory.createRequest(OAuth2ClientHttpRequestFactory.java:55)
    	at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:76)
    	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:434)
    	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
    	at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:199)
    	at com.newmedia.jobboard.controller.GoogleAnalyticsController.test(GoogleAnalyticsController.java:56)
    Here's my configuration:

    Maven
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <spring.version>3.1.0.RELEASE</spring.version>
    <spring.security.version>3.1.0.RELEASE</spring.security.version>
    <spring.data.jpa.version>1.1.0.M1</spring.data.jpa.version>
    <spring.security.oauth.version>1.0.0.M6</spring.security.oauth.version>

    ...
    </properties>

    oauth.xml
    Code:
             <oauth:client id="oauth2AuthenticationClientFilter" resource-details-service-ref="resourceDetailsService"/>
    
    	<beans:bean id="resourceDetailsService" class="org.springframework.security.oauth2.client.resource.InMemoryOAuth2ProtectedResourceDetailsService"
    		p:resourceDetailsStore-ref="oauthResourceMap"/>
    		
    	<util:map id="oauthResourceMap">
    	    <beans:entry key="1" value-ref="oauth-resource"/>
    	</util:map>
    	
    	<oauth:resource id="oauth-resource"  
    		type="authorization_code"
    		access-token-uri="https://accounts.google.com/o/oauth2/token" 
    		user-authorization-uri="https://accounts.google.com/o/oauth2/auth"
    		client-id="dlkfjglksdjglsdjglsdfgdfgagsd" 
    		client-secret="sgsdfgsdfgdfgdfgdf"
    		scope="https://www.googleapis.com/auth/analytics.readonly"/>
    	 
    	 <beans:bean id="oAuth2RestTemplate" class="org.springframework.security.oauth2.client.OAuth2RestTemplate"
    	 	c:resource-ref="oauth-resource"/>
    security.xml
    Code:
    <http use-expressions="true">
    		<intercept-url pattern="/login" access="permitAll"/>
    		<intercept-url pattern="/logout" access="permitAll"/>
    		
    ....
    		<intercept-url pattern="/google" access="hasRole('ROLE_ADMIN')"/>
    		<intercept-url pattern="/google/test" access="hasRole('ROLE_ADMIN')"/>
    		
    		<form-login login-page="/login" 
    			authentication-failure-url="/login?error=true" 
    			default-target-url="${afterLoginPage}"/>
    		
    		<access-denied-handler error-page="/login/denied"/>
    		
    		<logout invalidate-session="true" 
    			logout-success-url="${afterLogoutPage}" 
    			logout-url="/logout"/>
    			
    		<custom-filter after="EXCEPTION_TRANSLATION_FILTER" ref="oauth2AuthenticationClientFilter" />
    	</http>
    Controller
    Code:
    @Controller
    @RequestMapping("/google")
    public class GoogleAnalyticsController {
    
    	protected static Logger logger = Logger.getLogger("controller");
    	
    	@Autowired
    	@Qualifier("oAuth2RestTemplate")
    	private RestTemplate oauth2RestTemplate;
    	
    	@RequestMapping
    	public String getMain() {
    		return "google-analytics.tiles";
    	}
    	
    	@RequestMapping(value = "/test", method = RequestMethod.GET)
    	public @ResponseBody StatusResponse test() {
    
    			logger.debug("Calling dataUri");
    			String dataUri = "https://www.googleapis.com/analytics/v3/data/ga?" +
    			"ids=ga:11122233&" +
    			"start-date=2009-04-20&" +
    			"end-date=2012-06-20&" +
    			"metrics=ga:visits,ga:bounces";
    	
    			ObjectNode result = oauth2RestTemplate.getForObject(dataUri, ObjectNode.class);
    
    			return new StatusResponse(true, result.toString());
    		
    	}
    }

  2. #2
    Join Date
    Dec 2010
    Posts
    315

    Default

    By the way I have tested first the data url via Google's OAuth Playground.

  3. #3
    Join Date
    Jun 2005
    Posts
    4,231

    Default

    I can't see anything immediately wrong. What server platform are you on? Does tonr2 work on the same platform?

    The error suggests that the OAuth2ClientContextFilter was not applied to your request. If you look at the whole stack trace you should be able to see it, and if not it's a config error that somehow gets the request to your controller without going through that filter.

    N.B. the client support has changed a lot since M6. You might want to try with a more recent build (and use <oauth:rest-template/>).

  4. #4
    Join Date
    Dec 2010
    Posts
    315

    Default

    Dave, thanks for the reply. I'd been working with this problem for 12 hours :-)

    I actually inspected the whole stack trace but I get the same exception and information. You're right. It's possible that the OAuth2ClientContextFilter was not applied but I don't know why.

    My server platform is Tomcat 7.xx. I have tested with Tomcat 5.xx as well. I have deployed both on Ubuntu and Windows 7 and with the same results. Yes, tonr2 and sparkl work in the same platform.

    I will try to use the latest snapshots. M6 was the last tag in the GitHub page, so I thought it's the latest.

    By the way, I had also tried the following simpler configuration but with the same result

    Code:
          <oauth:client id="oauth2AuthenticationClientFilter"/>
    	
    	<oauth:resource id="oauth-resource"  
    		type="authorization_code"
    		access-token-uri="https://accounts.google.com/o/oauth2/token" 
    		user-authorization-uri="https://accounts.google.com/o/oauth2/auth"
    		client-id="dlkfjglksdjglsdjglsdfgdfgagsd" 
    		client-secret="sgsdfgsdfgdfgdfgdf"
    		scope="https://www.googleapis.com/auth/analytics.readonly"/>
    	 
    	 <beans:bean id="oAuth2RestTemplate" class="org.springframework.security.oauth2.client.OAuth2RestTemplate"
    	 	c:resource-ref="oauth-resource"/>
    This is equivalent to my initial config right?

  5. #5
    Join Date
    Dec 2010
    Posts
    315

    Default

    Dave, just a heads-up. I'm using M6d now, but there is no <oauth:rest-template/>.

    Anyway, I proceeded with my original configuration. Right after I updated to M6d, I got the following exception:
    Code:
    java.lang.IllegalStateException: No redirect URI has been established for the current request.
    	org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.getRedirectForAuthorization(AuthorizationCodeAccessTokenProvider.java:286)
    	org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:157)
    	org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:120)
    	org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:100)
    	org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:194)
    	org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:148)
    	org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:89)
    I fixed it by declaring a pre-established-redirect-uri (previously, this error wasn't visible):
    Code:
    <oauth:resource id="oauth-resource"  
    		type="authorization_code"
    		access-token-uri="https://accounts.google.com/o/oauth2/token" 
    		user-authorization-uri="https://accounts.google.com/o/oauth2/auth"
    		client-id="dlkfjglksdjglsdjglsdfgdfgagsd" 
    		client-secret="sgsdfgsdfgdfgdfgdf"
    		scope="https://www.googleapis.com/auth/analytics.readonly"
    		pre-established-redirect-uri="http://localhost:8080/app/google/test"/>
    But then I hit a new exception:
    Code:
    [(HttpAccessor.java:78:http-bio-8080-exec-5) 12:08:27]Created POST request for "https://accounts.google.com/o/oauth2/auth"
    [(RestTemplate.java:478:http-bio-8080-exec-5) 12:08:27]POST request for "https://accounts.google.com/o/oauth2/auth" resulted in 400 (Bad Request); invoking error handler
    [(AbstractHandlerExceptionResolver.java:132:http-bio-8080-exec-5) 12:08:27]Resolving exception from handler [public com.newmedia.jobboard.dto.StatusResponse com.newmedia.jobboard.controller.GoogleAnalyticsController.test()]: org.springframework.web.client.HttpClientErrorException: 400 Bad Request
    [(AbstractHandlerExceptionResolver.java:132:http-bio-8080-exec-5) 12:08:27]Resolving exception from handler [public com.newmedia.jobboard.dto.StatusResponse com.newmedia.jobboard.controller.GoogleAnalyticsController.test()]: org.springframework.web.client.HttpClientErrorException: 400 Bad Request
    [(AbstractHandlerExceptionResolver.java:132:http-bio-8080-exec-5) 12:08:27]Resolving exception from handler [public com.newmedia.jobboard.dto.StatusResponse com.newmedia.jobboard.controller.GoogleAnalyticsController.test()]: org.springframework.web.client.HttpClientErrorException: 400 Bad Request
    [(FrameworkServlet.java:910:http-bio-8080-exec-5) 12:08:27]Could not complete request
    org.springframework.web.client.HttpClientErrorException: 400 Bad Request
    	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:76)
    	at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:185)
    	at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486)
    	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443)
    	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:409)
    	at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAuthorizationCode(AuthorizationCodeAccessTokenProvider.java:109)
    Why is it doing a POST request to authenticate again?
    Code:
    POST request for "https://accounts.google.com/o/oauth2/auth"
    Earlier on the logs, it's clear that my app is already authenticated:
    Code:
    [(FilterChainProxy.java:304:http-bio-8080-exec-5) 12:08:27]/google/test?state=CpPSCj&code=4/dwv5L9828YvOwHAc0hAggZtPBRVm.4kKXndclHesWuJJVnL49Cc90Mt-ebwI reached end of additional filter chain; proceeding with original chain
    I got an access code there, and the next step in the OAuth dance should be an exchange for token (Spring is supposed to pass the access code in return for an access token).

  6. #6
    Join Date
    Jun 2005
    Posts
    4,231

    Default

    OK, so it looks like the original error was the missing redirect uri. If you go back to M6 with your simplified config it would work. To use M6d you will need the <oauth:rest-template/> . It's there, I promise, but maybe your XML editor is not detecting the XSD in the jar file (if it goes to the internet to find it it will fail)?

  7. #7
    Join Date
    Dec 2010
    Posts
    315

    Default

    Thanks for the reply, Dave.

    Maybe you're right. My STS might have some issues refreshing the schema. I've examined the XSD inside the M6d jar and saw the rest-template element. So I used it anyway on my project and I get the invalid markup error. But when I run the application, it run. Apparently, it's an editor issue.

    After running the application I was able to progress one step. However I got a new error:

    Code:
    (HttpAccessor.java:78:http-bio-8080-exec-4) 10:03:21]Created POST request for "https://accounts.google.com/o/oauth2/token"
    [(RestTemplate.java:478:http-bio-8080-exec-4) 10:03:22]POST request for "https://accounts.google.com/o/oauth2/token" resulted in 400 (Bad Request); invoking error handler
    [(AbstractHandlerExceptionResolver.java:132:http-bio-8080-exec-4) 10:03:22]Resolving exception from handler
    Code:
    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is error="access_denied", error_description="Access token denied."
    	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
    	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
    	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    	javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
    Code:
    error="access_denied", error_description="Access token denied."
    	org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:93)
    	org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:161)
    	org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:120)
    Code:
    error="invalid_request", error_description="invalid_request"
    	org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionDeserializer.deserialize(OAuth2ExceptionDeserializer.java:81)
    	org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionDeserializer.deserialize(OAuth2ExceptionDeserializer.java:30)
    	org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2391)
    	org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1651)
    I'm wondering why the Access Token is invalid or why is there an invalid request. Is there a way to show more logs? I'm already at debug level for log4j but unfortunately the explanation I got is just that.

  8. #8
    Join Date
    Dec 2010
    Posts
    315

    Default

    To fix the issue for the missing <oauth:rest-template> element, I had to do a clear cache of the stored XSDs.

    In STS, I went to Window > Preferences. Then on the General section, select Network Connections > Cache. Then I selected the spring-security-oauth2.xsd and remove it (see http://www.eclipse.org/forums/index.php/m/640708/)

    But my original problem is still on-going

    Edit: That didn't fix the missing tag issue, sorry
    Last edited by skram; Jun 10th, 2012 at 04:11 AM.

  9. #9
    Join Date
    Dec 2010
    Posts
    315

    Default

    I tried debugging the requests. It appears the reason why I get the following:

    Code:
    error="access_denied", error_description="Access token denied."
    	org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:93)
    	org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:161)
    is because the other parameters required in retrieving an access token is never passed by Spring. Based on Google's OAuth 2.0 docs, the following parameters need to be passed (of course with the correct data):

    Code:
    POST /o/oauth2/token HTTP/1.1
    Host: accounts.google.com
    Content-Type: application/x-www-form-urlencoded
    
    code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
    client_id=8819981768.apps.googleusercontent.com&
    client_secret={client_secret}&
    redirect_uri=https://oauth2-login-demo.appspot.com/code&
    grant_type=authorization_code
    All I see from the debug results is a call https://accounts.google.com/o/oauth2/token, and hence an "Access token denied" is returned.

    To test this observation, I fired up RESTClient (see http://rest-client.googlecode.com/) to simulate a post request to https://accounts.google.com/o/oauth2/token. And here's what I got:

    Code:
    {
      "error" : "invalid_request"
    }
    It's interesting that this is the same error I got from my app:

    Code:
    error="invalid_request", error_description="invalid_request"
    	org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionDeserializer.deserialize(OAuth2ExceptionDeserializer.java:81)
    	org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionDeserializer.deserialize(OAuth2ExceptionDeserializer.java:30)
    	org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2391)
    	org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1651)
    Just to verify it again, I opened up Google's OAuth 2.0 Playground (see https://code.google.com/oauthplayground) and tried modifying the authorization code:

    Code:
    POST /o/oauth2/token HTTP/1.1
    Host: accounts.google.com
    Content-length: 183
    content-type: application/x-www-form-urlencoded
    user-agent: google-oauth-playground
    code=gfj&redirect_uri=https%3A%2F%2Fcode.google.com%2Foauthplayground&client_id=***************=&client_secret=************&grant_type=authorization_code
    And the result:

    Code:
    HTTP/1.1 400 Ok
    status: 400
    content-length: 31
    x-xss-protection: 1; mode=block
    x-content-type-options: nosniff
    expires: Fri, 01 Jan 1990 00:00:00 GMT
    x-google-cache-control: remote-fetch
    server: GSE
    via: HTTP/1.1 GWA
    pragma: no-cache
    cache-control: no-cache, no-store, max-age=0, must-revalidate
    date: Sun, 10 Jun 2012 10:20:31 GMT
    x-frame-options: SAMEORIGIN
    content-type: application/json
    -content-encoding: gzip
    {
      "error" : "invalid_grant"
    }
    So my conclusion here is that in the first place Spring OAuth is not sending the correct HTTP parameters because if it did it would show in the Debug details and it would throw an invalid_grant error instead (if the values are wrong).
    Last edited by skram; Jun 10th, 2012 at 05:26 AM.

  10. #10
    Join Date
    Jun 2005
    Posts
    4,231

    Default

    OK, so evidently Google doesn't allow client authentication in the Authorization header (which is the default for the SECOAUTH client and the recommended option in the spec). You would need to add client-authentication-scheme="form" to your resource declaration. If you look at the tonr2 sample you can see that Facebook has the same unnecessary restriction.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •