1. Overview
In this tutorial, we'll focus on adding a new Facebook login to an existing form-login app.
We will use the Spring Social support to interact with Facebook and keep things clean and simple.
2. Maven Configuration
First, we will need to add spring-social-facebook dependency to our pom.xml:
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-facebook</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
3. Security Config – Just Form Login
Let's first start from the simple security configuration where we just have form-based authentication:
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = { "com.baeldung.security" })
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.and()
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/login*", "/signin/**", "/signup/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout();
return http.build();
}
}
We're not going to spend a lot of time on this config – if you want to understand it better, have a look at the form login article.
4. The Facebook Properties
Next, let's configure Facebook properties in our application.properties:
spring.social.facebook.appId=YOUR_APP_ID
spring.social.facebook.appSecret=YOUR_APP_SECRET
Note that:
- We need to create a Facebook application to obtain appId and appSecret
- From Facebook application Settings, make sure to Add Platform “Website” and http://localhost:8080/ as the “Site URL”
5. Security Config – Adding Facebook
Now, let's add a new way to authenticate into the system – driven by Facebook:
public class SecurityConfig {
@Autowired
private FacebookConnectionSignup facebookConnectionSignup;
@Value("${spring.social.facebook.appSecret}")
String appSecret;
@Value("${spring.social.facebook.appId}")
String appId;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login*","/signin/**","/signup/**").permitAll()
...
return http.build();
}
@Bean
public ProviderSignInController providerSignInController() {
ConnectionFactoryLocator connectionFactoryLocator =
connectionFactoryLocator();
UsersConnectionRepository usersConnectionRepository =
getUsersConnectionRepository(connectionFactoryLocator);
((InMemoryUsersConnectionRepository) usersConnectionRepository)
.setConnectionSignUp(facebookConnectionSignup);
return new ProviderSignInController(connectionFactoryLocator,
usersConnectionRepository, new FacebookSignInAdapter());
}
private ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(appId, appSecret));
return registry;
}
private UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator
connectionFactoryLocator) {
return new InMemoryUsersConnectionRepository(connectionFactoryLocator);
}
}
Let's carefully look at the new config:
- we're using a ProviderSignInController to enable the Facebook authentication, which needs two things:
first, a ConnectionFactoryLocator registered as a FacebookConnectionFactory with the Facebook properties we defined earlier.
second, an InMemoryUsersConnectionRepository.
- by sending a POST to “/signin/facebook” – this controller will initiate a user sign-in using the Facebook service provider
- we're setting up a SignInAdapter to handle the login logic in our application
- and we also setting up a ConnectionSignUp to handle signing up users implicitly when they first authenticate with Facebook
6. The Sign-In Adapter
Simply put, this adapter is a bridge between the controller above – driving the Facebook user sign-in flow – and our specific local application:
public class FacebookSignInAdapter implements SignInAdapter {
@Override
public String signIn(
String localUserId,
Connection<?> connection,
NativeWebRequest request) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(
connection.getDisplayName(), null,
Arrays.asList(new SimpleGrantedAuthority("FACEBOOK_USER"))));
return null;
}
}
Note that users logged in using Facebook will have the role FACEBOOK_USER, while users logged in using the form will have the role USER.
7. Connection Sign Up
When a user authenticates with Facebook for the first time, they have no existing account in our application.
This is the point where we need to create that account automatically for them; we're going to be using a ConnectionSignUp to drive that user creation logic:
@Service
public class FacebookConnectionSignup implements ConnectionSignUp {
@Autowired
private UserRepository userRepository;
@Override
public String execute(Connection<?> connection) {
User user = new User();
user.setUsername(connection.getDisplayName());
user.setPassword(randomAlphabetic(8));
userRepository.save(user);
return user.getUsername();
}
}
As you can see, we created an account for the new user – using their DisplayName as username.
8. The Front End
Finally, let's take a look at our front end.
We're going to now have support for these two authentication flows – form login and Facebook – on our login page:
<html>
<body>
<div th:if="${param.logout}">You have been logged out</div>
<div th:if="${param.error}">There was an error, please try again</div>
<form th:action="@{/login}" method="POST" >
<input type="text" name="username" />
<input type="password" name="password" />
<input type="submit" value="Login" />
</form>
<form action="/signin/facebook" method="POST">
<input type="hidden" name="scope" value="public_profile" />
<input type="submit" value="Login using Facebook"/>
</form>
</body>
</html>
Finally – here's the index.html:
<html>
<body>
<nav>
<p sec:authentication="name">Username</p>
<a th:href="@{/logout}">Logout</a>
</nav>
<h1>Welcome, <span sec:authentication="name">Username</span></h1>
<p sec:authentication="authorities">User authorities</p>
</body>
</html>
Note how this index page displays usernames and authorities.
And that's it – we now have two ways to authenticate into the application.
9. Conclusion
In this quick article, we learned how to use spring-social-facebook to implement a secondary authentication flow for our application.
And, of course, as always, the source code is fully available over on GitHub.
Course – LSS (cat=Security/Spring Security)
I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:
>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)