Configure pac4j for a Social-Login along with a Spring-Security based Form-Login

The Problem – What will be explained

If you just want to enable your spring-based webapplication to let users log in with their social accounts, without changing anything else, pac4j should be your first choice. But the provided example only shows, how to define all authentication mechanisms via pac4j. If you already have set up your log-in via spring-security, you have to reconfigure it with the appropriate pac4j-mechanism. That is a lot of unnecessary work, if you just want to supplement the already configured log in with the additionally possibility, to log in via a social provider.

In this short article, I will show you, how to set that up along with the normal form-based login of Spring-Security. I will show this for a Login via Facabook along the Form-Login of Spring-Security. The method should work as well for other social logins, that are supported by spring-security-pac4j, along other login-mechanisms provided by spring-security out-of-the-box.

In this article I will not explain, how to store the user-profile-data, that was retrieved during the social login. Also, if you need more social interaction, than just a login and access to the default data in the user-profile you probably need spring-social. How to combine spring-social with spring-security for that purpose, is explained in this nice article about how to add social sign in to a spring-mvc weba-pplication.

Adding the Required Maven-Artifacts

In order to use spring-security-pac4j to login to facebook, you need the following maven-artifacts:


<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>spring-security-pac4j</artifactId>
  <version>1.2.5</version>
</dependency>
<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>pac4j-http</artifactId>
  <version>1.7.1</version>
</dependency>
<dependency>
  <groupId>org.pac4j</groupId>
  <artifactId>pac4j-oauth</artifactId>
  <version>1.7.1</version>
</dependency>

Configuration of Spring-Security (Without Social Login via pac4j)

This is a bare minimal configuration to get the form-login via Spring-Security working:


<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
    ">

  <security:http use-expressions="true">
    <security:intercept-url pattern="/**" access="permitAll"/>
    <security:intercept-url pattern="/home.html" access="isAuthenticated()"/>
    <security:form-login login-page="/login.html" authentication-failure-url="/login.html?failure"/>
    <security:logout/>
    <security:remember-me/>
  </security:http>

  <security:authentication-manager>
    <security:authentication-provider>
      <security:user-service>
  	<security:user name="user" password="user" authorities="ROLE_USER" />
      </security:user-service>
    </security:authentication-provider>
  </security:authentication-manager>

</beans>

The http defines, that the access to the url /home.html is restriced and must be authenticated via a form-login on url /login.html. The authentication-manager defines an in-memory authentication-provider for testing purposes with just one user (username: user, password: user). For more details, see the documentation of spring-security.

Enabling pac4j via spring-security-pac4j alongside

To enable pac4j alongside, you have to add/change the following:


<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
    ">

  <security:http use-expressions="true">
    <security:custom-filter position="OPENID_FILTER" ref="clientFilter"/>
    <security:intercept-url pattern="/**" access="permitAll()"/>
    <security:intercept-url pattern="/home.html" access="isAuthenticated()"/>
    <security:form-login login-page="/login.html" authentication-failure-url="/login.html?failure"/>
    <security:logout/>
  </security:http>

  <security:authentication-manager alias="authenticationManager">
    <security:authentication-provider>
      <security:user-service>
  	<security:user name="user" password="user" authorities="ROLE_USER" />
      </security:user-service>
    </security:authentication-provider>
    <security:authentication-provider ref="clientProvider"/>
  </security:authentication-manager>

  <!-- entry points -->
  <bean id="facebookEntryPoint" class="org.pac4j.springframework.security.web.ClientAuthenticationEntryPoint">
    <property name="client" ref="facebookClient"/>
  </bean>

  <!-- client definitions -->
  <bean id="facebookClient" class="org.pac4j.oauth.client.FacebookClient">
    <property name="key" value="145278422258960"/>
    <property name="secret" value="be21409ba8f39b5dae2a7de525484da8"/>
  </bean>
  <bean id="clients" class="org.pac4j.core.client.Clients">
    <property name="callbackUrl" value="http://localhost:8080/callback"/>
    <property name="clients">
      <list>
        <ref bean="facebookClient"/>
      </list>
    </property>
  </bean>

  <!-- common to all clients -->
  <bean id="clientFilter" class="org.pac4j.springframework.security.web.ClientAuthenticationFilter">
    <constructor-arg value="/callback"/>
    <property name="clients" ref="clients"/>
    <property name="sessionAuthenticationStrategy" ref="sas"/>
    <property name="authenticationManager" ref="authenticationManager"/>
  </bean>
  <bean id="clientProvider" class="org.pac4j.springframework.security.authentication.ClientAuthenticationProvider">
    <property name="clients" ref="clients"/>
  </bean>
  <bean id="httpSessionRequestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache"/>
  <bean id="sas" class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy"/>

</beans>

In short:

  1. You have to add an additional filter in http. I added this filter on position OPENID_FILTER, because pac4j introduces a unified way to handle OpenID and OAuth and so on. If you are using the OpenID-mechanism of spring-security, you have to use another position in the filter-chain (for example CAS_FILTER) or reconfigure OpenID to use the pac4j-mechanism, which should be fairly straight-forward.
    The new Filter has the ID clientFilter and needs a reference to the authenticationManager. Also, the callback-URL (here: /callback) must be mapped to your web-application!
  2. You have to add an additional authentication-provider to the authentication-manager, that references your newly defined pac4j-ClientProvider (clientProvider).
  3. You have to configure your entry-points as pac4j-clients. In the example above, only one pac4j-client, that authenticats the user via Facebook, is configured. You easily can add more clients: just copy the definitions from the spring-security-pac4j example.

That should be all, that is necessary, to enable a Facebook-Login in your Spring-Security web-application.

Do Not Forget To Use Your Own APP-ID!

The App-ID 145278422258960 and the accompanying secret be21409ba8f39b5dae2a7de525484da8 were taken from the spring-security-pac4j example for simplicity. That works for a first test-run on localhost. But you have to replace that with your own App-ID and -scecret, that you have to generate using your App Dashboard on Facebook!

More to come…

This short article does not show, how to save the retrieved user-profiles in your user-database, if you need that. I hope, I will write a follow-up on that soon. In short: pac4j creates a Spring-Security UserDetails-Instance for every user, that was authenticated against it. You can use this, to access the data in the retrieved user-profile (for example to write out the name of the user in a greeting or contact him via e-mail).

Comments / Questions

  1. Zamir says:

    I’ve followed all instructions, but it doesn’t intercept any URL. It doesn’t show any error.

    Can anyone help me?

  2. Pascal says:

    And how can I trigger the facebookEntryPoint with a “Facebook Login”-button on the login form page?

    Thanks

  3. Thanks for the tutorial.

    spring-security-pac4j v1.3 is now based on Spring Security v4.

Leave a Reply

Your email address will not be published. Required fields are marked *