Security Top

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

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.

Security bottom

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
Security footer banner
Comments are closed on this article!