How to make Spring Security OAuth2 really stateless / get rid of "state" parameter?
Asked Answered
K

1

17

I'm currently working on a project where we want the user to sign in via Facebook and other OAuth2 providers. Furthermore the REST api should be stateless. Therefore no cookies/jsessionids should be created/utilized. For authorization against the api, a JWT is issued by the api after a successful sign in via Facebook. The webapp consuming the rest api is build with AgularJS and satellizer. I reduced my code to a minimal example available on github.

Workflow idea:

  1. User enters the site, selects "login with facebook"
  2. The web application opens a pop up showing the facebook login page
  3. User signs in, accepts and facebook redirects to the webapp
  4. The webapp receives the token from Facebook and uses it to login to the rest api (GET /login/facebook?code=XXXXXXXXXXXXXXXXX)
  5. The rest api returns a JWT.
  6. The webapp uses the JWT to authorize against the rest api for any further requests.

Working so far

  • The webapp can handle steps 1 to 4
  • The rest api can handle steps 5 and 6 if you use the redirect you get from the OAuth2ClientContextFilter doing a GET to "/login/facebook" without the code parameter. (This redirect goes to facebook, you sign in, you get redirected to the api again.)
  • The rest api is stateless, no jsessionid/cookies are created/required (disabled csrf and used SessionCreationPolicy.STATELESS). Except for the "login/facebook" call, this one is still creating a jsessionid.

The problem

The combination of webapp and the rest api's "login/facebook?code=XXX" call does not work. I found out that when you do a GET "login/facebook", you will be redirected to facebook with an additional state parameter attached to the url. Furthermore, this state parameter is also added when facebook redirects back to the api. From what I found online, this seems to be a kind of CSRF protection, right? And I guess this stuff is also creating the jsessionid cookie?

Questions

  1. Is the workflow presented above a reasonable idea?
  2. Do I need this CSRF protection in my use case?
  3. How could I disable this behavior? I mean, I used SessionCreationPolicy.STATELESS but spring still creates a session with the jsessionid. How can I create a truly stateless rest api then? (At least regarding the cookies...)
  4. Is this the right way to do it? Or am I missing something?

Example Code

As already mentioned, I put a complete working minimal example on GitHub. Here I will only post the (hopefully) most important part of WebSecurityConfigurerAdapter. The complete file is here.

@EnableOAuth2Client
@Configuration
public class OAuth2ClientConfigurer extends WebSecurityConfigurerAdapter {

@Autowired
private OAuth2ClientContext oAuth2ClientContext;

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).enableSessionUrlRewriting(false).and()
            .antMatcher("/**").authorizeRequests()
            .antMatchers("/login/**").permitAll()
            .anyRequest().authenticated().and()
            .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and()
            .addFilterBefore(statelessJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(createSsoFilter(facebook(), facebookSuccessHandler(), "/login/facebook"), BasicAuthenticationFilter.class);
}

private OAuth2ClientAuthenticationProcessingFilter createSsoFilter(ClientResourceDetails clientDetails, AuthenticationSuccessHandler successHandler, String path) {
    OAuth2ClientAuthenticationProcessingFilter ssoFilter = new OAuth2ClientAuthenticationProcessingFilter(path);
    ssoFilter.setAllowSessionCreation(false);
    OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(clientDetails.getClient(), oAuth2ClientContext);
    ssoFilter.setRestTemplate(restTemplate);
    ssoFilter.setTokenServices(new UserInfoTokenServices(clientDetails.getResource().getUserInfoUri(), clientDetails.getClient().getClientId()));
    ssoFilter.setAuthenticationSuccessHandler(successHandler);
    return ssoFilter;
}

@Bean // handles the redirect to facebook
public FilterRegistrationBean oAuth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(filter);
    registration.setOrder(-100);
    return registration;
}

Many thanks for your help!

Kapp answered 15/12, 2015 at 13:4 Comment(2)
Did you figure it out? I structured my app a little bit different, but I have the same problem.Lizalizabeth
Did you find the way how to make it stateless?Coastward
J
1

This is an optional but recommended OAuth 2.0 feature. It enforced by the authorization server and as you assumed its purpose is to prevent csrf attacks.

Copied from the OAuth 2.0 RFC:

state
     RECOMMENDED.  An opaque value used by the client to maintain
     state between the request and callback.  The authorization
     server includes this value when redirecting the user-agent back
     to the client.  The parameter SHOULD be used for preventing
     cross-site request forgery as described in Section 10.12.

https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1

Juan answered 21/9, 2018 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.