Monthly Archives: June 2018

Social login with Spring Boot

In this post, I will show how to use social login in a Spring Boot application. So we build an application, but we use a form-based login which is the most basic and most insecure authentication mechanism out there. How do we get over this hunch and use the latest more secure mechanism?

Social login – Tada.

Yes, with an increasing number of social networks, it has become increasingly popular and easier to build an OAuth based login mechanism using social networks. In other words, spring boot offers a solution with a social login plugin and in this post, we will show how to use social login to authenticate your users.

What will you need

  • IntelliJ
  • Java 8
  • Twitter/Facebook/Google/Linkedin/Github accounts
  • Spring Boot
  • Gradle

Spring Social Core

Spring offers a spring-social-core project that contains APIs to connect to user’s social accounts. Nevertheless, this library includes a connect framework that offers a solution to manage connections with social service providers. It offers support for OAuth1a and OAuth2. The simplest way to understand this library is that you create a connection factory for each social provider. A connection factory locator finds a factory to create a Sign In Provider. I will provide more details as we go along in implementing this module.

 

Create a Social Login Gradle Project

If you haven’t noticed from my blog posts, but I have switched from eclipse to IntelliJ for programming editor. Intellij is just smarter and easy to write code editor. So first create a Gradle project for spring boot. (Side note – if you are using IntelliJ ultimate edition, it offers a feature to create spring project.) We will be using the latest version of Spring Boot (2.0.3.RELEASE) to build this project.

The gradle file will look like below:

buildscript {
  ext {
    springBootVersion = '2.0.3.RELEASE'
  }
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
  }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.betterjavacode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
  mavenCentral()
  maven {
    url 'https://repo.spring.io/libs-milestone'
  }
}


dependencies {
  compile('org.springframework.boot:spring-boot-starter-thymeleaf')
  compile("org.springframework.boot:spring-boot-starter-web")
  compile("org.springframework.social:spring-social-security:1.1.6.RELEASE")
  compile("org.springframework.social:spring-social-config:1.1.6.RELEASE")
  compile("org.springframework.social:spring-social-core:1.1.6.RELEASE")
  compile('org.springframework.boot:spring-boot-starter-security')
  compile('org.springframework.boot:spring-boot-starter-data-jpa')
  compile("com.fasterxml.jackson.core:jackson-databind:2.9.6")
  compile('mysql:mysql-connector-java:5.1.6')
  compile("org.springframework.social:spring-social-twitter:1.1.2.RELEASE")
  compile("org.springframework.social:spring-social-facebook:2.0.3.RELEASE")
  compile("org.springframework.social:spring-social-google:1.0.0.RELEASE")
  compile("org.springframework.social:spring-social-github:1.0.0.M4")
  compile("org.springframework.social:spring-social-linkedin:1.0.2.RELEASE")
  testCompile('org.springframework.boot:spring-boot-starter-test')
}

I will explain each dependency added in Gradle file as we go along.

Create an entity class

We will be using a simple entity class for User with just one field name.  This will look like below:

@JsonIgnoreProperties(ignoreUnknown = true)
public class User
{
    public String name;

    public String getName() 
    {
        return name;
    }
    public void setName(String name) 
    {
        this.name = name;
    }
}

Create A Social Configuration to adapt Spring Social library

Firstly, we will implement an interface SocialConfigurer that Spring social library offers. For instance, as part of this implementation, we will create connection factories for different social service providers. Also for this module, we are using InMemoryUsersConnectionRepository. You can always implement a JDBC based database user connection repository. This class will look like below:

package com.betterjavacode.reusablesociallogin;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.social.UserIdSource;
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurer;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.mem.InMemoryUsersConnectionRepository;
import org.springframework.social.connect.support.ConnectionFactoryRegistry;
import org.springframework.social.connect.web.ProviderSignInController;
import org.springframework.social.facebook.connect.FacebookConnectionFactory;
import org.springframework.social.github.connect.GitHubConnectionFactory;
import org.springframework.social.google.connect.GoogleConnectionFactory;
import org.springframework.social.linkedin.connect.LinkedInConnectionFactory;
import org.springframework.social.security.AuthenticationNameUserIdSource;
import org.springframework.social.twitter.api.Twitter;
import org.springframework.social.twitter.api.impl.TwitterTemplate;
import org.springframework.social.twitter.connect.TwitterConnectionFactory;

import javax.inject.Inject;
import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:application.properties")
@EnableSocial
public class SocialConfig implements SocialConfigurer
{

    @Autowired
    private DataSource dataSource;

    @Override
    public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment)
    {
        connectionFactoryConfigurer.addConnectionFactory(new TwitterConnectionFactory(environment.getProperty("spring.social.twitter.consumerKey"), environment.getProperty("spring.social.twitter.consumerSecret")));
        connectionFactoryConfigurer.addConnectionFactory(new FacebookConnectionFactory(environment.getProperty("spring.social.facebook.appId"),environment.getProperty("spring.social.facebook.appSecret")));
        GoogleConnectionFactory googleConnectionFactory = new GoogleConnectionFactory(environment.getProperty("spring.social.google.appId"),environment.getProperty("spring.social.google.appSecret"));
        googleConnectionFactory.setScope("profile");
        connectionFactoryConfigurer.addConnectionFactory(googleConnectionFactory);
        connectionFactoryConfigurer.addConnectionFactory(new GitHubConnectionFactory(environment.getProperty("spring.social.github.appId"), environment.getProperty("spring.social.github.appSecret")));
        connectionFactoryConfigurer.addConnectionFactory(new LinkedInConnectionFactory(environment.getProperty("spring.social.linkedin.appId"), environment.getProperty("spring.social.linkedin.appSecret")));
    }

    @Override
    public UserIdSource getUserIdSource()
    {
        return new UserIdSource() {
            @Override
            public String getUserId() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication == null) {
                    throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
                }
                return authentication.getName();
            }
        };
    }

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator)
    {
        InMemoryUsersConnectionRepository usersConnectionRepository = new InMemoryUsersConnectionRepository(
                connectionFactoryLocator);
        return usersConnectionRepository;
    }

}

As you see in this class, I am referring to application.properties . The application.properties will look like below:

spring.social.twitter.consumerKey=[Twitter consumer key]
spring.social.twitter.consumerSecret=[Twitter consumer secret]
spring.social.facebook.appId=[Facebook client id]
spring.social.facebook.appSecret=[Facebook client secret]
spring.social.google.appId=[Google client id]
spring.social.google.appSecret=[Google client secret]
spring.social.github.appId=[Github client id]
spring.social.github.appSecret=[Github client secret]
spring.social.linkedin.appId=[Linkedin client id]
spring.social.linkedin.appSecret=[Linkedin client secret]
server.port = 8448

In other words, to get clientid and clientsecret , you will have to register your application with each social service provider. We will not be covering that in this post.

Create a spring web security configuration

In this class, we will extend websecurityconfigureradapter and configure HTTP security as part of spring security implementation. We also add a bean to create Sign In Providers which are part of Spring Social. In addition, we will implement this Sign In Provider to provide a facility to users to sign in with their social provider.

package com.betterjavacode.reusablesociallogin;


import com.betterjavacode.reusablesociallogin.social.SocialConnectionSignup;
import com.betterjavacode.reusablesociallogin.social.SocialSignInAdapter;
import org.springframework.beans.factory.annotation.Autowired;
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.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.mem.InMemoryUsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInController;
import org.springframework.social.security.SpringSocialConfigurer;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{

    @Autowired
    private ConnectionFactoryLocator connectionFactoryLocator;

    @Autowired
    private UsersConnectionRepository usersConnectionRepository;

    @Autowired
    private SocialConnectionSignup socialConnectionSignup;

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/","/socialloginhome","/signin/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll();
    }

    @Bean
    public ProviderSignInController providerSignInController()
    {
        ((InMemoryUsersConnectionRepository) usersConnectionRepository)
                .setConnectionSignUp(socialConnectionSignup);

        return new ProviderSignInController(
                connectionFactoryLocator,
                usersConnectionRepository,
                new SocialSignInAdapter());
    }
}

As you see in this class, we have a bean ProviderSignInController which will use SocialSignInAdapter.

Implement a Sign In Adapter

Above all, this is the heart of our implementation where authentication will take place and the user will be assigned a role to access the application. The user will be redirected to the application if the user successfully authenticates. This class will look like below:

package com.betterjavacode.reusablesociallogin.social;

import com.betterjavacode.reusablesociallogin.util.ConnectionHelper;
import com.betterjavacode.reusablesociallogin.util.ConnectionType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.web.SignInAdapter;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.NativeWebRequest;

import java.util.ArrayList;
import java.util.List;

@Service
public class SocialSignInAdapter implements SignInAdapter
{
    @Override
    public String signIn(String userId, Connection<?> connection, NativeWebRequest request)
    {
        Authentication authentication = getAuthentication(userId, connection);

        SecurityContextHolder.getContext().setAuthentication(authentication);

        return "/socialloginsuccess";
    }

    private Authentication getAuthentication(String localUserId, Connection<?> connection)
    {
        List<GrantedAuthority> roles = getRoles(connection);

        String password = null;

        Authentication authentication = new UsernamePasswordAuthenticationToken(localUserId, password, roles);

        return authentication;
    }

    private List<GrantedAuthority> getRoles(Connection<?> connection)
    {
        List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();

        ConnectionType type = ConnectionHelper.getConnectionType(connection);

        String role = type.toString();

        roles.add(new SimpleGrantedAuthority(role));

        return roles;
    }
}

As you see in getAuthentication, we pass userId and roles for token-based authentication.

If the user has not signed up with a social provider before, he will be asked to sign up and will be redirected to the application after the first time sign up.

package com.betterjavacode.reusablesociallogin.social;

import com.betterjavacode.reusablesociallogin.entity.User;
import com.betterjavacode.reusablesociallogin.util.UserHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.stereotype.Service;

@Service
public class SocialConnectionSignup implements ConnectionSignUp
{
    @Autowired
    UserHelper userHelper;

    @Override
    public String execute(Connection<?> connection)
    {
        User user = userHelper.getUser(connection);

        return user.getName();
    }
}

As you see in this class, we have Autowired a userHelper class, this class will have an implementation to get user details from each social provider.

Therefore, this UserHelper will look like below:

package com.betterjavacode.reusablesociallogin.util;

import com.betterjavacode.reusablesociallogin.entity.User;

import org.springframework.social.connect.Connection;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.social.github.api.GitHub;
import org.springframework.social.google.api.Google;
import org.springframework.social.linkedin.api.LinkedIn;
import org.springframework.social.twitter.api.Twitter;
import org.springframework.stereotype.Component;

@Component
public class UserHelper
{
    public User getUser(Connection<?> connection)
    {
        User user = null;

        //get the connection type
        ConnectionType type = ConnectionHelper.getConnectionType(connection);

        if (type.equals(ConnectionType.TWITTER)) {
            user = getTwitterUser(connection);
        } else if (type.equals(ConnectionType.FACEBOOK)) {
            user = getFacebookUser(connection);
        } else if (type.equals(ConnectionType.GOOGLE)) {
            user = getGoogleUser(connection);
        } else if (type.equals(ConnectionType.GITHUB)) {
            user = getGithubUser(connection);
        } else if (type.equals(ConnectionType.LINKEDIN)){
            user = getLinkedInUser(connection);
        }

        return user;
    }

    private User getTwitterUser(Connection<?> connection)
    {
        User user = new User();
        Twitter twitterApi = (Twitter)connection.getApi();

        String name = twitterApi.userOperations().getUserProfile().getName();

        user.setName(name);

        return user;
    }

    private User getFacebookUser(Connection<?> connection)
    {
        User user = new User();
        Facebook facebookApi = (Facebook)connection.getApi();
        String [] fields = { "name" };
        User userProfile = facebookApi.fetchObject("me", User.class, fields);

        String name = userProfile.getName();

        user.setName(name);

        return user;
    }

    private User getGoogleUser(Connection<?> connection)
    {
        User user = new User();
        Google googleApi = (Google) connection.getApi();
        String name = googleApi.plusOperations().getGoogleProfile().getDisplayName();
        user.setName(name);
        return user;
    }

    private User getGithubUser(Connection<?> connection)
    {
        User user = new User();
        GitHub githubApi = (GitHub) connection.getApi();
        String name = githubApi.userOperations().getUserProfile().getName();
        user.setName(name);
        return user;
    }

    private User getLinkedInUser(Connection<?> connection)
    {
        User user = new User();
        LinkedIn linkedInApi = (LinkedIn) connection.getApi();
        String name = linkedInApi.profileOperations().getUserProfile().getFirstName();
        user.setName(name);
        return user;
    }
}

Implementing a controller and views

Similarly, the last piece in this puzzle is to add a controller and corresponding views so when the user accesses the application, the user will be challenged for authentication.

However, we will add a login controller which will have three views for login, sociallogin and socialloginsuccess . This will look like below:

@Controller
public class LoginController
{
    @RequestMapping(value="/login", method= RequestMethod.GET)
    public String login(Model model)
    {
        return "login";
    }

    @RequestMapping(value ="/socialloginhome", method = RequestMethod.GET)
    public String socialloginhome(Model model)
    {
        return "socialloginhome";
    }

    @RequestMapping(value="/socialloginsuccess", method= RequestMethod.GET)
    public String socialloginsuccess(Model model)
    {
        return "socialloginsuccess";
    }
}

 

Running the application

Once I build the application and run it, the flow will look like below:

Spring Boot Application - Social Login

You click on hereit will take you to social login screen as below:

Choose social login option - Spring Boot Application

I will choose Facebook and server-side code will redirect me to Facebook login screen. Once I enter my credentials, Facebook will redirect back me to my application as below:

Social Login Spring Boot

Hence, we showed successful social authentication. Lastly, social login is part of any saas application you are building.

Conclusion

In conclusion, we showed how to create a social login module using the Spring boot social feature. Moreover, the code for this will be available to download here.

References

  1. Spring Social Overview – Spring Social
  2. Spring Social development – Spring Social Development
  3. Spring Social tutorial – Spring Social Tutorial

 

How to add SSLContext with TLSv1.2 in Android Kitkat

coding

Recently, I came across an issue where a server was not responding in my android application running in Kitkat version of Android. Yes, Kitkat is old, and why one should use it when we can always upgrade to a higher version of Android. We can argue, find pros and cons, but that’s not the point. If we have the technology, it should be more flexible and sometimes, a customer will not be able to go for a higher version of Android. When technology plays with time, certain things are not in control. So if websites had made TLSv1.2 compulsory when Android Kitkat was released, Google had no choice but to release a solution. But that wasn’t the case.

Back to the present time, my Android application is trying to contact a server that has TLSv1.2 enabled. In my Android application, I used Android provided DefaultHttpClient.

So the issue is “How do we add a SSLContext in this DefaultHttpClient“?

Solution –

Create a HTTP Client Socket Factory –

We will build a socket factory that will implement LayeredSocketFactory like below:

public class TlsSniSocketFactory implements LayeredSocketFactory {

  private final static HostnameVerifier hostnameVerifier = new StrictHostnameVerifier();

  private final boolean acceptAllCertificates;
  private final String selfSignedCertificateKey;

  public TlsSniSocketFactory() 
        {
    this.acceptAllCertificates = false;
    this.selfSignedCertificateKey = null;
  }

  public TlsSniSocketFactory(String certKey) 
        {
    this.acceptAllCertificates = false;
    this.selfSignedCertificateKey = certKey;
  }

  public TlsSniSocketFactory(boolean acceptAllCertificates) 
        {
    this.acceptAllCertificates = acceptAllCertificates;
    this.selfSignedCertificateKey = null;
  }

  // Plain TCP/IP (layer below TLS)

  @Override
  public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort,
                HttpParams params) throws IOException {
    return null;
  }

  @Override
  public Socket createSocket() throws IOException {
    return null;
  }

  @Override
  public boolean isSecure(Socket s) throws IllegalArgumentException {
    if (s instanceof SSLSocket) {
      return s.isConnected();
    }
    return false;
  }

  // TLS layer

  @Override 
  public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException 
        {
    if (autoClose) 
                {
      plainSocket.close();
    }

    SSLCertificateSocketFactory sslSocketFactory =
        (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);

    // For self-signed certificates use a custom trust manager
    if (acceptAllCertificates) {
      sslSocketFactory.setTrustManagers(new TrustManager[]{new IgnoreSSLTrustManager()});
    } else if (selfSignedCertificateKey != null) {
      sslSocketFactory.setTrustManagers(new TrustManager[]{new SelfSignedTrustManager(selfSignedCertificateKey)});
    }

    // create and connect SSL socket, but don't do hostname/certificate verification yet
    SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(InetAddress.getByName(host), port);

    // enable TLSv1.1/1.2 if available
    // ssl.setEnabledProtocols(ssl.getSupportedProtocols());
                // this can be hard coded too
                ssl.setEnabledProtocols(new String[] {"TLSv1.2"});

    // set up SNI before the handshake
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      sslSocketFactory.setHostname(ssl, host);
    } else {
      try {
        java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
        setHostnameMethod.invoke(ssl, host);
      } catch (Exception e) {
        Log.d(TlsSniSocketFactory.class.getSimpleName(), "SNI not usable: " + e);
      }
    }

    // verify hostname and certificate
    SSLSession session = ssl.getSession();
    if (!(acceptAllCertificates || selfSignedCertificateKey != null) && !hostnameVerifier.verify(host, session)) {
      throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
    }

    return ssl;
  }

}

Register a HTTPS Scheme

We will register a scheme that will use our custom socket factory.

SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("https", new TlsSniSocketFactory(),443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(httpparams, schemeRegistry);
DefaultHttpClient defaultHttpClient = new DefaultHttpClient(ccm, httpparams);

Now if use defaultHttpClient to call a GET or POST request, we should be able to connect to a server that is enabled with TLSv1.2.

Conclusion

In this post, we showed how to use DefaultHttpClient in Android Kitkat with TLSv1.2. If you enjoyed this post, subscribe to my blog here.

References

  1. TLS Socket Factory
  2. Default HTTP Client