SCENARIO 1 When logging in with facebook to my webapp
If the browser is already logged in to Facebook, and
if the user's social authentication details are already registered in my webapp,
then when the user clicks "Sign in with Facebook" on my webapp,
he/she is authorized by Facebook and the Authorization object returned by
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
is the named user.
All is good.
SCENARIO 2 If the browser is NOT logged into Facebook but
the user's social authentication details are already registered in my webapp,
then when the user clicks "Sign in with Facebook" on my webapp,
the user signs in with Facebook by entering username and password BUT
the the Authorization object returned by
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
is authorized as anonymous and has the name anonymousUser.
Moreover, the call from Facebook after authorization is to /signup
However, I am expecting the same registered user as in scenario 1
My security configuration is below. Can anyone give me a hint as to what the problem may be?
<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.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http use-expressions="true" entry-point-ref="appAuthenticationEntryPoint">
<security:intercept-url pattern="/login" access="permitAll()" />
<security:intercept-url pattern="/flow-entry.html" access="permitAll()"/>
<security:intercept-url pattern="/flow-jobpostdata.html" access="permitAll()"/>
<security:intercept-url pattern="/flow-jobpostdata_anydegree.html" access="permitAll()"/>
<security:intercept-url pattern="/j_spring_security_check" access="permitAll()"/>
<!-- Adds social authentication filter to the Spring Security filter chain. -->
<security:custom-filter before="PRE_AUTH_FILTER" ref="socialAuthenticationFilter"/>
<security:custom-filter position="FORM_LOGIN_FILTER" ref="SecurityAuthFilter"/>
</security:http>
<!-- authentication manager and its provider( social provider deals with social login & local user provider deals with form login ) -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="socialAuthenticationProvider"/>
<security:authentication-provider user-service-ref="localUserDetailService"/>
</security:authentication-manager>
<bean id="customAuthenticationProvider" class="com.ikoda.service.loginservices.CustomAuthenticationProvider">
<property name="auService" ref="auService" />
</bean>
<bean id="socialAuthenticationProvider" class="org.springframework.social.security.SocialAuthenticationProvider">
<constructor-arg ref="inMemoryUsersConnectionRepository"/>
<constructor-arg ref="socialUserDetailService"/>
</bean>
<!-- form login beans -->
<bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
</bean>
<bean id="appAuthenticationEntryPoint"
class="com.ikoda.service.loginservices.AppAuthenticationEntryPoint">
<constructor-arg name="loginFormUrl" value="/login"/>
</bean>
<bean id="rememberMeServices"
class="org.springframework.security.web.authentication.NullRememberMeServices"/>
<bean id="failureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<constructor-arg name="defaultFailureUrl" value="/login?error=true"/>
</bean>
<bean class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
id="SecurityAuthFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler" ref="successHandler"/>
<property name="authenticationFailureHandler" ref="failureHandler"/>
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
<!-- social login filter which is a pre authentication filter and works for /auth service url -->
<bean id="socialAuthenticationFilter" class="org.springframework.social.security.SocialAuthenticationFilter">
<constructor-arg name="authManager" ref="authenticationManager"/>
<constructor-arg name="userIdSource" ref="userIdSource"/>
<constructor-arg name="usersConnectionRepository" ref="inMemoryUsersConnectionRepository"/>
<constructor-arg name="authServiceLocator" ref="appSocialAuthenticationServiceRegistry"/>
<property name="authenticationSuccessHandler" ref="successHandler"/>
</bean>
<!-- inmemory connection repository which holds connection repository per local user -->
<bean id="inMemoryUsersConnectionRepository"
class="org.springframework.social.connect.mem.InMemoryUsersConnectionRepository">
<constructor-arg name="connectionFactoryLocator" ref="appSocialAuthenticationServiceRegistry"/>
<property name="connectionSignUp" ref="connectionSignUp"/>
</bean>
<!-- service registry will holds connection factory of each social provider-->
<bean id="appSocialAuthenticationServiceRegistry"
class="com.ikoda.service.loginservices.AppSocialAuthenticationServiceRegistry">
<constructor-arg>
<list>
<ref bean="facebookAuthenticationService"/>
<ref bean="googleAuthenticationService"/>
</list>
</constructor-arg>
</bean>
<bean id="facebookAuthenticationService"
class="org.springframework.social.facebook.security.FacebookAuthenticationService">
<constructor-arg name="apiKey" value="11111"/>
<constructor-arg name="appSecret" value="11111"/>
</bean>
<bean id="googleAuthenticationService"
class="org.springframework.social.google.security.GoogleAuthenticationService">
<constructor-arg name="apiKey" value="111-lpmhcmuj1577bd6god0696g4u2g16c1i.apps.googleusercontent.com"/>
<constructor-arg name="appSecret" value="111-111-"/>
</bean>
<bean id="userIdSource" class="org.springframework.social.security.AuthenticationNameUserIdSource"/>
<!-- If no local user is associated to a social connection then connection sign up will create a new local user and map it to social user -->
<bean id="connectionSignUp" class="com.ikoda.service.loginservices.AppConnectionSignUp"/>
</beans>
The redirect to signUp is the intended behaviour of Spring Social. The key is how to manage the behaviour.
One way to solve the problem is by authenticating the social login in the controller signup method. To do this requires accessing the social connection. You can do this by autowiring ProviderSignInUtils in your controller.
Note AppConnectionSignUP is some implementation of org.springframework.social.connect.ConnectionSignUp
Connection<?> connection = providerSignInUtils.getConnectionFromSession(webRequest);
if (null != connection)
{
String userIdString = appConnectionSignUp.execute(connection);
}
Another way to solve the problem is to implement your own ConnectionSignUp
<bean id="connectionSignUp" class="com.ikoda.service.loginservices.AppConnectionSignUp"/>
and reference that in your UsersConnectionRepository
<bean id="jdbcUsersConnectionRepository"
class="org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository">
<constructor-arg ref="dataSource" />
<constructor-arg ref="appSocialAuthenticationServiceRegistry" />
<constructor-arg ref="textEncryptor" />
<property name="connectionSignUp" ref="connectionSignUp"/>
</bean>
If you take this approach, then social login will bypass the Controller /signUp and directly invoke the execute method of connectionSignUP bean.
Meanwhile, in the question above the inMemoryUsersConnectionRepository was ill-advised. I switched to the jdbcUsersConnectionRepository.