Securing GWT Apps using Spring Oauth & Spring Social

General purpose :

Provides a spring security configuration for any Oauth2 providers in our projects. For that I found many good starting points on the web and I merged them into a simple GWTP project forked from an existing repository (https://github.com/imrabti/gwtp-spring-security) which helped me to achieve this goal with minor changes.

This a solution for GWT developers who want to provide a session management using usual (Google, Facebook, …) or custom Oauth2 provider in their applications.

Dependency management :

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>${oauth.version}</version>
</dependency>
...
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring-security.version}</version>
</dependency>

<!-- Spring Social -->
<dependency>
    <groupId>org.springframework.social</groupId>
    <artifactId>spring-social-security</artifactId>
    <version>${spring-social.version}</version>
</dependency>

<!-- Persistance dependencies -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.1</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.186</version>
</dependency>

2 – Custom provider definition :

I added some classes here as other embedded Social provider to define my own provider

– Provider definition

public interface Corporate {
    CorporatePofile getUserProfile();

    void updateStatus(String message);
}

– Oauth2 provider service

public class CorporateServiceProvider extends AbstractOAuth2ServiceProvider<Corporate> {
    public CorporateServiceProvider(OAuth2Operations oauth2Operations) {
        super(oauth2Operations);
    }

    @Override
    public Corporate getApi(String s) {
        return new CorporateServiceTemplate(s);
    }
}

– Provider service template

public class CorporateServiceTemplate extends AbstractOAuth2ApiBinding implements Corporate {
    private final static String corporateProfileURL = "http://localhost:8080/oauthprotoserver/profile/";
    private String accessToken;

    CorporateServiceTemplate(String accessToken) {
        super(accessToken);
        this.accessToken = accessToken;
    }

    @Override
    public CorporatePofile getUserProfile() {
        try {
            ResponseEntity<String> content = getRestTemplate().exchange(URI.create(corporateProfileURL + accessToken),
                    HttpMethod.GET, null, String.class);

            // TODO Retrieve a Corporate JSON user build a serialized object
            return new CorporatePofile(content.getBody(),"test@test.com","client1","employee","foo","bar");

        }  catch (HttpClientErrorException e2) {
            throw new OAuth2Exception(e2.getMessage());
        }
    }

    @Override
    public void updateStatus(String message) {
      return;
    }
}

And to perform the implicit SignUp and build a valid session with SocialUserDetails

РA SimpleConnectionSignUp which requires a JDBC user connection repository

public final class SimpleConnectionSignUp implements ConnectionSignUp {
    private final AtomicLong userIdSequence = new AtomicLong();

    public String execute(Connection<?> connection) {
        return Long.toString(userIdSequence.incrementAndGet());
    }
}

– A SimpleSignInAdapter which will help to store UserDetails into Security Context

public class SimpleSignInAdapter implements SignInAdapter {
    private static final Logger log = LoggerFactory.getLogger(SimpleSignInAdapter.class);
    private final RequestCache requestCache;
    private final NuvolaCasDetailsService userService;

    @Autowired
    public SimpleSignInAdapter(RequestCache requestCache,
                               NuvolaCasDetailsService userService) {
        this.requestCache = requestCache;
        this.userService = userService;
    }

    @Override
    public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
        SocialUserDetails user = userService.loadUserByUserId(connection.getDisplayName());
        SignInUtils.signin(user);
        // Return Application home page
        return "/";
    }

This will help us into the final step add a simple login page to post a SignIn request catched by the ProviderSignInController (Framework controller)

...
<div align="center">
    <form id="casForm" name="casForm" action="/api/signin/corporate" method="POST" >
        <p><input name="login" value="Connexion via Portail" type="submit"></p>
    </form>
</div>

The “corporate” at the end of the uri (/api/signin/corporate) is our providerId which is a Path parameter helping the controller to build a valid OAuth2ConnectionFactory.

3 – Spring security configuration :

...
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
    return super.userDetailsServiceBean();
}

@Bean
OAuth2Template restTemplate() {
    return new OAuth2Template(corporateProvider().getClientId(),
            corporateProvider().getClientSecret(),
            corporateProvider().getUserAuthorizationUri(),
            corporateProvider().getAccessTokenUri());
}

@Bean
@Scope(value="singleton", proxyMode= ScopedProxyMode.INTERFACES)
public SocialAuthenticationServiceLocator socialAuthenticationServiceLocator() {
    SocialAuthenticationServiceRegistry registry = new SocialAuthenticationServiceRegistry();
    registry.addConnectionFactory(new CorporateConnectionFactory(restTemplate()));

    return registry;
}

@Bean
@Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES)
public UsersConnectionRepository usersConnectionRepository() {
    JdbcUsersConnectionRepository connectionRepository =
            new JdbcUsersConnectionRepository(dataSource, socialAuthenticationServiceLocator(), Encryptors.noOpText());
    connectionRepository.setConnectionSignUp(new SimpleConnectionSignUp());
    return connectionRepository;
}

@Bean
public ProviderSignInController providerSignInController() {
    ProviderSignInController controller = new ProviderSignInController(socialAuthenticationServiceLocator(),
            usersConnectionRepository(), new SimpleSignInAdapter(new HttpSessionRequestCache(),
            userDetailsService));
    return controller;
}

@Bean
AuthorizationCodeResourceDetails corporateProvider() {
    AuthorizationCodeResourceDetails codeResourceDetails = new AuthorizationCodeResourceDetails();
    codeResourceDetails.setClientId(appClientId);
    codeResourceDetails.setClientSecret("appClientSecret");
    codeResourceDetails.setGrantType("authorization_code");
    codeResourceDetails.setTokenName("bearer");
    codeResourceDetails.setAuthenticationScheme(AuthenticationScheme.header);
    codeResourceDetails.setClientAuthenticationScheme(AuthenticationScheme.header);
    codeResourceDetails.setAccessTokenUri("accessTokenUri");
    codeResourceDetails.setUserAuthorizationUri("userAuthorizationUri");
    codeResourceDetails.setUseCurrentUri(true);
    codeResourceDetails.setPreEstablishedRedirectUri("http://localhost:8090/");
    return codeResourceDetails;
}

@Bean
AuthorizationCodeResourceDetails googleProvider() {
    return null;
}

@Bean
AuthorizationCodeResourceDetails facebookProvider() {
    return null;
}

@Override
protected AuthenticationManager authenticationManager() throws Exception {
    return super.authenticationManager();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .formLogin().loginPage("/access/login")
            .permitAll()
            .successHandler(authSuccessHandler)
            .failureHandler(authFailureHandler)
            .and()
            .logout().logoutUrl("/access/logout")
            .invalidateHttpSession(true)
            .logoutSuccessHandler(logoutSuccessHandler)
            .and()
            .apply(new SpringSocialConfigurer()
                    .postLoginUrl("/home")
                    .alwaysUsePostLoginUrl(true));

    http.authorizeRequests()
            .antMatchers(LOGIN_PATH).permitAll()
            .anyRequest().authenticated();
}
...

The complete source code is available here : https://github.com/gael-jurin/gwtp-spring-security I hope this will help many developers to customize Oauth2 authorization and authentication process via providers. This implementation supports the Oauth2 authorization code flow. Thanks to Spring community for this huge work

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s