Announcement Announcement Module
Collapse
No announcement yet.
Facebook single sing-on Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Facebook single sing-on

    I'm currently using spring-security's form authentication to authenticate users of my application by comparing their username/password to those in the DB, but i'd like to add facebook's single sign-on support. I can't believe i'm the first one to think of doing that, but all the tutorials or documentation I find are either missing some important parts or only doing facebook authentication.

    I tried adding a second authentication provider to the authentication manager, but I'm having trouble coding the custom userDetailsService for facebook. And even then I'm not sure what the best practices are in relation to linking a facebook account to a user account. Should I keep the user's facebook uid in his user account or should I compare with something else?

    I looked at the tonr2/sparklr2 demo, but it's not linking the accounts...

    I'd really like it if someone could point me in the right direction.

  • #2
    I feel your pain, and I do know there are a lot of people that would like to do the same thing.

    Unfortunately, this is kinda out of the scope of this forum since signing on using facebook credentials is delegated authentication, and OAuth is about delegated access. There's a significant difference.

    Having said that, I can give you some code that I used for delegated authentication with Google credentials. Basically I implemented my own AuthenticationFilter:

    Code:
    package org.springframework.security.oauth.examples.tonr.servlet;
    
    import org.springframework.security.authentication.AuthenticationServiceException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.GrantedAuthorityImpl;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.*;
    
    /**
     * @author Ryan Heaton
     */
    public class GoogleAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
      public GoogleAuthenticationFilter() {
        super("/googleauth");
      }
    
      @Override
      public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String responseCode = request.getParameter("openid.mode");
        if (responseCode == null) {
          //initiate google login by sending the user to the google login endpoint with all the openid parameters that it needs.
    
          //we know that this is the google login endpoint. If we were afraid that google might change it on us, we'd want to first ask
          //for it by making a request to the xml document located at https://www.google.com/accounts/o8/id
          //or at least make it configurable. For now, we'll just hard-code it.
          String googleLoginEndpoint = "https://www.google.com/accounts/o8/ud";
    
          String loginUrl = new StringBuilder(googleLoginEndpoint)
    
            //standard required google openid parameters.
            .append("?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=checkid_setup&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select")
    
            //we want to decide what to ask google to send back to use about the user
            //for now, we'll just ask for the e-mail and pretend that's enough for us to let google users use our site.
            //but there are other things you can ask for, like firstname, lastname, country, and language
            //here's how to ask for the e-mail:
            .append("&openid.ns.ax=http://openid.net/srv/ax/1.0&openid.ax.mode=fetch_request&openid.ax.type.email=http://axschema.org/contact/email&openid.ax.required=email")
    
            //tell google what url the user needs to return to in order to finish login
            //for now, we'll just send them back to this url.
            .append("&openid.return_to=").append(request.getRequestURL()).append("&openid.realm=").append(request.getRequestURL())
            .toString();
    
          response.sendRedirect(loginUrl);
          return null;
        }
        else if ("id_res".equals(responseCode)) { //this is how we know that the login succeeded, per openid spec.
    
          //there has to be a decision made as to what roles to grant users who log-in using google creds. For now, we'll just hard-code "ROLE_USER"
          List<GrantedAuthority> authorities = Arrays.asList((GrantedAuthority) new GrantedAuthorityImpl("ROLE_USER"));
    
          String email = findEmail(request); //find the e-mail.
    
          if (email == null) {
            //we require at least the e-mail.
            throw new AuthenticationServiceException("no email supplied from Google.");
          }
    
          return new UsernamePasswordAuthenticationToken(new User(email, "", true, true, true, true, authorities), null, authorities);
        }
        else {
          //otherwise, login failed.
          throw new AuthenticationServiceException("Google login failed.");
        }
      }
    
      private String findEmail(HttpServletRequest request) {
        //the openid parameters that google sends back are namespaced.
        //we're looking for the "openid.NAMESPACE_ID.value.email" parameter
        //where "NAMESPACE_ID" is the id of the "http://openid.net/srv/ax/1.0" namespace.
    
        //first build up the map of namespaces to namespace ids.
        Enumeration paramNames = request.getParameterNames();
        Map<String, String> namespaceIds = new HashMap<String, String>();
        while (paramNames.hasMoreElements()) {
          String name = (String) paramNames.nextElement();
          if (name.startsWith("openid.ns.")) {
            namespaceIds.put(request.getParameter(name), name.substring("openid.ns.".length()));
          }
        }
    
        //now look up the id for the attribute exchange namespace.
        String attributeExchangeId = namespaceIds.get("http://openid.net/srv/ax/1.0");
    
        if (attributeExchangeId != null) {
          //return the value
          return request.getParameter("openid." + attributeExchangeId + ".value.email");
        }
    
        return null;
      }
    }
    created a bean in the application context:

    Code:
      <beans:bean id="googleAuthFilter" class="org.springframework.security.oauth.examples.tonr.servlet.GoogleAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authMan"/>
        <beans:property name="rememberMeServices" ref="rememberMeServices"/>
      </beans:bean>
    and introduced it into the filter chain as a custom filter:

    Code:
      <http ...>
        ...
        <custom-filter ref="googleAuthFilter" after="FORM_LOGIN_FILTER"/>
        ...
      </http>
    This is according to the instructions of the Google federated login API:

    http://code.google.com/apis/accounts/docs/OpenID.html

    Hope that helps.

    Comment


    • #3
      Thank you

      I'm sorry if it's not posted in the good section, but thank you very much for helping me with that, it's appreciated!

      Comment


      • #4
        Originally posted by stoicflame View Post
        This is according to the instructions of the Google federated login API:

        http://code.google.com/apis/accounts/docs/OpenID.html

        Hope that helps.
        Just as an FYI Spring Security does contain support for OpenID along with a sample application. Although I'm not sure if the OpenID support helps for authenticating with Facebook as I think they are only an OpenID relying party. I believe they use OAuth 2.0 for both authentication and authorization.

        Comment


        • #5
          This could be done to allow login in facebook/twitter using Spring-Oauth 2 since these sites authenticates the user before accessing their resources.

          Oauth Conf:
          Code:
          <oauth:consumer resource-details-service-ref="sampleResource"  oauth-failure-page="/oauth_error.jsp">
          			<oauth:url pattern="/OAuth*" resources="sample"/>
          </oauth:consumer>
          <oauth:resource-details-service id="sampleResource">
          			<oauth:resource id="sample"  
                                             request-token-url="http://twitter.com/oauth/request_token"  
                                             user-authorization-url="http://twitter.com/oauth/authorize"  
                                             access-token-url="http://twitter.com/oauth/access_token"  
                                             secret="..." key="..."></oauth:resource>
          </oauth:resource-details-service>
          Controller:
          Code:
          @Controller
          public class OAuthClientController {
          
          	@Autowired
          	private OAuthConsumerSupport oauthConsumerSupport;
          	@Autowired
          	private ProtectedResourceDetailsService resourceDetailsService;
          
          	@RequestMapping("/OAuthClient.htm")
          	protected ModelAndView handleRequestInternal(HttpServletRequest request,
          			HttpServletResponse response) throws Exception {
          		OAuthConsumerToken token = null;
          
          		List<OAuthConsumerToken> tokens = (List<OAuthConsumerToken>) request
          				.getAttribute("OAUTH_ACCESS_TOKENS");
          
          		if (tokens != null) {
          			for (OAuthConsumerToken consumerToken : tokens) {
          				if (consumerToken.getResourceId().equals("sample")) {
          					token = consumerToken;
          					break;
          				}
          			}
          		}
          
          		if (token == null) {
          			throw new IllegalArgumentException("Access token not found.");
          		}
          
          		// code to redirect to home page
                          modelAndView = ....
          		return modelAndView;
          	}
          }
          When OAuthClient.htm is accessed, request is intercepted and user can enter his username & password on twitter login screen. user is authenticated if valid token is returned.
          Last edited by vikrant chavan; Feb 3rd, 2011, 10:38 AM.

          Comment


          • #6
            Thanks vikrant chavan for sharing this code. It helped me a lot.

            For a clean sign in flow with twitter one would want to change the user-authorization-url from "https://api.twitter.com/oauth/autorize" to "https://api.twitter.com/oauth/authenticate". This way once the user has authorized the app twitter does not ask again to authorize it.

            Comment

            Working...
            X