OAuth2 SSO for multiple resource servers with spring boot and jHipster

Miciurash picture Miciurash · Mar 4, 2015 · Viewed 8.7k times · Source

So, I have an oAuth2 app which is jHipster app (using mongodb). I want to connect 3 resource apps to that app but all of them should share the same user base, so that the users should be able to login only once.

Is there a way to configure multiple resources in Spring Boot with jHipster so that it won't be as a separate client that would need username and password before accessing the resource?

And also how can I specify user role for each resource server?

All of the app are based on spring-boot.

The diagram below is a simplistic view of what I'm trying to accomplish.

enter image description here

So the OAuth2 app has has the Authorization Server configuration:

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
        AuthorizationServerConfigurerAdapter implements EnvironmentAware {

    private static final String ENV_OAUTH = "authentication.oauth.";
    private static final String PROP_CLIENTID = "clientid";
    private static final String PROP_SECRET = "secret";
    private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

    private RelaxedPropertyResolver propertyResolver;

    @Inject
    private OAuth2AccessTokenRepository oAuth2AccessTokenRepository;

    @Inject
    private OAuth2RefreshTokenRepository oAuth2RefreshTokenRepository;

    @Bean
    public TokenStore tokenStore() {
        return new MongoDBTokenStore(oAuth2AccessTokenRepository,
                oAuth2RefreshTokenRepository);
    }

    @Inject
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {

        endpoints.tokenStore(tokenStore()).authenticationManager(
                authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
                .withClient("app-auth")
                .scopes("read", "write")
                .authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
                .authorizedGrantTypes("password", "refresh_token")
                .secret(propertyResolver.getProperty(PROP_SECRET))
                .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))

                .and()

                .withClient("app-A")
                .scopes("read", "write")
                .authorities(AuthoritiesConstants.ADMIN,AuthoritiesConstants.USER)
                .authorizedGrantTypes("password", "refresh_token")
                .secret(propertyResolver.getProperty(PROP_SECRET))
                .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))

                .and()

                .withClient("app-A")
                .scopes("read", "write")
                .authorities(AuthoritiesConstants.ADMIN,AuthoritiesConstants.USER)
                .authorizedGrantTypes("password", "refresh_token")
                .secret(propertyResolver.getProperty(PROP_SECRET))
                .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))

                .and()

                .withClient("app-C")
                .scopes("read", "write")
                .authorities(AuthoritiesConstants.ADMIN,AuthoritiesConstants.USER)
                .authorizedGrantTypes("password", "refresh_token")
                .secret(propertyResolver.getProperty(PROP_SECRET))
                .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));


    }

    @Override
    public void setEnvironment(Environment environment) {
        this.propertyResolver = new RelaxedPropertyResolver(environment,
                ENV_OAUTH);
    }
}

As well the OAuth2 app has has the Resource Server configuration:

@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends
        ResourceServerConfigurerAdapter {

@Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;

@Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;

@Override
public void configure(HttpSecurity http) throws Exception {
    http.exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint)
            .and()
            .logout()
            .logoutUrl("/api/logout")
            .logoutSuccessHandler(ajaxLogoutSuccessHandler)
            .and()
            .csrf()
            .requireCsrfProtectionMatcher(
                    new AntPathRequestMatcher("/oauth/authorize"))
            .disable().headers().frameOptions().disable()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().authorizeRequests().antMatchers("/api/authenticate")
            .permitAll().antMatchers("/api/register").permitAll()
            .antMatchers("/api/logs/**")
            .hasAnyAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/api/**").authenticated()
            .antMatchers("/metrics/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/health/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/trace/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/dump/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/shutdown/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/beans/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/configprops/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/info/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/autoconfig/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/env/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/trace/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/api-docs/**")
            .hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/protected/**").authenticated();
        }
    }

And the Resource Server on App A (which is almost the same for B and C):

@Configuration
@EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {

@Override
public void configure(HttpSecurity http) throws Exception {
    http.requestMatchers().antMatchers("/api/**")
            .and()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
            .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
            .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
            .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
            .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')");
}

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("app-A");
    }

}

Answer

kinjelom picture kinjelom · Mar 9, 2016

@EnableResourceServer annotation by default protects all your resources (except resources explicitly ignored or exposed by the AuthorizationEndpoint if there is an Authorization Server in the same application).

If you want to set up multiple Resource Servers in the same app you can do it in this way:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

import java.util.Collections;
import java.util.List;

@Configuration
public class ResourceServersConfig {

    @Bean
    protected ResourceServerConfiguration adminResources() {
        ResourceServerConfiguration resource = new ResourceServerConfiguration() {
            public void setConfigurers(List<ResourceServerConfigurer> configurers) {
                super.setConfigurers(configurers);
            }
        };
        resource.setConfigurers(Collections.<ResourceServerConfigurer>singletonList(new ResourceServerConfigurerAdapter() {
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.resourceId("admin-resources");
            }

            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.antMatcher("/rest/admin/**").authorizeRequests().anyRequest()
                        .access("#oauth2.hasScope('administration') and #oauth2.clientHasRole('admin')");
            }
        }));
        resource.setOrder(3);
        return resource;
    }

    @Bean
    protected ResourceServerConfiguration userResources() {
        ResourceServerConfiguration resource = new ResourceServerConfiguration() {
            public void setConfigurers(List<ResourceServerConfigurer> configurers) {
                super.setConfigurers(configurers);
            }
        };
        resource.setConfigurers(Collections.<ResourceServerConfigurer>singletonList(new ResourceServerConfigurerAdapter() {
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.resourceId("user-resources");
            }

            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.antMatcher("/rest/user/**").authorizeRequests().anyRequest()
                        .access("#oauth2.hasAnyScope('offer','order') and #oauth2.clientHasRole('user')");
            }
        }));
        resource.setOrder(4);
        return resource;
    }

}

Please take a look at Dave Syer's example.