If you want to avoid maintaining a users database and creating a custom authentication based on a login form, you should consider OAuth2 and their 3rd party providers. Your users will be able to log in with their social accounts. That is great for them as they do not need to remember new passwords and for other various reasons. Some of them are described in What is OpenID, OAuth2 and Google Sign In?
Additionally, I have already wrote how to integrate a web application with Google authentication. For those that prefer Facebook, there is also an article about Facebook login.
Multiple authentication providers
Integration with social media is a way to make the authentication process less painful to the end users. A problem occurs when the user does not have an account in the social medium that your application is integrated with. To reduce this risk, you can add more than one social medium. In this article, I show how to build an authentication mechanism on two OAuth2 3rd party providers.
Client Ids and Secrets
The first step is to register the application in both: Google and Facebook.
To register the application Google follow these steps:
- Log into Google API Console.
- Choose Credentials on the left menu.
- If you are there for the first time, you should see the Create a project button. Click it.
- Create the project.
- Create credentials - OAuth client ID by clicking an appropriate button.
- Choose an application type - in my case it is Web application.
- Provide URLs to your application that are authorized to request signing in through Google Sign-In. If you want to test it locally, typing http://localhost:8080 should work. Multiple URLs can be provided.
- In the Client ID section for Web application you will find Client ID and Client secret. Copy them out and save them in a secure place. You will need them later during configuration of the web application.
To register the application in Facebook follow these steps:
- Log into App Dashboard.
- Choose My Apps on the top right corner and Add a New App from the dropdown.
- Type a name of the website and a contact email.
- Once it is created, go into the website details and copy App ID and App Secret. You will need them later.
- Go to the settings and find Website section. Set a site URL to a link where your application is accessible. If you are testing it locally only, http://localhost:8080 should be fine.
After doing this, you have client ID and client secret for both systems. Let's assume that in my case, the values are like this:
Social Medium | Client ID | Client Secret |
aaaaaaaabbbbbbbbbbbbcccccccccc.apps.googleusercontent.com | 111122223333334444445555 | |
11223344556677 | 33572de38a294f749529aac74c797b93 |
Spring Security OAuth2 library
As I have to integrate two providers (Google, Facebook) with my application, Spring Security will work well. It allows enough configurability to complete this task. My project is based on Gradle so this is what I add to the build.gradle file.
compile group: "org.springframework.boot", name: "spring-boot-starter-web", version: "1.4.1.RELEASE" compile group: "org.springframework.security.oauth", name: "spring-security-oauth2", version: "2.0.11.RELEASE"
This configuration downloads Spring Security OAuth2 library and adds it to the project. It automatically does the same for all dependencies so core of Spring Security is added too. I use Spring Boot for some annotations too even if the security is covered by Spring Security and Spring OAuth2.
Configuration
Client IDs and secrets have to be put in the application. As I am using Spring Security in the application, I can use application.yml for that purpose. A part of that file that has Google and Facebook configuration looks like this.
security:
oauth2:
google:
client:
clientId: aaaaaaaabbbbbbbbbbbbcccccccccc.apps.googleusercontent.com
clientSecret: 111122223333334444445555
accessTokenUri: https://www.googleapis.com/oauth2/v3/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/auth
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
scope: profile
resource:
userInfoUri: https://www.googleapis.com/userinfo/v2/me
preferTokenInfo: false
facebook:
client:
clientId: 11223344556677
clientSecret: 33572de38a294f749529aac74c797b93
accessTokenUri: https://graph.facebook.com/oauth/access_token
userAuthorizationUri: https://www.facebook.com/dialog/oauth
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
resource:
userInfoUri: https://graph.facebook.com/me
This configuration has a little bit different structure than the one used by Spring Boot (an example is in Getting started with Google Sign-In in Spring Boot app). It is not the same because Spring Boot supports only one OAuth2 3rd party provider but we have two of them here (Google and Facebook). To integrate both, we have to give up on Spring Boot and go one step down to Spring Security. As I have to create some beans manually anyway, I can propose a different configuration path.
Google configuration is under security.oauth2.google and Facebook is under security.oauth2.facebook.
WebSecurityConfiguration
As accounts configuration is already done, some coding is ahead of us. Actually, it is less coding but more security setup in the code. There are a few parts but all of them can/should be done in a class that extends WebSecurityConfigurerAdapter. It is usually named WebSecurityConfiguration in various Spring tutorials.
Enable OAuth2
I add @EnableOAuth2Client Spring annotation to WebSecurityConfigurerAdapter.
@SpringBootApplication
@EnableOAuth2Client
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
It enables OAuth2 client configuration in the web application.
I will also need a client context bean which is covered by this simple line.
@Autowired
private OAuth2ClientContext oauth2ClientContext;
Google OAuth2 filter
A special security filter is needed to take care of authentication. The filter should know Google account configuration (the client ID and the client secret) to be able to talk to Google.
@Bean
@ConfigurationProperties("security.oauth2.google.client")
public AuthorizationCodeResourceDetails google() {
return new AuthorizationCodeResourceDetails();
}
@Bean
@ConfigurationProperties("security.oauth2.google.resource")
public ResourceServerProperties googleResource() {
return new ResourceServerProperties();
}
private Filter ssoGoogleFilter() {
OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/google");
OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(google(), oauth2ClientContext);
googleFilter.setRestTemplate(googleTemplate);
googleFilter.setTokenServices(new UserInfoTokenServices(googleResource().getUserInfoUri(), google().getClientId()));
return googleFilter;
}
Two beans are created: google() and googleResource(). They are configured based on the values in application.yml. Exact paths are passed as arguments to @ConfigurationProperties. A private method ssoGoogleFilter() returns a configured filter.
Facebook OAuth2 filter
Exactly the same thing has to be done to create a filter for Facebook.
@Bean
@ConfigurationProperties("security.oauth2.facebook.client")
public AuthorizationCodeResourceDetails facebook() {
return new AuthorizationCodeResourceDetails();
}
@Bean
@ConfigurationProperties("security.oauth2.facebook.resource")
public ResourceServerProperties facebookResource() {
return new ResourceServerProperties();
}
private Filter ssoFacebookFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId()));
return facebookFilter;
}
The code is pretty much the same, exept the configuration path.
CompositeFilter
At this point, there are two filters: ssoGoogleFilter and ssoFacebookFilter. They both can be combined into a composite filter.
private Filter ssoFilter() {
List<Filter> filters = new ArrayList<>();
filters.add(ssoGoogleFilter());
filters.add(ssoFacebookFilter());
CompositeFilter filter = new CompositeFilter();
filter.setFilters(filters);
return filter;
}
HttpSecurity
The last change on the Java side is to add that filter to the filter chain.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.antMatcher("/**")
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/chooseLogin.html")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/chooseLogin.html");
}
It means that all requests will go through ssoFilter() before any other security filters. That filter is a composite filter combined with Google and Facebook OAuth2 filters.
As it comes to the security settings - only one page does not require authentication - chooseLogin.html. This page is set as a form login page which means that whenever the OAuth2 filter does not mark the session as authenticated, the user will be redirected to this page.
Login page
Configuration is done. Only chooseLogin.html is missing. Probably, the simplest way to implement it is to create two links.
<a href="/login/google">Sign in with Google</a>
<a href="/login/facebook">Sign in with Facebook</a>
Important are href attributes. They are endpoints defined in respective filters. When Sign in with Google is clicked, the browser sends a GET request to /login/google which redirects to the Google Sign In page outside my application. It works analogically for Facebook.