Table of Contents
- Part-1: Authorising user using Spring Social (Google, FaceBook and LinkedIn)
- Introduction
- Step 1- Maven Dependencies
- Step 2- Adding Login Page and Controller
- Step 3- Creating a POJO to show authorised user detail
- Step 4 – Creating Social Media Providers
- Step 5 – Fixing some issues before we can run – Adding Google Support
- Step 6- Fixing some issues before we can run – Changing the default spring social redirect face flow
- Step 7 – Fixing some issues before we can run – Allowing Multiple Users to Login
- Step 8 – Updating the LoginController
- Step 9 – Adding Spring properties
- Step 10 – User Details on page
- Conclusion
Part-1: Authorising user using Spring Social (Google, FaceBook and LinkedIn)
Introduction
Social Logins are becoming increasingly popular across web applications, they not only offer good user experience are safe and also saves the user from password fatigue. Imagine losing a customer because they can not log on to the application as they cannot remember a password are not bothered to reset passwords etc. A social login is a kind of single sign-on where you use login information of a social network like Facebook, Twitter, Google+ to log on to a third website, instead of creating a new log-in account specially for that website.
Earlier I have written few blogs about Spring-Social features where I had written about few problems like no support for Google, only one user allowed by spring-social etc and also few exceptions which occur and then how to fix them.I thought of collating all the problems and writing a step by step tutorial explaining in detail, on how to register a user using spring social.
The main aim of this tutorial will be to integrate spring-security and spring-social together and create a web application where users can be registered either by
- Registering their details
In this first part of the tutorial, our aim will be to get data from spring-social and then display the details on the page, in the second part we will integrate the spring-security where we will ask the user to enter his details and then store on DB.
Step 1- Maven Dependencies
Assuming that you know how to create a simple spring boot project, let us move on to add maven-dependencies required to run the project. If not, then you can create a spring-starter project and add thymleaf and web support.
Adding Spring-Social dependencies
The first step would be to add spring-social maven repositories, following dependencies are required
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-core</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-config</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-facebook</artifactId> <version>3.0.0.M1</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-linkedin</artifactId> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-google</artifactId> <version>1.0.0.RELEASE</version> </dependency> |
In case you get the repository not available error, you might have to add the following repository to your pom.
1 2 3 4 5 6 7 8 | <repositories> <repository> <id>alfresco-public</id> <url>https://artifacts.alfresco.com/nexus/content/groups/public</url> </repository> </repositories> |
Adding Thymleaf, BootStrap, JQuery and Font-awesome dependencies
To make our web pages look nice we will add some Bootstrap CSS, Font-awesome and Jquery to them and also we will use thymleaf for HTML. Read Here if you want to know more in detail.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>font-awesome</artifactId> <version>4.7.0</version> </dependency> |
Complete POM
To avoid any mistakes, here is the complete POM.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.login</groupId> <artifactId>SpringLogin</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SpringLogin</name> <description>Spring Login - Example POC</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-core</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-config</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-facebook</artifactId> <version>3.0.0.M1</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-linkedin</artifactId> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-google</artifactId> <version>1.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>font-awesome</artifactId> <version>4.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> </dependency> </dependencies> <repositories> <repository> <id>alfresco-public</id> <url>https://artifacts.alfresco.com/nexus/content/groups/public</url> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
Step 2- Adding Login Page and Controller
Once the dependencies have been resolved be will create a View and Controller
Adding View
To start we will create a view which will have 3 buttons each for Google, Facebook and Login. When the user will click on any of them he will be asked for authorization for the selected provider, upon verification his details will be rendered on the page.
Create a file under src/main/resources/templates/login.html with the following content. Please note that this view has 3 different forms each for different provider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>Login</title> <meta name="description" content="" /> <meta name="viewport" content="width=device-width" /> <base href="/" /> <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css" /> <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script> <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script> <link rel="stylesheet" href="/webjars/font-awesome/css/font-awesome.min.css"></link> </head> <body> <div class="container"> <h1>Login Using</h1> <form action="/connect/google" method="POST" style="display: inline"> <input type="hidden" name="scope" value="profile email" /> <button type="submit" class="btn btn-danger"> Google <span class="fa fa-google-plus"></span> </button> </form> <form action="/connect/facebook" method="POST" style="display: inline"> <input type="hidden" name="scope" value="public_profile,email" /> <button type="submit" class="btn btn-primary"> Facebook <span class="fa fa-facebook"></span> </button> </form> <form action="/connect/linkedin" method="POST" style="display: inline"> <input type="hidden" name="scope" value="r_basicprofile,r_emailaddress" /> <button type="submit" class="btn btn-primary"> LinkedIn <span class="fa fa-linkedin"></span> </button> </form> </div> </body> </html> |
Adding Controller
Let us create a controller class called LoginController in package com.login.controller with a simple method which will map the above page to URL / or /login.
1 2 3 4 5 6 7 8 9 10 | @Controller public class LoginController { @RequestMapping(value = { "/","/login" }) public String login() { return "login"; } } |
Now run the above application and at this point, a page like this should appear and of course, it won’t do anything yet.
Once we verify the user via our social provider, we will show the user details on another page. We can store these details in a POJO called UserBean.
Let us define a class called UserBean.java as shown below in package com.login.model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | package com.login.model; import java.beans.Transient; import java.io.Serializable; public class UserBean implements Serializable{ private static final long serialVersionUID = 1L; private String firstName; private String lastName; private String email; private String title; private String country; private String password; private String passwordConfirm; private String provider; private String image; public String getEmail() { return email; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPasswordConfirm() { return passwordConfirm; } public void setPasswordConfirm(String passwordConfirm) { this.passwordConfirm = passwordConfirm; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getProvider() { return provider; } public void setProvider(String provider) { this.provider = provider; } } |
Step 4 – Creating Social Media Providers
For Facebook, Google and LinkedIn we will create a Provider class which will be used to do authentication and save user details to the UserBean, which can be later displayed on the page. To start off with we will create a BaseProvider which will have a constructor where all the providers will be initialized.
This BaseProvider is created by injecting the Facebook, Google, LinkedIn and ConnectionRepository repository. These objects are a reference to Spring Social’s Facebook, Google, LinkedIn and ConnectionRepository API binding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package com.login.social.providers; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.facebook.api.Facebook; import org.springframework.social.google.api.Google; import org.springframework.social.linkedin.api.LinkedIn; @Configuration @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class BaseProvider { private Facebook facebook; private Google google; private LinkedIn linkedIn; private ConnectionRepository connectionRepository; public BaseProvider(Facebook facebook,Google google, LinkedIn linkedIn, ConnectionRepository connectionRepository) { this.facebook = facebook; this.connectionRepository = connectionRepository; this.google=google; this.linkedIn= linkedIn; } public Facebook getFacebook() { return facebook; } public void setFacebook(Facebook facebook) { this.facebook = facebook; } public ConnectionRepository getConnectionRepository() { return connectionRepository; } public void setConnectionRepository(ConnectionRepository connectionRepository) { this.connectionRepository = connectionRepository; } public Google getGoogle() { return google; } public void setGoogle(Google google) { this.google = google; } public LinkedIn getLinkedIn() { return linkedIn; } public void setLinkedIn(LinkedIn linkedIn) { this.linkedIn = linkedIn; } } |
Now let us create provider class for each provider
Facebook Provider
We will use the below Facebook provider to authorize the user and then access Facebook Data. We will save the fetched user data into our UserBean.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.login.social.providers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.facebook.api.Facebook; import org.springframework.social.facebook.api.User; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.login.model.UserBean; @Service public class FacebookProvider { private static final String FACEBOOK = "facebook"; private static final String REDIRECT_LOGIN = "redirect:/login"; @Autowired BaseProvider baseProvider ; public String getFacebookUserData(Model model, UserBean userForm) { ConnectionRepository connectionRepository = baseProvider.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(Facebook.class) == null) { return REDIRECT_LOGIN; } populateUserDetailsFromFacebook(userForm); model.addAttribute("loggedInUser",userForm); return "user"; } protected void populateUserDetailsFromFacebook(UserBean userForm) { Facebook facebook = baseProvider.getFacebook(); User user = facebook.userOperations().getUserProfile(); userForm.setEmail(user.getEmail()); userForm.setFirstName(user.getFirstName()); userForm.setLastName(user.getLastName()); userForm.setImage(user.getCover().getSource()); userForm.setProvider(FACEBOOK); } } |
Google Provider
We will use the below google provider to authorize the user and then access Google Data. We will save the fetched user data into our UserBean.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package com.login.social.providers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.google.api.Google; import org.springframework.social.google.api.plus.Person; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.login.model.UserBean; @Service public class GoogleProvider { private static final String REDIRECT_CONNECT_GOOGLE = "redirect:/login"; private static final String GOOGLE = "google"; @Autowired BaseProvider socialLoginBean ; public String getGoogleUserData(Model model, UserBean userForm) { ConnectionRepository connectionRepository = socialLoginBean.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(Google.class) == null) { return REDIRECT_CONNECT_GOOGLE; } populateUserDetailsFromGoogle(userForm); model.addAttribute("loggedInUser",userForm); return "user"; } protected void populateUserDetailsFromGoogle(UserBean userform) { Google google = socialLoginBean.getGoogle(); Person googleUser = google.plusOperations().getGoogleProfile(); userform.setEmail(googleUser.getAccountEmail()); userform.setFirstName(googleUser.getGivenName()); userform.setLastName(googleUser.getFamilyName()); userform.setImage(googleUser.getImageUrl()); userform.setProvider(GOOGLE); } } |
LinkedIn Provider
We will use the below LinkedIn provider to authorize the user and then accessLinkedIn Data. We will save the fetched user data into our UserBean.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package com.login.social.providers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.linkedin.api.LinkedIn; import org.springframework.social.linkedin.api.LinkedInProfileFull; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.login.model.UserBean; @Service public class LinkedInProvider { private static final String LINKED_IN = "linkedIn"; private static final String REDIRECT_LOGIN = "redirect:/login"; @Autowired BaseProvider socialLoginBean ; public String getLinkedInUserData(Model model, UserBean userForm) { ConnectionRepository connectionRepository = socialLoginBean.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(LinkedIn.class) == null) { return REDIRECT_LOGIN; } populateUserDetailsFromLinkedIn(userForm); model.addAttribute("loggedInUser",userForm); return "user"; } private void populateUserDetailsFromLinkedIn(UserBean userForm) { LinkedIn linkedIn = socialLoginBean.getLinkedIn(); LinkedInProfileFull linkedInUser = linkedIn.profileOperations().getUserProfileFull(); userForm.setEmail(linkedInUser.getEmailAddress()); userForm.setFirstName(linkedInUser.getFirstName()); userForm.setLastName(linkedInUser.getLastName()); userForm.setImage(linkedInUser.getProfilePictureUrl()); userForm.setProvider(LINKED_IN); } } |
Step 5 – Fixing some issues before we can run – Adding Google Support
If you would have noticed by now, spring social doesn’t support Google so to enable autoconfiguration of spring-social-google we need to
Add a GoogleAutoConfiguration Class
Spring social do have a spring-social-google project but it doesn’t have autoconfiguration for Google. So google authorization doesn’t work with spring boot autoconfigure straight away.
To enable autoconfigure of spring-social-google we need to
- Add a GoogleAutoConfiguration
- Add GoogleProperties
FacebookAutoConfiguration class you should be able to find it either in org.springframework.boot.autoconfigure.social package in spring-autoconfigure.jar does the autoconfiguration for Facebook, the cheat sheet or the trick would be to copy this File and replace Facebook with Google to enable GoogleAutoConfiguration
Alternatively, copy the below class and put in some package, make sure it is available in classpath(src/main/java or inside another source folder)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | package com.login.config.google; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter; import org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.social.config.annotation.EnableSocial; import org.springframework.social.config.annotation.SocialConfigurerAdapter; import org.springframework.social.connect.Connection; import org.springframework.social.connect.ConnectionFactory; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.connect.web.GenericConnectionStatusView; import org.springframework.social.google.api.Google; import org.springframework.social.google.connect.GoogleConnectionFactory; @Configuration @ConditionalOnClass({ SocialConfigurerAdapter.class, GoogleConnectionFactory.class }) @ConditionalOnProperty(prefix = "spring.social.google", name = "app-id") @AutoConfigureBefore(SocialWebAutoConfiguration.class) @AutoConfigureAfter(WebMvcAutoConfiguration.class) public class GoogleAutoConfiguration { @Configuration @EnableSocial @EnableConfigurationProperties(GoogleProperties.class) @ConditionalOnWebApplication protected static class GoogleConfigurerAdapter extends SocialAutoConfigurerAdapter { private final GoogleProperties properties; protected GoogleConfigurerAdapter(GoogleProperties properties) { this.properties = properties; } @Bean @ConditionalOnMissingBean(Google.class) @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES) public Google google(ConnectionRepository repository) { Connection<Google> connection = repository.findPrimaryConnection(Google.class); return connection != null ? connection.getApi() : null; } @Bean(name = { "connect/googleConnect", "connect/googleConnected" }) @ConditionalOnProperty(prefix = "spring.social", name = "auto-connection-views") public GenericConnectionStatusView googleConnectView() { return new GenericConnectionStatusView("google", "Google"); } @Override protected ConnectionFactory<?> createConnectionFactory() { return new GoogleConnectionFactory(this.properties.getAppId(), this.properties.getAppSecret()); } } } |
Add GoogleProperties
In the same package add the below class, this is needed so when we add secret keys in properties file for google. We wont see any error.
1 2 3 4 5 6 7 8 9 10 11 12 13 | package com.login.config.google; import org.springframework.boot.autoconfigure.social.SocialProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.social.google") public class GoogleProperties extends SocialProperties{ } |
Spring Social is quick and easy to start but it needs some configuring before it can be used for production. The default flow for
The Views are predefined so the user flow is login.html -> connect/facebookConnected.html by default, irrespective of what our submit URL is, the facebook will override the flow and redirect you to connect/facebookConnected.html.
Read Here: How to change the default spring social redirect page flow
We will add a ChangeDefaultFlowController which will override the connectedView method and redirect the flow to “/facebook”, “/google” or “/LinkedIn”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package com.login.controller; import org.springframework.social.connect.ConnectionFactoryLocator; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.connect.web.ConnectController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/connect") public class ChangeDefaultFlowController extends ConnectController { public ChangeDefaultFlowController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) { super(connectionFactoryLocator, connectionRepository); } @Override protected String connectedView(String providerId) { return "redirect:/"+providerId; } } |
Step 7 – Fixing some issues before we can run – Allowing Multiple Users to Login
If you test the Spring Facebook example, accessing FB data you will realise that it only supports one user. So when the first users log in to Facebook, only his details will be shared across all new sessions/users and they won’t be asked for any kind of authentication. One of the forum says that this example is supposed to demonstrate what can be done and is not intended for production use.
Read Here: Spring Social Facebook Authentication Example for multiple users
To fix this problem you need to override a method which always returns a string called “anonymous”.The solution is to override the “anonymous” as the UserId for each new user/session. So for each session, we can simply return a SessionID, however, it may not be unique enough to identify users, especially if it’sh being cached or stored somewhere in a connection database.
Using a Universally Unique Identifier(UUID) would be a safer bet.So we will store a new UUID in session so it persists between different requests but is alive only till the session is valid. See the below method which does the trick.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package com.login.identifier; import java.util.UUID; import org.springframework.context.annotation.Configuration; import org.springframework.social.UserIdSource; import org.springframework.social.config.annotation.SocialConfigurerAdapter; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @Configuration public class SessionIdentifier extends SocialConfigurerAdapter { @Override public UserIdSource getUserIdSource() { return new SessionIdUserIdSource(); } private static final class SessionIdUserIdSource implements UserIdSource { @Override public String getUserId() { RequestAttributes request = RequestContextHolder.currentRequestAttributes(); String uuid = (String) request.getAttribute("_socialUserUUID", RequestAttributes.SCOPE_SESSION); if (uuid == null) { uuid = UUID.randomUUID().toString(); request.setAttribute("_socialUserUUID", uuid, RequestAttributes.SCOPE_SESSION); } return uuid; } } } |
Step 8 – Updating the LoginController
Now since we have added all the fixes, its time to update the controller so our view can submit the forms to Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.login.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.login.model.UserBean; import com.login.social.providers.FacebookProvider; import com.login.social.providers.GoogleProvider; import com.login.social.providers.LinkedInProvider; @Controller public class LoginController { @Autowired FacebookProvider facebookProvider; @Autowired GoogleProvider googleProvider; @Autowired LinkedInProvider linkedInProvider; @RequestMapping(value = "/facebook", method = RequestMethod.GET) public String loginToFacebook(Model model) { return facebookProvider.getFacebookUserData(model, new UserBean()); } @RequestMapping(value = "/google", method = RequestMethod.GET) public String loginToGoogle(Model model) { return googleProvider.getGoogleUserData(model, new UserBean()); } @RequestMapping(value = "/linkedin", method = RequestMethod.GET) public String helloFacebook(Model model) { return linkedInProvider.getLinkedInUserData(model, new UserBean()); } @RequestMapping(value = { "/","/login" }) public String login() { return "login"; } } |
Step 9 – Adding Spring properties
Assuming that you know how to register the API and public/private keys on Google/FB and LinkedIn for this example purpose you can use the below properties file.
Also, notice that server starts on port 3000(don’t change that, since the URL registered with these keys is localhost:3000
1 2 3 4 5 6 7 8 9 10 11 12 | spring.social.facebook.appId=384261248599251 spring.social.facebook.appSecret=fd7fa1c5f5a267f463263a0ce7ff2025 spring.social.linkedin.app-id=771mrzk94hye1w spring.social.linkedin.app-secret=iIJFgBf9lCb18zYe spring.social.google.appId=12894100090-tqso3lih5o42isneort886la2pesafmp.apps.googleusercontent.com spring.social.google.appSecret=9xfU16efvxQ-BTMsXT9wOLpw server.port:3000 |
This is how the project structure will look like
Step 10 – User Details on page
To show the authenticated user on the page, we will display the UserBean using thymleaf. Add the below html page under src/main/resources/static/ as user.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>Login</title> <meta name="description" content="" /> <meta name="viewport" content="width=device-width" /> <meta name="ctx" th:content="${#httpServletRequest.getContextPath()}" /> <base href="/" /> <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css" /> <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script> <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script> <link rel="stylesheet" href="/webjars/font-awesome/css/font-awesome.min.css"></link> </head> <body> <div class="container"> <br /><br /><br /> <form th:object="${loggedInUser}" method="post"> <div class="row"> <label for="username">Username:</label> <span th:text="${loggedInUser.email}" /> </div> <div class="row"> <label for="firstName">Name:</label> <span th:text="*{firstName}" /> <span th:text="*{lastName}" /> </div> <div class="row"> <label for="image">Name:</label> <img th:attr="src=@{*{image}}" style="width: 150px; height: 150px;"/> </div> </form> <br /> <a href="/login" th:href="@{/login}" class="btn btn-info btn-lg"> <span class="glyphicon glyphicon-chevron-left"></span> Login using other social Providers </a> </div> </body> </html> |
Now run the Project and access the application on http://localhost:3000/login.
You should be greeted by an authentication popup depending upon your provider selected and then you should be redirected to the page where your username, name, profile image will be displayed as below.
Conclusion
Spring Social API helps us in implementing an authentication mechanism with social providers. It is easy to use and we have seen how to configure it to tailor it to our needs.
In the next part we will use spring-security to register users and then only allow logged in users to navigate to secure pages. The link has been provided below for second part.
Hi Abhi, thanks a lot for taking up the time to write such a great tutorial. I am able to set up the facebook login but the google login does not work. To be precise, it works till Step 6 (changing the default workflow). After I sign in at google I am being redirectied to connect/googleConnect instead to /google. I have even copy/pasted Your ChangeDefaultFlowController from Github. I do not understand why it works for Facebook but not for google.
Would be very thankful to You if You could give me some pointers.
How to call from /connect/facebook from my front end application(running on different port) and after authenticate with Facebook how to get redirect to the page in front end application from back end(spring and running on different port)
How to call /connect/facebook from my front end application(running on different port) and after authenticate with Facebook how to get redirect to the page in front end application from back end(spring and running on different port)
I am using spring boot 2.0.1-RELEASE and with your example I got the following error:
– – – cut here – – –
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.demo.controller.ChangeDefaultFlowController required a bean of type ‘org.springframework.social.connect.ConnectionFactoryLocator’ that could not be found.
Action:
Consider defining a bean of type ‘org.springframework.social.connect.ConnectionFactoryLocator’ in your configuration.
– – – cut here – – –
It seems the injection is not working. I tried to use your social facebook version (even with your defined repository), but it didn’t work. I am using social-facebook 2.0.3.RELEASE.
Could you help me, please?
Thanks in advance
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘changeDefaultFlowController’
please help!!!
where u use these properties file
Great Work. Thank you!
Hi,
Thank your code works well with your credential. I am facing a redirect issue while using My credentials.
Let me know, what should be the redirect URI.
Currently, I am using: http://localhost:3000/connect/google
Hi,
we are using spring social to integrate social login to website. facebook is working fine but google login giving access denied error. Auth provider is coming as null. Please kindly suggest how can i fix the issue.
Thanks in advance,
KArthik
Thanks for this useful tutorial. When I use with spring boot 2.2.1 version SocialAutoConfigurerAdapter class can not be resolved. How to use this adapter with 2.2.1 and also non auto config.