4

As context, I've been trying to get a fairly simple @SprintBootApplication with an additional @EnableOAuth2Sso annotation integrated with WSO2 Identity Server for quite some time now.

In my mind getting this working should be a matter of configuration (as advertised on Spring Cloud Security) - but I've had no luck thus far.

In an effort to understand what is going on I've used my debugger to step through spring-security-oauth2 code to figure out what is going on. In doing so I've noticed that my AccessTokenRequest's PreservedState is perpetually null with a resultant CSRF related InvalidRequestException. This is the relevant code:

public class AuthorizationCodeAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider {

....

private MultiValueMap<String, String> getParametersForTokenRequest(AuthorizationCodeResourceDetails resource,
        AccessTokenRequest request) {

    MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
    form.set("grant_type", "authorization_code");
    form.set("code", request.getAuthorizationCode());

    Object preservedState = request.getPreservedState();
    if (request.getStateKey() != null || stateMandatory) {
        // The token endpoint has no use for the state so we don't send it back, but we are using it
        // for CSRF detection client side...
        if (preservedState == null) {
            throw new InvalidRequestException(
                    "Possible CSRF detected - state parameter was required but no state could be found");
        }
    }

When it comes to the above bit of code, I'm at the point where I've approved the claim after having logged in with admin/admin and my web application has received the auth code:

http://localhost:9998/loginstate=Uu8ril&code=20ffbb6e4107ce3c5cf9ee22065f4f2 

Given that all I need to do in the first instance is to get the login part working I've tried disabling CSRF, to no avail.

The relevant configuration is as follows:

spring:
  profiles: default
security:
  oauth2:
    client:
      accessTokenUri: https://localhost:9443/oauth2/token
      userAuthorizationUri: https://localhost:9443/oauth2/authorize
      clientId: yKSD9XwET9XJ3srGEFXP6AfHhAka
      clientSecret: zuPTcdJH435h3wgl055XNZ5ffNMa
      scope: openid
      clientAuthenticationScheme: header
     resource:
      userInfoUri: https://localhost:9443/oauth2/userinfo?schema=openid

In terms of my own investigative efforts, of concern is that in the DefaultOAuthClientContext preserved state gets cleared right before it needs to used, this appears to be a sequencing issue.

I'm using the latest releases of Spring Boot (1.3.0) and WSO2 Identity Server (5.0). Also using spring-security-oauth 2.0.8.

Community
  • 1
  • 1
Nico de Wet
  • 319
  • 2
  • 12

2 Answers2

5

As it turns out, the reason why the cited preservedState is null in the section of code provided is because a new instance of bean Oauth2ClientContext is being created which is precisely what should not be happening - the entire purpose of the OAuth2ClientContext is to store state. In terms of the OAuth2 protocol (RFC 6749), preserving the state is important in terms of Cross-Site Request Forgery prevention (see Section 10.12).

Picking this up is a simple matter of enabling debug logging and also comparing the output generated against WSO2 IS with what one sees with a working example. In my case the working example that I always revert back to is the one provided by the Spring team themselves.

This is the client configuration (application.yml) and then log output testing using the Spring team SSO server:

spring:
  profiles: default
security:
  oauth2:
    client:
      accessTokenUri: http://192.168.0.113:32768/uaa/oauth/token
      userAuthorizationUri: http://192.168.0.113:32768/uaa/oauth/authorize
  clientId: acme
  clientSecret: acmesecret
resource:
  jwt:
    keyValue: |
      -----BEGIN PUBLIC KEY-----
      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGp/Q5lh0P8nPL21oMMrt2RrkT9AW5jgYwLfSUnJVc9G6uR3cXRRDCjHqWU5WYwivcF180A6CWp/ireQFFBNowgc5XaA0kPpzEtgsA5YsNX7iSnUibB004iBTfU9hZ2Rbsc8cWqynT0RyN4TP1RYVSeVKvMQk4GT1r7JCEC+TNu1ELmbNwMQyzKjsfBXyIOCFU/E94ktvsTZUHF4Oq44DBylCDsS1k7/sfZC2G5EU7Oz0mhG8+Uz6MSEQHtoIi6mc8u64Rwi3Z3tscuWG2ShtsUFuNSAFNkY7LkLn+/hxLCu2bNISMaESa8dG22CIMuIeRLVcAmEWEWH5EEforTg+QIDAQAB
      -----END PUBLIC KEY-----
  id: openid
  serviceId: ${PREFIX:}resource

Take note that there is no line mentioned the creation of OAuth2ClientContext.

DEBUG o.s.security.web.FilterChainProxy - /login?code=9HLSpP&state=G9kpy3 at position 6 of 12 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/login'
DEBUG o.s.s.o.c.f.OAuth2ClientAuthenticationProcessingFilter - Request is to process authentication
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Getting access token request
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'scopedTarget.accessTokenRequest'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'scopedTarget.accessTokenRequest'
INFO  o.s.s.o.client.OAuth2RestTemplate - Longer lived state key: G9kpy3
INFO  o.s.s.o.client.OAuth2RestTemplate - Removing preserved state in oauth2context
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Found preserved state: http://localhost:9999/login

This is the client configuration (application.yml) and then log output testing using WSO2IS 5.0.0:

spring:
  profiles: wso2
server:
  port: 9998
security:
  oauth2:
    client:
      accessTokenUri: https://localhost:9443/oauth2/token
      userAuthorizationUri: https://localhost:9443/oauth2/authorize
      clientId: yKSD9XwET9XJ3srGEFXP6AfHhAka
      clientSecret: zuPTcdJH435h3wgl055XNZ5ffNMa
      scope: openid
      clientAuthenticationScheme: header
    resource:
      userInfoUri: https://localhost:9443/oauth2/userinfo?schema=openid

Take note of the line with Creating instance of bean 'scopedTarget.oauth2ClientContext'.

DEBUG o.s.security.web.FilterChainProxy - /login?state=PWhQwv&code=372ff0c197a4c85a0caf070cc9a6678 at position 6 of 12 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/login'
DEBUG o.s.s.o.c.f.OAuth2ClientAuthenticationProcessingFilter - Request is to process authentication
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'scopedTarget.oauth2ClientContext'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration$SessionScopedConfiguration$ClientContextConfiguration'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'scopedTarget.oauth2ClientContext'
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Getting access token request
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'scopedTarget.accessTokenRequest'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'scopedTarget.accessTokenRequest'
INFO  o.s.s.o.client.OAuth2RestTemplate - Longer lived state key: PWhQwv
INFO  o.s.s.o.client.OAuth2RestTemplate - Removing preserved state in oauth2context
INFO  o.s.s.o.c.DefaultOAuth2ClientContext - Found preserved state: null

Finally, the next port of call is naturally to ascertain why the OAuth2ClientContext was not created with the WSO2 IS config. Investigation has shown that it is because WSO2 IS is not passing back an expected JSESSIONID, hence the session scoped OAuth2ClientContext will not be found.

A potential hack to fix this situation if desperate is to clone Spring OAuth 2 and do the following:

In class AuthorizationCodeAccessTokenProvider do the following with the hack being to change the preserved state in the request.

    private MultiValueMap<String, String>     getParametersForTokenRequest(AuthorizationCodeResourceDetails resource,
        AccessTokenRequest request) {

            MultiValueMap<String, String> form = new   LinkedMultiValueMap<String, String>();
            form.set("grant_type", "authorization_code");
            form.set("code", request.getAuthorizationCode());

            request.setPreservedState("http://localhost:9998/login");
            Object preservedState = request.getPreservedState();
Community
  • 1
  • 1
Nico de Wet
  • 319
  • 2
  • 12
  • if you can can share the source code? Kind a stuck with this over a week now. – Randika Hapugoda Feb 05 '16 at 04:32
  • sure, I ended up forking spring oauth 2.0.8 locally and making a change in the library which is what I want to avoid at all costs since this will be a headache for maintenance developers that come after me - so I want to keep digging into this – Nico de Wet Feb 11 '16 at 07:48
  • I edited the answer with the hack at the end, if memory serves it worked for me - but I still don't want to do this – Nico de Wet Feb 11 '16 at 08:03
  • The client context is supposed to be session scoped in the client (not the provider), so WSO2 has no role to play in that. You probably need to fix the client to make sure it has sessions and cookies. – Dave Syer Feb 11 '16 at 08:20
  • 3
    Hi, Is there any concrete way to get this solved without hacking the library? Facing similar issue when trying to access the resources from system tests using RestAssured but working fine from browser. – vikas Jun 01 '16 at 20:16
  • Here is a working Spring Boot project you can clone that works against WSO2 IS 5.1.0: https://github.com/nicodewet/template-spring-boot-oauth2-wso2-is – Nico de Wet Jul 17 '16 at 07:34
1

Just seen a similar issue while trying out https://spring.io/guides/tutorials/spring-boot-oauth2/ and observed that this issue is with chrome browser that wasn't passing the JSESSIONID after facebook redirect .

Same worked when I Used Safari , (just adding it here so that it saves someone's two hours !) request sequence

you can see that set-cookie is called twice as second login/facebook call is not passing previously set sessionid

Prashant Bhate
  • 10,907
  • 7
  • 47
  • 82