Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OAuth 2 with spring security and setting the State parameter in the redirect

I am using Spring boot with Spring security, with custom "Filter" Class calling to CIAM server with OAuth 2 authentication. I want to set explicitly or override the default setting so that I could set custom dynamic STATE parameter in the redirect URL that Spring Security prepares under the hood and sends the user to the CIAM server login page. This seamed trivial to me but it turned out to be far from that.

The goal is to add the custom STATE parameter of the OAuth2 redirect link so that after the authentication is finished and the CIAM server redirects me back to my page I take back the STATE parameter which is automatically included in the successful redirect link from the CIAM server.

The Security configuration

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, 
proxyTargetClass = true)
@EnableOAuth2Client
@Order(3)
public class OAuth2LoginWebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
CiamOAuth2ClientFilter oAuth2CiamClientFilter;

@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
    return new InMemoryUserDetailsManager();
}



@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/**/*.css", "/**/*.png", "/**/*.gif", "/**/*.jpg", "/h2-console/**", "/css/**",
            "/img/**", "/font-awesome/**", "/fonts/**", "/js/**", "/signout","/signout/**", "/health");
}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {

    httpSecurity
        .antMatcher("/**")
            .authorizeRequests()
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/backoffice/**").hasRole("ADMIN")
                .antMatchers("/api/**").hasRole("API")
                .antMatchers(/*"/", */"/login**", "/webjars/**", "/favicon.*", "/resources/**", 
                        "/auth/**", "/signin/**","css/**","js/**", "/signup/**", "/signout/", "/health", "/awsTest/login")
                    .permitAll()
                .anyRequest()
                    .authenticated()
                    .and()
                        .exceptionHandling()
                            .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/callback"))
                            .and()
                                .addFilterBefore(oAuth2CiamClientFilter.ciamFilter(), BasicAuthenticationFilter.class)
        .logout()
            .logoutUrl("/signout")
            .logoutSuccessUrl("/logout");
}

}

The custom filter class

@Configuration
public class CiamOAuth2ClientFilter {

@Autowired
AuthorizationCodeResourceDetails oauth2CiamResourceDetails;

@Autowired
CiamOAuth2ClientProperties oauth2CiamClientProperties;

@Autowired
OAuth2ClientContext oauth2ClientContext;

@Autowired
CiamPrincipalExtractor ciamPrincipalExtractor;

@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(filter);
    registration.setOrder(-100);
    registration.addInitParameter("test", "trrrrrrr");

    System.out.println("333333333333333333333333");
    System.out.println(registration);
    return registration;
}

public Filter ciamFilter() {

    System.out.println("postaeget");
    System.out.println(oauth2CiamClientProperties);
    System.out.println(" _-------------------------------: " + oauth2CiamClientProperties.getResource().getUserInfoUri());

    UserInfoTokenServices tokenService = new UserInfoTokenServices(oauth2CiamClientProperties.getResource().getUserInfoUri(), oauth2CiamResourceDetails.getClientId());
    OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(oauth2CiamResourceDetails, oauth2ClientContext);
    OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter("/login/callback");

    tokenService.setRestTemplate(restTemplate);
    tokenService.setPrincipalExtractor(ciamPrincipalExtractor);
    filter.setRestTemplate(restTemplate);
    filter.setTokenServices(tokenService);



    return filter;
}

}

Application yml settings file connected with the issue

security:
oauth2:
client:

  clientId: ...
  clientSecret: ....
  accessTokenUri: ...
  userAuthorizationUri: ...
  useCurrentUri: false
  preEstablishedRedirectUri: https://localhost/login/callback
  clientAuthenticationScheme: query
  authenticationScheme: header
  serverLogoutUrl: ..
  postLogoutRedirectUri: https://localhost/signout
  scope:
    - openid
    - profile
    - email
    - offline_access
  state: TEST
resource:
  userInfoUri: ...
  preferTokenInfo: ...
like image 939
Boris Gichev Avatar asked Nov 08 '22 08:11

Boris Gichev


1 Answers

In my case

I configure OAuth2ClientAuthenticationProcessingFilter somewhere in @Configuration:

private Filter ssoFilter() {
    OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter(API_LOGIN_FACEBOOK);
    OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
    AuthorizationCodeAccessTokenProvider authorizationCodeAccessTokenProviderWithUrl = new AuthorizationCodeAccessTokenProvider();
    authorizationCodeAccessTokenProviderWithUrl.setStateKeyGenerator(new StateKeyGeneratorWithRedirectUrl());
    facebookTemplate.setAccessTokenProvider(authorizationCodeAccessTokenProviderWithUrl);
    facebookFilter.setRestTemplate(facebookTemplate);
    UserInfoTokenServices tokenServices = new CheckedUserInfoTokenServices(
            facebookResource().getUserInfoUri(), facebook().getClientId(),
            facebookPrincipalExtractor, blogPreAuthenticationChecks(), blogPostAuthenticationChecks());
    tokenServices.setAuthoritiesExtractor(new FacebookAuthoritiesExtractor());
    tokenServices.setRestTemplate(facebookTemplate);
    facebookFilter.setTokenServices(tokenServices);
    facebookFilter.setAuthenticationSuccessHandler(new OAuth2AuthenticationSuccessHandler());
    return facebookFilter;
}

And you can access to current request in StateKeyGeneratorWithRedirectUrl with:

RequestContextHolder.getRequestAttributes()

so you can extract Referer header for example:

public class StateKeyGeneratorWithRedirectUrl extends DefaultStateKeyGenerator {
    private RandomValueStringGenerator generator = new RandomValueStringGenerator();

    @Override
    public String generateKey(OAuth2ProtectedResourceDetails resource) {
        HttpServletRequest currentHttpRequest = getCurrentHttpRequest();
        if (currentHttpRequest!=null){
            String referer = currentHttpRequest.getHeader("Referer");
            if (!StringUtils.isEmpty(referer)){
                return generator.generate()+","+referer;
            }
        }
        return generator.generate();
    }

    private static HttpServletRequest getCurrentHttpRequest(){
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            return ((ServletRequestAttributes)requestAttributes).getRequest();
        }
        return null;
    }
}

Next - read state from callback:

public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    public static final String DEFAULT = "/";

    @Override
    protected String determineTargetUrl(HttpServletRequest request,
                                        HttpServletResponse response) {

        UriComponents uriComponents = UriComponentsBuilder.newInstance()
            .query(request.getQueryString())
            .build();

        MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
        String stateEncoded = queryParams.getFirst("state");
        if (stateEncoded == null) {
            return DEFAULT;
        }
        String stateDecoded = URLDecoder.decode(stateEncoded, StandardCharsets.UTF_8);
        String[] split = stateDecoded.split(",");
        String redirect;
        if (split.length != 2){
            return DEFAULT;
        } else {
            return split[1];
        }
    }
}
like image 174
Nikita Konev Avatar answered Nov 15 '22 13:11

Nikita Konev