Recently I started development on a new app to plan my meals. I wanted to make use of the rich collection of recipes I have accumlated on Pinterest. Thankfully there is an API that allows me to access my public pins. Unfortunately I was required to configure my application to use spring-security-oauth which is not so easy. Here I’d like to share my OAuth2 configuration for Pinterest with you, especially one problem that took me a while to figure out. I assume that you are familiar with the official OAuth2 standard (read it, its not that bad and really helpful) and have a general idea about Spring Boot.

Setting up an app

We first start by setting up our app on Pinterest. Create a developer account and register a new app. You can follow pinterests Getting started guide. When setting up your app make sure to add a redirect URI. In my case I have a controller listening on https://localhost:8443/boards. The moment you access that URL without a valid access token you’ll be redirect to Pinterest to authorize the app and you’ll be redirected back to the same URL you came from. In order for OAuth2 to work properly make sure to add that redirect URL to your app.

Furthermore it is important that you use https. Otherwise an access token might be accessed by an attacker. Even though we are running in development mode, it doesn’t hurt to use an SSL certificate. It’s just closer to production and I think that never hurts.

Setup SSL

I personally found it to be a bit tricky to setup Springt Boot with SSL. There are tutorials out there that explain the steps quite ok. But I had to combine a few tutorials to get it right. So let me have another try here.

Generate a new certificate

… by using a Java tool called keytool:

 keytool -genkey -keyalg RSA -keystore keystore.jks -storepass password -validity 360 -keysize 2048 

When asked about your first and last name put in localhost. The result of this command will be a new file with the name keystore.jks. Put that file into your resources folder so it ends up in your classpath (mine is in /src/main/resources).

Adjust the application settings

… to make use of the new certificate (read the reference documentation regarding the consequences of these settings):

/src/main/resources/application.properties:

server.port=8443
security.basic.enabled=false
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=password
server.ssl.key-password=password

Disable SSL verification for Unittests.

If you are running unittests and also integration tests you’ll notice that your tests are failing with a Java exception. That is due to the fact that you either configured something wrong, or your certifcate is simply not considered valid. That is because you generated it yourself. If you start your app and access it in the browser, he will tell you that the certificate is not trusted. It is easy to add a security exception in the browser, but not so easy in Java. While it is possible to add your custom certificate to the trusted certificates in Java, I prefer another simpler option (especially if you want to ease the setup for other users). While not the best solution I choose it for its simplicity.

It’s taken from: https://stackoverflow.com/questions/23504819/how-to-disable-ssl-certificate-checking-with-spring-resttemplate?noredirect=1&lq=1 . There is another way I found in the OAuth2 examples, where a HostnameVerifier is injected into the container that basically does the same thing, just with more configuration effort.

Create a new class with a static method to turn off the certificate validation and call it in your unittests (hint: @Before).

import javax.net.ssl.*;
import java.security.*;
import java.security.cert.X509Certificate;

/**
* Taken from: https://stackoverflow.com/questions/23504819/how-to-disable-ssl-certificate-checking-with-spring-resttemplate?noredirect=1&lq=1
* Helps with self signed certificates and unit tests.
*/
public final class SSLUtil {

    private static final TrustManager[] UNQUESTIONING_TRUST_MANAGER = new TrustManager[]{
            new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers(){
                    return null;
                }
                public void checkClientTrusted( X509Certificate[] certs, String authType ){}
                public void checkServerTrusted( X509Certificate[] certs, String authType ){}
            }
    };

    public  static void turnOffSslChecking() throws NoSuchAlgorithmException, KeyManagementException {
        // Install the all-trusting trust manager
        final SSLContext sc = SSLContext.getInstance("SSL");
        sc.init( null, UNQUESTIONING_TRUST_MANAGER, null );
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }

    public static void turnOnSslChecking() throws KeyManagementException, NoSuchAlgorithmException {
        // Return it to the initial state (discovered by reflection, now hardcoded)
        SSLContext.getInstance("SSL").init( null, null, null );
    }

    private SSLUtil(){
        throw new UnsupportedOperationException( "Do not instantiate libraries.");
    }
}

Setup the OAuth2 resource configuration

Now that we set up our SSL certificate and everything is up and running again, we have to configure our OAuth2 resource. This way we tell spring-security-oauth how our resource (Pinterest) expects the authorization to go down.

Note: Spring security will be enabled by default and you have to authenticate. That means you have to setup a user account to login with. Why? Because your access token will be tied to your session and it is not desired that one anonymous user has mutliple access tokens…

My security configuration with a user:

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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
*
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("test").password("test").roles("USER").build());
        return manager;
    }
}

The acutal resource configuration, this one took me a bit to figure out:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.common.AuthenticationScheme;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;

import java.util.Collections;

@Configuration
@EnableOAuth2Client
public class ResourceConfiguration {


    /**
    * Configure a OAuth2 interface for Pinterest.
    * @return The configuration.
    */
    @Bean
    public OAuth2ProtectedResourceDetails pinterestOAuth2Configuration() {
        AuthorizationCodeResourceDetails resourceDetails = new AuthorizationCodeResourceDetails();
        resourceDetails.setClientId("client id of your app");
        resourceDetails.setClientSecret("client secret of your app");
        resourceDetails.setAccessTokenUri("https://api.pinterest.com/v1/oauth/token");
        resourceDetails.setUserAuthorizationUri("https://api.pinterest.com/oauth/");
        resourceDetails.setClientAuthenticationScheme(AuthenticationScheme.query);
        resourceDetails.setScope(Collections.singletonList("read_public"));
        resourceDetails.setId("pinterest");
        resourceDetails.setAuthenticationScheme(AuthenticationScheme.query);

        return resourceDetails;
    }

    /**
    * Get a new rest template with OAuth2 support for Pinterest.
    * @param clientContext The context that stores information for the oauth2 requests.
    * @return A new rest template that can be used to make requests to protected resources.
    */
    @Bean
    public OAuth2RestTemplate pinterestRestTemplate(OAuth2ClientContext clientContext) {
        OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(pinterestOAuth2Configuration(), clientContext);
        return restTemplate;
    }
}

Most of that configuration is probably straightforward to you, after understanding the OAuth2 standard and the developer documentation. There was just one stumbling block for me:

resourceDetails.setClientAuthenticationScheme(AuthenticationScheme.query);

If you get a message along the way of “missing client_id”, it is because you are not sending your client id in the query, that is what you have to set specifically.