Tag Archives: #springboot

Json Web Token: How to Secure Spring Boot REST API

In this post, I will show how to secure your spring boot based REST API. It has been more of a trend to secure REST APIs to avoid any unnecessary calls to public APIs. We will be using some Spring boot features for Spring security along with JSON WebTokens for authorization. You can learn more about basic authentication here.

User flow in this case is

  1. User logs in
  2. We validate user credentials
  3. A token is sent back to user agent.
  4. User tries to access a protected resource.
  5. User sends JWT when accessing the protected resource. We validate JWT.
  6. If JWT is valid, we allow the user to access the resource.

JSON WebTokens, known as JWTs are used for forming authorization for users. This helps us to build secure APIs and it is also easy to scale. During authentication, a JSON web token is returned. Whenever the user wants to access a protected resource, the browser must send JWTs in the Authorization header along with the request. One thing to understand here is that it is a good security practice to secure REST API.

Basically, we will show

  1. Verify JSON WebToken
  2. Validate the signature
  3. Check the client permissions

What you will need?

  1. Java 8,
  2. MySQL Database
  3. IntelliJ Editor
  4. Gradle

Note – This won’t be a full-fledged app, but REST APIs based on Spring Boot, and Spring security.

Spring Boot Based REST API

Since I have already shown this before on my blog, I won’t be creating any new APIs. I will be securing REST API for company that I created in this blog post REST API. This API also includes caching. A user will try to access /cachedemo/v1/companies/ and since APIs are protected, he will get a response like below:

Secure REST API - Forbidden

Response from protected API

Now we will implement how to protect this API and how to access it.

Adding User and User Registration

Since we want to add authorization for APIs, we will need where the user is able to log in and send credentials. These credentials will be validated and a token will be generated. This token then will be transmitted in a request to an API call. The token will be validated in the Spring security authorization filter that we will add. If a valid token, the user will be able to access the API.

Create a user model


package com.betterjavacode.models;

import javax.persistence.*;
import java.io.Serializable;

@Entity(name = "User")
@Table(name = "user")
public class User implements Serializable
{
    public User()
    {

    }

    @Id
    @GeneratedValue(strategy =  GenerationType.IDENTITY)
    private long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    public long getId()
    {
        return id;
    }

    public void setId(long id)
    {
        this.id = id;
    }

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public String getPassword()
    {
        return password;
    }

    public void setPassword(String password)
    {
        this.password = password;
    }
}

We will add a controller where a user can register with its details for username and password.


package com.betterjavacode.resources;

import com.betterjavacode.models.User;
import com.betterjavacode.repositories.UserRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/cachedemo/v1/users")
public class UserController
{
    private UserRepository userRepository;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public UserController(UserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder)
    {
        this.userRepository = userRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @PostMapping("/signup")
    public void signUp(@RequestBody User user)
    {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        userRepository.save(user);
    }

}

Now when we POST a request to /cachedemo/v1/users/signup , a user will be saved in the database. Password for the user will be saved in encrypted format as we are using BCryptPasswordEncoder. We will show how a user can log in to create a token.

User Login

To handle user login, we will add an AuthenticationFilter which will get added in FilterChain and Spring boot will handle the execution of it appropriately. This filter will look like below:


package com.betterjavacode.SpringAppCache;


import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter
{
    private AuthenticationManager authenticationManager;

    public AuthenticationFilter(AuthenticationManager authenticationManager)
    {
        this.authenticationManager = authenticationManager;
        setFilterProcessesUrl("/login");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
    {
        try
        {
            com.betterjavacode.models.User creds = new ObjectMapper().readValue(request.getInputStream(), com.betterjavacode .models.User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword(),new ArrayList<>()));
        }
        catch(IOException e)
        {
            throw new RuntimeException("Could not read request" + e);
        }
    }

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain, Authentication authentication)
    {
        String token = Jwts.builder()
                .setSubject(((User) authentication.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + 864_000_000))
                .signWith(SignatureAlgorithm.HS512, "SecretKeyToGenJWTs".getBytes())
                .compact();
        response.addHeader("Authorization","Bearer " + token);
    }
}

Basically, a user will send credentials in a request to URL ending with /login . This filter will help to authenticate the user, if there is successful authentication, a Token will be added in response header with the key Authorization.

Token Validation and Authorization

We add another filter AuthorizationFilter to validate the token that we passed through AuthenticationFilter earlier. This filter will look like below:


package com.betterjavacode.SpringAppCache;

import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;


public class AuthorizationFilter extends BasicAuthenticationFilter
{
    public AuthorizationFilter(AuthenticationManager authenticationManager)
    {
        super(authenticationManager);
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException
    {
        String header = request.getHeader("Authorization");
        if(header == null || !header.startsWith("Bearer"))
        {
            filterChain.doFilter(request,response);
            return;
        }

        UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request)
    {
        String token = request.getHeader("Authorization");
        if(token != null)
        {
            String user = Jwts.parser().setSigningKey("SecretKeyToGenJWTs".getBytes())
                    .parseClaimsJws(token.replace("Bearer",""))
                    .getBody()
                    .getSubject();
            if(user != null)
            {
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }
            return null;
        }
        return null;
    }
}

If the validation of the token is successful, the application returns a user and assigns it to a security context.

To enable Spring security, we will add a new class WebSecurityConfiguration with annotation @EnableWebSecurity. This class will extend the standard WebSecurityConfigurerAdapter . In this class, we will restrict our APIs and also add some whitelisted URLs that we will need to access without any authorization token. This will look like below:


package com.betterjavacode.SpringAppCache;

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter
{
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    private UserDetailsService userDetailsService;

    private static final String[] AUTH_WHITELIST = {
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/webjars/**"
    };

    public WebSecurityConfiguration(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder)
    {
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
        this.userDetailsService = userDetailsService;
    }


    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity.cors().and().csrf().disable().authorizeRequests()
                .antMatchers(AUTH_WHITELIST).permitAll()
                .antMatchers(HttpMethod.POST, "/cachedemo/v1/users/signup").permitAll()
                .anyRequest().authenticated()
                .and().addFilter(new AuthenticationFilter(authenticationManager()))
                .addFilter(new AuthorizationFilter(authenticationManager()))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
    {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource()
    {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

In method configure we have restricted most APIs, only allowing Swagger URLs and signup URL. We also add filters to HttpSecurity. We will add our own UserDetailsServiceImpl class to validate user credentials.


package com.betterjavacode.services;

import com.betterjavacode.models.User;
import com.betterjavacode.repositories.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.Collections;

@Component
public class UserDetailsServiceImpl implements UserDetailsService
{
    private UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository)
    {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        User user = userRepository.findByUsername(username);
        if(user == null)
        {
            throw new UsernameNotFoundException(username);
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), Collections.emptyList());
    }
}

Demo

With all the code changes, now we are ready to create a user, login and access secured REST APIs. From the image above, a user gets Access Denied error for accessing secured APIs. To demo this, I have already registered a user with username `test1` and password test@123.

This POST request will give us Authorization token in response as shown above. Now using this token in our GET request to retrieve companies data. This GET request will look like below:

In this way, we showed how to secure REST API using JSON web token.

I will be launching the book “Simplifying Spring Security“. Do you want to get updates on launch? Sign up

References

  1. Implementing JWTs Authentication on Spring Boot API – JWT Authentication
  2. How to secure REST APIs – Secure REST APIs

Building user interface for Social KPI

As part of building the web application Social KPI SocialPie, we will be building the backend and frontend in module forms. Eventually, the plan is to deploy the application on the cloud. But backend and frontend are not different services as Microservice architecture generally dictates. In this post, I will show how we will be building a user interface for Social KPI using thymeleaf and angular JS.

The idea is to create a skeleton of UI by bringing different points in the discussion to make our decisions about choosing different parts of UI. In the previous post here, we discussed user flow.

User Story for user interface

  1. A user will access the application and will see the initial screen for login or sign up.
  2. If the user has not signed up before, he will sign up with the first name, last name, email, company name, password.
  3. Once the user signs up, the user will receive a confirmation email to sign in. This user will be the administrator to manage its company.
  4. A user can come back to the login screen through confirmation email. Then the user will enter credentials.
  5. User will see the company profile. User will have the option to modify company profile details as well as to add users with role REPORT.
  6. Administrator when adding these users will submit their first name, last name, email, and role as REPORT. Administrator will have an option to send emails to these users through portal or provide them username and password.
  7. Once the users with role REPORT login, they will have the option to change their temporary password. Once the password has been changed, he will be redirected to the reports screen.
  8. Administrator can also access reports screen any time.
  9. On Reports screen, user will have an option to synchronize the data with social media APIs to get the latest data. This will be once in a day option considering the limitation on access of APIs.
  10. On Reports screen, user will have an option to generate the report post-synchronization. User will be able to see Jasper reports in graph as well in data form. User will have an option to send these reports to other people in email.
  11. There will be logout and home screen options on the screen all the time.
  12. Home for user with Report role will be their profile information.

The skeleton of the user interface

Screen 1:

First Page

Screen 2:

Second Page

Screen 3:

Third Page

Screen 4:

Forth Page

Screen 5:

Fifth Page

Screen 6:

Sixth Page

Screen 7:

Seventh Page

Conclusion

In this post, we showed the skeleton of the user interface for the Social KPI web application. Of course, this is not a final design, but as we go on building it, we will have our changes and I will also show the code for this design. In future posts, I will be showing the functioning UI for login and sign-up pages.

 

 

 

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:

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

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:

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

 

Custom Twitter Client vs Spring Boot Twitter Plugin

To use twitter data in my saas application, I was going to write my own custom Twitter client by doing a rest call. However, I found out Spring Boot offers a Twitter plugin that can be used to fetch Twitter data. Neat.

In this post, I will show some comparison of these two approaches and why one can choose over another:

Custom Twitter Client

So custom twitter client will be a standalone client which will build an HTTP entity with client secrets that are needed to authenticate with Twitter API. In this client, we will use restOperations to call API endpoint passing HTTP entity and the REST call will respond with Twitter Data Model.

This will look like below:

public TwitterDataModel getTwitterData(long accountId)
{
    String url = buildRestUrl(accountId);
    ParameterizedTypeReference<HashMap<Long, TwitterDataModel>> responseType = new ParameterizedTypeReference<HashMap<Long, TwitterDataModel>>(){};
    HttpEntity entity = buildHttpEntity(CLIENT_ID, CLIENT_SECRET);
    Map<Long, TwitterDataModel> twitterDataModelMap = restOperations.exchange(url, HttpMethod.GET, entity, responseType).getBody();

    Long keyForData = new Long(accountId);
    TwitterDataModel twitterDataModel = twitterDataModelMap.get(keyForData);

    return twitterDataModel;
}

public String buildRestUrl(long accountId)
{
    return TWITTER_REST_ENDPOINT + accountId + TWITTER_REST_API;
}

There is nothing much wrong with this approach, except the fact that we will have to write an extra TwitterDataModel business object. Also, this business model should be created before we do the actual REST call.

Spring Boot Twitter Plugin

To use this plugin, first, we need to add the plugin in Gradle or maven like below:

compile('org.springframework.social:spring-social-twitter')

Once we have this plugin, we can add an object of type Twitter in our code to call REST APIs.

This will look like below:

private final Twitter twitter;

public TwitterDataModel getTwitterData(long accountId)
    {
        String url = buildRestUrl(accountId);
        ParameterizedTypeReference<HashMap<Long, TwitterDataModel>> responseType = new ParameterizedTypeReference<HashMap<Long, TwitterDataModel>>(){};
        HttpEntity entity = buildHttpEntity(CLIENT_ID,CLIENT_SECRET);

        Map<Long, TwitterDataModel> twitterDataModelMap = twitter.restOperations().exchange(url, HttpMethod.GET, entity, responseType).getBody();

        Long keyForData = new Long(accountId);
        TwitterDataModel twitterDataModel = twitterDataModelMap.get(keyForData);

        return twitterDataModel;
    }

    public String buildRestUrl(long accountId)
    {
        return TWITTER_REST_ENDPOINT + accountId + TWITTER_REST_API;
    }

The major advantage of this plugin is that we can get the data in Twitter Data Model that twitter offers. An then we can go on to use to handle our data.

Conclusion

In this post, I showed how we can use a Spring Boot Twitter social plugin to gather Twitter data.

Database and design discussion – Part III

To continue the development of a spring-based web application, this post will discuss using of Twitter API in saas application. If you want to understand, what we are building, you can read the first two posts of this series where we discussed the design of the application we are building:

  1. Database design and discussion – Part I
  2. Database design and discussion – Part II

In the previous post, we discussed the Instagram API that we will be using. With recent events around Facebook, I have decided not to use Facebook API for application development. We will still use Instagram and Twitter API.

Using Twitter API in SAAS Application

Firstly, Twitter offers different APIs for developers to build applications. We will be using Engagement API. You can find more details Twitter API.

Our goal is to use this Twitter API to collect engagement metrics in the Saas application.

Secondly, Engagement API offers us details about account engagement metrics which can help us to design marketing strategy. Sample response of this API looks like the below:

{
  "Tweet metrics": {
    "902301386880286721": {
      "engagements": "433",
      "favorites": "21",
      "impressions": "72218"
    },
    "902731270274166784": {
      "engagements": "61",
      "favorites": "27",
      "impressions": "7827"
    },
    "907022936564838401": {
      "engagements": "187",
      "favorites": "37",
      "impressions": "1916"
    }
  }
}

Therefore this API provides metrics for tweets, which tweet generated more traffic. The key decision that can be devised based on these metrics is what kind of tweet, moment or incident generates the traffic.

What fields we will use in our database?

In conclusion, we will be using the following fields in our database table TwitterData

  • tweet id
  • engagements
  • impressions
  • tweet

Twitter is a viable medium. This data will provide small businesses with a key metric about what tweets have worked with their followers and how they can leverage that pattern. Once the small businesses sort out these patterns, they will be able to create a number of tweets to engage with customers. Eventually, the goal here is to help small businesses to attract customers, and repeat customers.

References

  1. Twitter API documentation – Twitter API