Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Spring security social and Stormpath users repository

Prerequisites

This tutorial assumes that you have already created the Facebook and Google applications used by the example application. You can create these applications by following links:

  • Developers Facebook
  • Google Developers Console

Requirements

The requirements of our solution are the following:

  • It must be possible to login by using username and password Stormpath user’s repository.
  • The application must support Facebook and Google OAuth2 authentication.

Getting the Required Dependencies with Maven

The first thing that we have to do is to get the required dependencies with Maven. We can do this by declaring the following dependencies in our POM file:

  • Spring Security (version 3.2.0.RELEASE).
    • The core module contains core authentication and and access control components.
    • The config module contains the code used to parse XML configuration files using the Spring Security XML namespace.
    • The taglibs module contains the Spring Security JPS tag libraries.
    • The web module contains filters and all other code related to web security.
  • Apache HttpClient (version 4.3.2). Apache HttpClient is an optional dependency (but recommended) dependency of Spring Social. If it is present, Spring Social will use it as a HTTP client. If not, Spring social will use the standard Java SE components.
  • Spring Social (version 1.1.0.RELEASE).
    • The config module contains the code used to parse XML configuration files using the Spring Social XML namespace. It also adds support for Java Configuration of Spring Social.
    • The core module contains the connect framework and provides support for OAuth clients.
    • The security module integrates Spring Security with Spring Social. It delegates the authentication concerns typically taken care by Spring Security to service providers by using Spring Social.
    • The web module contains components which handle the authentication handshake between our web application and the service provider.
  • Spring Social Facebook (version 1.1.0.RELEASE) is an extension to Spring Social and it provides Facebook integration.
  • Spring Social Google (version 1.0.0.RELEASE) is an extension to Spring Social which provides Google integration.
  • Stormpath SDK core module
    • Stormpath SDK api module (version 1.0.RC2) provides core framework.
    • Stormpath http client module (version 1.0.RC2) provides connect framework.
    • Stormpath spring security module (version 0.3.0) is an extension of spring security which provides Stormpath integration.

The relevant part of the pom.xml file looks as follows:

<!-- Spring Security Dependencies -->
<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-config</artifactId>
 <version>${spring.security.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-taglibs</artifactId>
 <version>${spring.security.version}</version>
</dependency>
<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-web</artifactId>
 <version>${spring.security.version}</version>
</dependency>
<!-- Apache HttpClient as HTTP Client -->
<dependency>
 <groupId>org.apache.httpcomponents</groupId>
 <artifactId>httpclient</artifactId>
 <version>4.3.2</version>
</dependency>
<!-- Spring Social -->
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-config</artifactId>
 <version>1.1.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-core</artifactId>
 <version>1.1.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-security</artifactId>
 <version>1.1.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-web</artifactId>
 <version>1.1.0.RELEASE</version>
</dependency>
<!-- Spring Social Facebook -->
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-facebook</artifactId>
 <version>1.1.0.RELEASE</version>
</dependency>
<!-- Spring Social Google -->
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-google</artifactId>
 <version>1.0.0.RELEASE</version>
</dependency>
<!-- stormpath -->
<dependency>
 <groupId>com.stormpath.sdk</groupId>
 <artifactId>stormpath-sdk-api</artifactId>
 <version>${stormpath.sdk.version}</version>
</dependency>
<dependency>
 <groupId>com.stormpath.sdk</groupId>
 <artifactId>stormpath-sdk-httpclient</artifactId>
 <version>${stormpath.sdk.version}</version>
 <scope>runtime</scope>
</dependency>
 <!-- This next runtime dependency is only necessary if you have
 a REST API and you want to secure it with OAuth: -->
<dependency>
 <groupId>com.stormpath.sdk</groupId>
 <artifactId>stormpath-sdk-oauth</artifactId>
 <version>${stormpath.sdk.version}</version>
 <scope>runtime</scope>
</dependency>
<dependency>
 <groupId>com.stormpath.spring.security</groupId>
 <artifactId>stormpath-spring-security-core</artifactId>
 <version>${stormpath.spring.security.version}</version>
</dependency>

Creating the User Password Stormpath Authentication

Stormpath user authentication implementation is based on Stormpath spring security example. The full source code available for downloading here.

We have to create six components which are used during the authentication process. These components are:

  • We have create a class which contains application restful methods MainController.
  • We have to create a class which provide authority resolver GroupRoleGrantedAuthorityResolver. This class is used to determine whether or not authorized user is permitted to do something. It tryes to resolve role name by stormpath group name.
  • We have to create a class which implements the AccountBean. This class is used to hold user information.
  • We have to create a class which implements the CustomDataBean. This class is used to hold custom data user information.
  • We have to create a class which implements the CustomDataFieldBean. This class is used to hold custom data key/value data couple.
  • We have to create a class which implements the CustomDataManager. This class is used to create, read, store and delete custom data fields in Stormpath. Current implementation of CustomDataManager just read custom data from the Stormpath.

The content of the application restful methods MainController class looks as follows:

@Controller
public class MainController {
/**
 * define logger 
 */
 private final static Logger LOG = LoggerFactory.getLogger(MainController.class);
/**
 * define stormpath custom fields data manager reference 
 */
 @Autowired
 private CustomDataManager customDataManager;
/**
 * define message source reference 
 */
 @Autowired
 private MessageSource messageSource;
/**
 * Define controller method to handle default path requests 
 */
 @RequestMapping(value = "/", method = RequestMethod.GET)
 public String main(Model model) {
   StormpathUserDetails user = (StormpathUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
   try {
     String href = user.getProperties().get("href");
     CustomDataBean customData = customDataManager.getCustomData(href);
     AccountBean accountBean = new AccountBean(user, customData);
     model.addAttribute("account", accountBean);
   } catch (Exception e) {
     LOG.error("Can't open user data with username=" + user.getUsername());
     model.addAttribute("accountError", e.getMessage());
   }
   return "main.page";
 }
/**
 * Define controller method to handle social authentication failure requests 
 */
 @RequestMapping(value = "/login/social/failure")
 public String loginSocialFail(@RequestParam(value = "providerId") String providerId, Model model) {
   String messageKey = "login.social.error";
   if (SocialMediaService.FACEBOOK.getProviderId().equals(providerId)) {
     messageKey = "login.social.facebook.error";
   } else if (SocialMediaService.GOOGLE.getProviderId().equals(providerId)) {
     messageKey = "login.social.google.error";
   }
   model.addAttribute("social_login_error", messageSource.getMessage(messageKey, null, LocaleContextHolder.getLocale()));
   return "login.page";
 }
/**
 * Define controller method to handle stormpath user/password authentication failure requests 
 */
 @RequestMapping(value = "/login/stormpath/failure")
 public String loginStormPathFail(Model model) {
   model.addAttribute("stormpath_login_error", "stormpath_login_error");
   return "login.page";
 }
/**
 * Define controller method to handle login page request 
 */
 @RequestMapping(value = "/login")
 public String login(Model model) {
   return "login.page";
 }
}

The content of the authority resolver class looks as follows:

public class GroupRoleGrantedAuthorityResolver implements GroupGrantedAuthorityResolver {
 /**
 * map of user roles 
 */
 private Map<String, List<String>> rolesMap;
 public GroupRoleGrantedAuthorityResolver(Map<String, List<String>> roleMap) {
    if (roleMap == null || roleMap.size() == 0) {
      throw new IllegalArgumentException("roleMap property cannot be null or empty.");
    }
    this.rolesMap = roleMap;
 }
 public Map<String, List<String>> getRolesMap() {
    return rolesMap;
 }
 public void setRolesMap(Map<String, List<String>> roleMap) {
    if (roleMap == null || roleMap.size() == 0) {
      throw new IllegalArgumentException("roleMap property cannot be null or empty.");
    }
    this.rolesMap = roleMap;
 }
 @Override
 public Set<GrantedAuthority> resolveGrantedAuthorities(Group group) {
    Set<GrantedAuthority> set = new HashSet<GrantedAuthority>();
    String groupHref = group.getHref();
    //REST resource hrefs should never ever be null:
    if (groupHref == null) {
     throw new IllegalStateException("Group does not have an href property. This should never happen.");
    }
    if (rolesMap.containsKey(groupHref)) {
      for(String role : rolesMap.get(groupHref)) {
        set.add(new SimpleGrantedAuthority(role));
      }
    } else {
     throw new UnsupportedOperationException("No GrantedAuthority mapping found for " + group);
    }
    return set;
 }
}

The content of the account bean class with out getter and setter methods looks as follows:

public class AccountBean implements Serializable {
 private final static Logger LOG = LoggerFactory.getLogger(AccountBean.class);
 public static final DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
 public static final DateFormat dfDay = new SimpleDateFormat("dd");
 public static final DateFormat dfMonth = new SimpleDateFormat("MM");
 public static final DateFormat dfYear = new SimpleDateFormat("yyyy");
 public final static String USERNAME_KEY = "username";
 public final static String EMAIL_KEY = "email";
 public final static String CHANNEL_KEY = "channel";
 public final static String ABOUT_KEY = "about";
 public final static String BIRTH_DAY_KEY = "birthDay";
 public final static String GENDER_KEY = "gender";
 public final static String PHOTO_EXTENSION_KEY = "photoExtension";
 private String username;
 private String email;
 //custom fields
 private String channel;
 private String about;
 private String gender;
 private String day;
 private String month;
 private String year;
 private String photoExtension;
 public AccountBean() {}
 public AccountBean(StormpathUserDetails user, CustomDataBean customData) {
   try {
     username = user.getUsername();
     email = user.getProperties().get(EMAIL_KEY);
     for (CustomDataFieldBean customFiled : customData.getCustomDataFields()) {
         if (CHANNEL_KEY.equals(customFiled.getKey())) {
           channel = (String) customFiled.getValue();
         } else if (ABOUT_KEY.equals(customFiled.getKey())) {
           about = (String) customFiled.getValue();
         } else if (BIRTH_DAY_KEY.equals(customFiled.getKey())) {
            try {
               Date date = df.parse((String) customFiled.getValue());
              day = dfDay.format(date);
              month = dfMonth.format(date);
              year = dfYear.format(date);
            } catch (ParseException e) {
               LOG.error(e.getMessage());
            }
         } else if (GENDER_KEY.equals(customFiled.getKey())) {
            gender = (String) customFiled.getValue();
         } else if (PHOTO_EXTENSION_KEY.equals(customFiled.getKey())) {
            photoExtension = (String) customFiled.getValue();
         }
     }
   } catch (Exception e) {
     LOG.error(e.getMessage());
   }
 }
 public String getBirthDay() {
   String dateStr = day + "-" + month + "-" + year;
   return dateStr;
 }
}

The implementation of the custom data field’s holder looks as follows:

public class CustomDataBean {
 private List<CustomDataFieldBean> customDataFields = new ArrayList<CustomDataFieldBean>();
 public List<CustomDataFieldBean> getCustomDataFields() {
   return customDataFields;
 }
 public void setCustomDataFields(List<CustomDataFieldBean> customDataFields) {
   if (customDataFields == null) {
   throw new IllegalArgumentException("customDataFields cannot be null");
   }
   this.customDataFields = customDataFields;
 }
}

The content of the custom data key/value pair holder is very simple and looks as follows:

public class CustomDataFieldBean {
 private String key;
 private Object value;
 public CustomDataFieldBean(String aKey, Object aValue) {
   if (aKey == null) {
     throw new IllegalArgumentException("key cannot be null");
   }
   this.key = aKey;
   this.value = aValue;
 }
 public String getKey() {
   return key;
 }
 public Object getValue() {
   return value;
 }
}

The content of the custom data manager looks as follows:

@Service
public class CustomDataManager {
 @Autowired
 private Client stormpathClient;
 /**
 * Method that returns the custom data for the given account.
 *
 * @param accountHref the account href whose custom data is being requested
 * @return a {@link CustomDataBean} instance containing the custom data fields
 */
 @PreAuthorize("isAuthenticated() and principal.properties.get('href') == #accountHref") //Is the user the owner of the account to be edited?
 public CustomDataBean getCustomData(String accountHref) {
 if (!StringUtils.hasText(accountHref)) {
     throw new IllegalArgumentException("accountHref cannot be null or empty");
  }
  CustomData customData = stormpathClient.getResource(accountHref + "/customData", CustomData.class);
  List<CustomDataFieldBean> customDataFieldBeanList = new ArrayList<CustomDataFieldBean>();
  for (Map.Entry<String, Object> entry : customData.entrySet()) {
    customDataFieldBeanList.add(new CustomDataFieldBean(entry.getKey(), entry.getValue()));
  }
  CustomDataBean customDataBean = new CustomDataBean();
  customDataBean.setCustomDataFields(customDataFieldBeanList);
 return customDataBean;
 }
}

Creating the Social Stormpath Authentication

We are going to use the spring social authentication solutions and Stormpath spring authentication solution. The full source code available for downloading here.

Stormpath social authentication implementation based on Integrating Stormpath with Facebook and Google and  Social Login: Facebook & Google in One API Call descriptions.

According the description we have to implement the following steps:

  1. Create a Facebook or Google Directory in Stormpath to mirror social accounts.
  2. Assign the created directory to our application in Stormpath.
  3. Populate our directory with social accounts from Google or Facebook using the application’s accounts endpoint.

We have to create eight components which are used during the social authentication process. These components are:

  • We have to create a class which implements creation Stormpath social directories and assign created directory to our Stormpath application. This class ensures existent of the Facebook and Google Stormpath directories. If not it creates Facebook and Google Stormpath directories with provided Facebook and Google application id and secret.
  • We have to create a class which perform social authentication in the Stormpath.
  • We have to create a class which can build social user id by social connection data.
  • We have to create a helper locator class to locate Stormpath ProviderRequestFactory. This class is used to provide a builder to generate an attempt to create data in the Provider-based directory in Stormpath.
  • We have to create a class which holds Stormpath user details data and social user details data.
  • We have to create a class which allows to locate Stormpath user data by social user profile data.
  • We have to create a helper class which defines provider names.
  • We have to create a class which extends failure redirect URL with additional social provider URL parameters.

The content of the class which create Stormpath social directories looks as follows:

/**
 * Class to check and create stormpath social user directories 
 * and map Facebook or Google Directory to our application
 */
@Component("socialInitializer")
@Scope("singleton")
public class SocialInitializer {
 private final static Logger LOGGER = LoggerFactory.getLogger(SocialInitializer.class);
 public final static String FACEBOOK_DIRECTORY_NAME = "my-facebook-directory";
 public final static String FACEBOOK_DIRECTORY_DESCRIPTION = "A Facebook directory";
 public final static String GOOGLE_DIRECTORY_NAME = "my-google-directory";
 public final static String GOOGLE_DIRECTORY_DESCRIPTION = "A Google directory";
 @Autowired
 private Client stormpathClient;
 private Application application;
 @Value("${facebook.clientId}")
 private String fbId;
 @Value("${facebook.clientSecret}")
 private String fbSecret;
 @Value("${google.clientId}")
 private String gpId;
 @Value("${google.clientSecret}")
 private String gpSecret;
 @Value("${google.redirectUrl}")
 private String gpRedirectUrl;
 @Value("${stormpath.sdk.app.rest.url}")
 private String stormPathAppUrl;
 @PostConstruct
 public void initSocial() {
  try {
    if (getFBDirectory() == null) {
      //create Facebook directory in the stormpath
      Directory fbDirectory = createFBDirectory();
      //create mapping Facebook stormpath directory to the default stormpath directory
      createStoreMapping(fbDirectory);
    }
    if (getGooglePlusDirectory() == null) {
       //create Google directory in the stormpath
       Directory googleDirectory = createGooglePlusDirectory();
       //create mapping Google stormpath directory to the default stormpath directory
       createStoreMapping(googleDirectory);
    }
  } catch (Exception e){
     LOGGER.error(e.getMessage(), e);
  }
}
protected Directory createFBDirectory()throws Exception {
   Directory directory = stormpathClient.instantiate(Directory.class);
   directory.setName(FACEBOOK_DIRECTORY_NAME);
   directory.setDescription(FACEBOOK_DIRECTORY_DESCRIPTION);
   CreateDirectoryRequest request = Directories.newCreateRequestFor(directory).
   forProvider(Providers.FACEBOOK.builder()
     .setClientId(fbId)
     .setClientSecret(fbSecret)
     .build()
     ).build();
   Tenant tenant = stormpathClient.getCurrentTenant();
   directory = tenant.createDirectory(request);
   return directory;
 }
private void createStoreMapping(Directory directory){
   AccountStoreMapping accountStoreMapping = stormpathClient.instantiate(AccountStoreMapping.class)
   .setAccountStore(directory)
   .setApplication(getApplication());
   application.createAccountStoreMapping(accountStoreMapping);
 }
protected Directory createGooglePlusDirectory() throws Exception {
  Directory directory = stormpathClient.instantiate(Directory.class);
  directory.setName(GOOGLE_DIRECTORY_NAME);
  directory.setDescription(GOOGLE_DIRECTORY_DESCRIPTION);
  CreateDirectoryRequest request = Directories.newCreateRequestFor(directory).
  forProvider(Providers.GOOGLE.builder()
     .setClientId(gpId)
     .setClientSecret(gpSecret)
     .setRedirectUri(gpRedirectUrl)
     .build()
     ).build();
  Tenant tenant = stormpathClient.getCurrentTenant();
  directory = tenant.createDirectory(request);
  return directory;
}
public Directory getFBDirectory() throws Exception{
   return getDirectoryByName(FACEBOOK_DIRECTORY_NAME);
}
public Directory getGooglePlusDirectory() throws Exception{
   return getDirectoryByName(GOOGLE_DIRECTORY_NAME);
}
private Directory getDirectoryByName(String dirName) throws Exception{
   Directory result = null;
   Tenant tenant = stormpathClient.getCurrentTenant();
   DirectoryList dl = tenant.getDirectories();
   if (dl != null) {
     Iterator<Directory> di = dl.iterator();
     while(di.hasNext()) {
        Directory dEntry = di.next();
        if (dirName.equals(dEntry.getName())) {
          result = dEntry;
        }
     }
   }
   return result;
 }
private Application getApplication(){
   if (application == null) {
     this.application = stormpathClient.getDataStore().getResource(stormPathAppUrl, Application.class);
   }
   return this.application;
 }
}

The content of the class which implements Stormpath social user authentication looks as follows:

/**
 * Define social user authentication provider and populating user data in the stormpath user directory
 */
public class StormPathSocialAuthenticationProvider extends SocialAuthenticationProvider {
 private Client client;
 private String applicationRestUrl;
 private ProviderAccountRequestServiceLocator providerAccountRequestLocator;
 private Application application; //acquired via the client at runtime, not configurable by the StormpathAuthenticationProvider user
 private StormPathSocialUserDetailsService userDetailsService;
 public StormPathSocialAuthenticationProvider(
 UsersConnectionRepository usersConnectionRepository, StormPathSocialUserDetailsService userDetailsService) {
   super(usersConnectionRepository, userDetailsService);
   this.userDetailsService = userDetailsService;
 }
 /**
 * Authenticate user based on {@link org.springframework.social.security.SocialAuthenticationToken}
 */
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
   Assert.isInstanceOf(SocialAuthenticationToken.class, authentication, "unsupported authentication type");
   Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
   SocialAuthenticationToken authToken = (SocialAuthenticationToken) authentication;
   Connection<?> connection = authToken.getConnection();
   String userId = toUserId(connection);
   if (userId == null) {
     throw new BadCredentialsException("Unknown access token");
   }
   return stormpathAuthenticate(authToken, userId);
 }
 //implement stormpath authentication
 protected Authentication stormpathAuthenticate(SocialAuthenticationToken authToken, String userId){
   assertState();
   ProviderAccountRequest request = createAuthenticationRequest(authToken);
   Account account;
   try {
     account = authenticateAccount(request).getAccount();
   } catch (ResourceException e) {
     String msg = StringUtils.clean(e.getMessage());
     if (msg == null) {
       msg = StringUtils.clean(e.getDeveloperMessage());
     }
     if (msg == null) {
       msg = "Invalid login or password.";
     }
     throw new AuthenticationServiceException(msg, e);
   }
   if (account == null) {
     throw new UsernameNotFoundException("Empty account");
   }
   UserDetails userDetails = userDetailsService.loadUserByUserId(userId);
   if (userDetails == null) {
     throw new UsernameNotFoundException("Unknown connected account id");
   }
   Connection<?> connection = authToken.getConnection();
   return new SocialAuthenticationToken(connection, userDetails, authToken.getProviderAccountData(), userDetails.getAuthorities());
 }
 private void assertState() {
   if (this.client == null) {
     throw new IllegalStateException("Stormpath SDK Client instance must be configured.");
   }
   if (this.applicationRestUrl == null) {
     throw new IllegalStateException("\n\nThis application's Stormpath REST URL must be configured.\n\n " +
     "You may get your application's Stormpath REST URL as shown here:\n\n " +
     "http://www.stormpath.com/docs/application-rest-url\n\n" +
     "Copy and paste the 'REST URL' value as the 'applicationRestUrl' property of this class.");
   }
 }
 private ProviderAccountResult authenticateAccount(ProviderAccountRequest request){
   Application application = ensureApplicationReference();
   return application.getAccount(request);
 }
 protected ProviderAccountRequest createAuthenticationRequest(SocialAuthenticationToken authToken) {
   return providerAccountRequestLocator.getProviderAccountRequestByProviderId(authToken.getProviderId()).account()
   .setAccessToken(getAccessToken(authToken))
   .build();
 }
 protected String getAccessToken(SocialAuthenticationToken authToken){
   ConnectionData cd = authToken.getConnection().createData();
   return cd.getAccessToken();
 }
 //This is not thread safe, but the Client is, and this is only executed during initial Application
 //acquisition, so it is negligible if this executes a few times instead of just once.
 protected final Application ensureApplicationReference() {
   if (this.application == null) {
     String href = getApplicationRestUrl();
     this.application = client.getDataStore().getResource(href, Application.class);
   }
   return this.application;
 }
}

The content of the class which can build social user id by social connection data looks as follows:

/**
 * Class to construct social authentication id
 */
public class SocialConnectionSignUp implements ConnectionSignUp {
 public final static String USER_ID_SPLITTER = ":";
 public String execute(Connection<?> connection) {
   return getUserIdByConnection(connection);
 }
 protected String getUserIdByConnection(Connection<?> connection){
   UserProfile up = connection.fetchUserProfile();
   //connection id contains provider id and authenticated social user email
   return connection.getKey().getProviderId() + USER_ID_SPLITTER + up.getEmail();
 }
}

The content of the class which can locate Stormpath ProviderRequestFactor looks as follows:

/**
 * Class to search ProviderRequestFactory by social provider id
 */
 public class ProviderAccountRequestServiceLocator {
   ProviderRequestFactory getProviderAccountRequestByProviderId(String providerId){
   if (SocialMediaService.FACEBOOK.getProviderId().equals(providerId)) {
    return Providers.FACEBOOK;
   }
   if (SocialMediaService.GOOGLE.getProviderId().equals(providerId)) {
    return Providers.GOOGLE;
   }
   throw new IllegalArgumentException("Can not detect Provider Request Factory By Provider Id :" + providerId);
 }
}

The content of the class which hold Stormpath user details data and social user details data looks as follows:

/**
 * This class combine stormpath and social user details data
 * Why stormpath does not define StormpathUserDetails interface?
 */
public class SocialStormpathUserDetails extends StormpathUserDetails implements SocialUserDetails {
 public final static String DEFAULT_SOCIAL_PASSWORD = "SocialUser";
 private String userId;
 private String email;
 private String firstName;
 private String lastName;
 private GroupList groups;
 private SocialMediaService socialSignInProvider;
 public SocialStormpathUserDetails(String userId,
                                   String email,
                                   String firstName,
                                   String lastName,
                                   GroupList groups,
                                   SocialMediaService socialSignInProvider,
                                   String username,
                                   String password, Collection<? extends GrantedAuthority> authorities,
                                   Account account) {
  super(username, ensurePassword(password), authorities, account);
  this.userId = userId;
  this.email = email;
  this.firstName = firstName;
  this.lastName = lastName;
  this.groups = groups;
  this.socialSignInProvider = socialSignInProvider;
 }
 public static String ensurePassword(String password){
  if (password == null) {
    return DEFAULT_SOCIAL_PASSWORD;
  }
  return password;
 }
}

The content of the class which allow locating Stormpath user data by social user profile data looks as follows:

/**
 * Class provide social user stormpath details service which populates user data in the stormpath user directory
 */
public class StormPathSocialUserDetailsService implements SocialUserDetailsService {
 private static final Logger LOGGER = LoggerFactory.getLogger(StormPathSocialUserDetailsService.class);
 public final static String QUERY_USEER_ID_PARAM_NAME = "email";
 private Client client;
 private String applicationRestUrl;
 private Application application; //acquired via the client at runtime, not configurable by the StormpathAuthenticationProvider user
 private AccountGrantedAuthorityResolver accountGrantedAuthorityResolver;
 private GroupGrantedAuthorityResolver groupGrantedAuthorityResolver;
 private GroupPermissionResolver groupPermissionResolver;
 private AccountPermissionResolver accountPermissionResolver;
 public StormPathSocialUserDetailsService(){
  initDefaultGroupGrantedAuthorityResolver();
  setGroupPermissionResolver(new GroupCustomDataPermissionResolver());
  setAccountPermissionResolver(new AccountCustomDataPermissionResolver());
 }
 protected void initDefaultGroupGrantedAuthorityResolver(){
  DefaultGroupGrantedAuthorityResolver defaultGroupGrantedAuthorityResolver = new DefaultGroupGrantedAuthorityResolver();
  Set<DefaultGroupGrantedAuthorityResolver.Mode> modes = new HashSet<DefaultGroupGrantedAuthorityResolver.Mode>();
  modes.add(DefaultGroupGrantedAuthorityResolver.Mode.NAME);
  defaultGroupGrantedAuthorityResolver.setModes(modes);
  setGroupGrantedAuthorityResolver(defaultGroupGrantedAuthorityResolver);
 }
 /**
 * Loads the username by using the account ID of the user.
 * @param userId The account ID of the requested user.
 * @return The information of the requested user.
 * @throws UsernameNotFoundException Thrown if no user is found.
 * @throws DataAccessException
 */
 @Override
 public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
  StringBuilder sbProviderId = new StringBuilder();
  StringBuilder sbUserId = new StringBuilder();
  parseUserId(userId, sbProviderId, sbUserId);
  Account account = populateSocialAccount(sbUserId.toString());
  if (account == null) {
    String error = "No user found with user id: " + sbUserId.toString();
    LOGGER.error(error);
     throw new UsernameNotFoundException(error);
  }
  SocialStormpathUserDetails userDetails = new SocialStormpathUserDetails(
      userId,
      account.getEmail(),
      account.getGivenName(),
      account.getSurname(),
      account.getGroups(),
      SocialMediaService.getByProviderId(sbProviderId.toString()),
      account.getUsername(),
      null,
      getGrantedAuthorities(account),
      account
    );
   return userDetails;
 }
 private Account populateSocialAccount(String userId){
  Map<String, Object> queryParams = new HashMap<String, Object>();
  queryParams.put(QUERY_USEER_ID_PARAM_NAME, userId);
  AccountList accounts = ensureApplicationReference().getAccounts(queryParams);
  if (accounts.iterator().hasNext()) {
    Iterator<Account> acIt = accounts.iterator();
    while (acIt.hasNext()) {
      Account account = acIt.next();
       if (!SocialInitializer.FACEBOOK_DIRECTORY_NAME.equals(account.getDirectory().getName()) &&
          !SocialInitializer.GOOGLE_DIRECTORY_NAME.equals(account.getDirectory().getName())) {
          return account;
       }
    }
  }
  return null;
  }
  //This is not thread safe, but the Client is, and this is only executed during initial Application
  //acquisition, so it is negligible if this executes a few times instead of just once.
  protected final Application ensureApplicationReference() {
  if (this.application == null) {
   String href = getApplicationRestUrl();
   this.application = client.getDataStore().getResource(href, Application.class);
  }
  return this.application;
 }
 protected Collection<GrantedAuthority> getGrantedAuthorities(Account account) {
  Collection<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();
  GroupList groups = account.getGroups();
  for (Group group : groups) {
    Set<GrantedAuthority> groupGrantedAuthorities = resolveGrantedAuthorities(group);
    grantedAuthorities.addAll(groupGrantedAuthorities);
    Set<Permission> groupPermissions = resolvePermissions(group);
    grantedAuthorities.addAll(groupPermissions);
  }
   Set<GrantedAuthority> accountGrantedAuthorities = resolveGrantedAuthorities(account);
   grantedAuthorities.addAll(accountGrantedAuthorities);
   Set<Permission> accountPermissions = resolvePermissions(account);
   for (GrantedAuthority permission : accountPermissions) {
     grantedAuthorities.add(permission);
   }
    return grantedAuthorities;
 }
 private Set<GrantedAuthority> resolveGrantedAuthorities(Group group) {
   if (groupGrantedAuthorityResolver != null) {
     return groupGrantedAuthorityResolver.resolveGrantedAuthorities(group);
  }
   return Collections.emptySet();
 }
 private Set<GrantedAuthority> resolveGrantedAuthorities(Account account) {
   if (accountGrantedAuthorityResolver != null) {
     return accountGrantedAuthorityResolver.resolveGrantedAuthorities(account);
   }
   return Collections.emptySet();
 }
 private Set<Permission> resolvePermissions(Group group) {
  if (groupPermissionResolver != null) {
     return groupPermissionResolver.resolvePermissions(group);
  }
  return Collections.emptySet();
 }
 private Set<Permission> resolvePermissions(Account account) {
   if (accountPermissionResolver != null) {
     return accountPermissionResolver.resolvePermissions(account);
  }
  return Collections.emptySet();
 }
 private void parseUserId(String userId, StringBuilder sbProviderId, StringBuilder sbUserId){
  if (!userId.contains(SocialConnectionSignUp.USER_ID_SPLITTER)) {
    String error = "Usere id " + userId +    
       " does not contain splitter value:" + SocialConnectionSignUp.USER_ID_SPLITTER;   
    LOGGER.error(error);
    throw new IllegalArgumentException(error);
  }
  String[] parts = userId.split(SocialConnectionSignUp.USER_ID_SPLITTER);
  sbProviderId.append(parts[0]);
  sbUserId.append(parts[1]);
 }
}

The content of the helper enum class which define authentication provider name strings looks as follows:

/**
 * Define supported authentication providers
 */
public enum SocialMediaService {
 STORMPATH("stormpath"),
 FACEBOOK("facebook"),
 GOOGLE("google");
 private static final Map<String, SocialMediaService> MODES = new HashMap<String, SocialMediaService>();
static {
     for (final SocialMediaService type : EnumSet.allOf(SocialMediaService.class)) {
        MODES.put(type.providerId, type);
     } 
 }
 private String providerId;
  SocialMediaService(String providerId) {
  this.providerId = providerId;
 }
 public String getProviderId(){
  return this.providerId;
 }
 public static SocialMediaService getByProviderId(String providerId){
  return MODES.get(providerId);
 }
}

The content of the class which implements failure redirect strategy looks as follows:

/**
 * Class to implement basic Failure Redirect Strategy and add additional social provider data to the redirect request
 */
public class SocialFailureRedirectStrategy extends DefaultRedirectStrategy{
 private boolean contextRelative;
 public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
  String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
  redirectUrl = response.encodeRedirectURL(redirectUrl);
  if (logger.isDebugEnabled()) {
    logger.debug("Redirecting to '" + redirectUrl + "'");
  }
  String providerId = SocialMediaService.FACEBOOK.getProviderId();
  if (request.getServletPath().contains(SocialMediaService.GOOGLE.getProviderId())) {
    providerId = SocialMediaService.GOOGLE.getProviderId();
  }
  redirectUrl += "?providerId="+providerId;
  response.sendRedirect(redirectUrl);
 }
private String calculateRedirectUrl(String contextPath, String url) {
  if (!UrlUtils.isAbsoluteUrl(url)) {
    if (contextRelative) {
      return url;
    } else {
      return contextPath + url;
    }
  }
  // Full URL, including http(s)://
  if (!contextRelative) {
    return url;
  }
  // Calculate the relative URL from the fully qualified URL, minus the scheme and base context.
  url = url.substring(url.indexOf("://") + 3); // strip off scheme
  url = url.substring(url.indexOf(contextPath) + contextPath.length());
  if (url.length() > 1 && url.charAt(0) == '/') {
    url = url.substring(1);
  }
  return url;
 }
 public void setContextRelative(boolean useRelativeContext) {
  super.setContextRelative(useRelativeContext);
  this.contextRelative = useRelativeContext;
 }
}

Application Configuration

We are going to configure the following systems:

  • Application property files.
  • Spring beans and security.
  • Facebook and Google applications.

Creating the Properties File

We have to create the properties file by following steps:

  1. Create a file called apiKey.properties Stormpath security keys file and ensure that it will accessible from the deployed application.
  2. Create a file called local.properties - localhost application settings and ensure that it is placed in to the src “<source app home>/main/filters” folder.
  3. Add the Facebook application id and application secret to the properties file.
  4. Add the Google consumer key and consumer secret to the properties file.

The content of the apiKey.properties file looks as follows:

apiKey.id = &lt;stormpath api key id&gt;
apiKey.secret = &lt;stormpath api secret&gt;

The content of the local.properties file looks as follows:

stormpath.api.key.file.location=&lt;path to the apiKey.properties file&gt;
stormpath.sdk.app.rest.url=https://api.stormpath.com/v1/applications/&lt;stormpath app id&gt;
stormpath.sdk.user.role.rest.url=https://api.stormpath.com/v1/groups/&lt;stormpath users id&gt;
facebook.clientId=&lt;facebook client id&gt;
facebook.clientSecret=&lt;facebook client secret&gt;
google.clientId=&lt;google client id&gt;
google.clientSecret=&lt;google client secret&gt;
google.redirectUrl=https://localhost:8080/auth/google

Creating the Spring Configuration

The spring configuration implemented as a xml configuration.

The Stormpath client settings and cached beans defined in the root-context.xml:

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:cache="http://www.springframework.org/schema/cache"
 xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 http://www.springframework.org/schema/cache
 http://www.springframework.org/schema/cache/spring-cache-4.0.xsd" &gt;
   &lt;!-- Root Context: defines shared resources visible to all other web components --&gt;
   &lt;bean id="stormpathClient" class="com.stormpath.spring.security.client.ClientFactory" &gt;
    &lt;property name="apiKeyFileLocation" value="${stormpath.api.key.file.location}" /&gt;
    &lt;property name="cacheManager" ref="cacheManager" /&gt;
   &lt;/bean&gt;
   &lt;!-- Generic cache manager based on the JDK ConcurrentMap --&gt;
   &lt;bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"&gt;
    &lt;property name="caches"&gt;
      &lt;set&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.application.Application" /&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.account.Account" /&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.group.Group" /&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.directory.CustomData" /&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.tenant.Tenant" /&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.directory.Directory" /&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.application.AccountStoreMapping" /&gt;
        &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="com.stormpath.sdk.impl.provider.ProviderAccountResultHelper" /&gt;
      &lt;/set&gt;
    &lt;/property&gt;
   &lt;/bean&gt;
   &lt;cache:annotation-driven cache-manager="cacheManager" /&gt;
&lt;/beans&gt;

Spring security configuration defined in the spring-security.xml file:

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans:beans xmlns="http://www.springframework.org/schema/security"
 xmlns:beans="http://www.springframework.org/schema/beans"
 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-4.0.xsd
 http://www.springframework.org/schema/security
 http://www.springframework.org/schema/security/spring-security-3.2.xsd" &gt;
 &lt;beans:import resource="mvc-dispatcher-servlet.xml" /&gt;
  &lt;!-- We are using our own permission evaluation for WildcardPermissions --&gt;
  &lt;beans:bean id="permissionEvaluator" class="com.stormpath.spring.security.authz.permission.evaluator.WildcardPermissionEvaluator"/&gt;
  &lt;beans:bean id="methodExpressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"&gt;
  &lt;!-- Let's use our own permission evaluation for WildcardPermissions --&gt;
  &lt;beans:property name="permissionEvaluator" ref="permissionEvaluator"/&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="webExpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler"&gt;
  &lt;!-- Let's use our own permission evaluation for WildcardPermissions --&gt;
  &lt;beans:property name="permissionEvaluator" ref="permissionEvaluator"/&gt;
  &lt;/beans:bean&gt;
  &lt;!-- When mapping to domain-specific role names (ie. using GroupRoleGrantedAuthorityResolver) --&gt;
  &lt;beans:bean id="groupRoleGrantedAuthoritiesMap" class="java.util.HashMap" scope="prototype"&gt;
    &lt;beans:constructor-arg&gt;
      &lt;beans:map key-type="java.lang.String" value-type="java.util.List"&gt;
        &lt;beans:entry key="${stormpath.sdk.user.role.rest.url}" value="ROLE_USER"/&gt;
      &lt;/beans:map&gt;
    &lt;/beans:constructor-arg&gt;
  &lt;/beans:bean&gt;
  &lt;!-- When mapping to domain-specific role names --&gt;
  &lt;beans:bean id="groupGrantedAuthorityResolver"
    class="com.spring.social.security.stormpath.authentication.GroupRoleGrantedAuthorityResolver"&gt;
  &lt;beans:constructor-arg ref="groupRoleGrantedAuthoritiesMap"/&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="authenticationProvider" class="com.stormpath.spring.security.provider.StormpathAuthenticationProvider"&gt;
    &lt;beans:property name="client" ref="stormpathClient" /&gt;
    &lt;beans:property name="applicationRestUrl" value="${stormpath.sdk.app.rest.url}"/&gt;
    &lt;!-- When mapping to domain-specific role names (ie. using GroupRoleGrantedAuthorityResolver) --&gt;
    &lt;beans:property name="groupGrantedAuthorityResolver" ref="groupGrantedAuthorityResolver" /&gt;
  &lt;/beans:bean&gt;
  &lt;http pattern="/login/**" security="none" /&gt;
  &lt;http pattern="/images/**" security="none" /&gt;
  &lt;http pattern="/js/**" security="none" /&gt;
  &lt;http pattern="/css/**" security="none" /&gt;
  &lt;http auto-config='true' use-expressions="true" &gt;
    &lt;intercept-url pattern="/**" access="hasRole('ROLE_USER')" /&gt;
    &lt;intercept-url pattern="/auth/**" access="permitAll"/&gt;
    &lt;form-login login-page="/login" default-target-url="/" authentication-failure-url="/login/stormpath/failure"/&gt;
    &lt;logout logout-url="/logout" logout-success-url="/login"/&gt;
    &lt;!-- Adds social authentication filter to the Spring Security filter chain. --&gt;
    &lt;custom-filter ref="socialAuthenticationFilter" before="PRE_AUTH_FILTER" /&gt;
  &lt;/http&gt;
  &lt;global-method-security pre-post-annotations="enabled" secured-annotations="enabled"&gt;
    &lt;expression-handler ref="methodExpressionHandler"/&gt;
  &lt;/global-method-security&gt;
  &lt;authentication-manager alias="authenticationManager"&gt;
    &lt;authentication-provider ref='authenticationProvider'/&gt;
    &lt;authentication-provider ref="socialAuthenticationProvider"/&gt;
  &lt;/authentication-manager&gt;
  &lt;beans:bean id="socialUserDetailsService"
    class="com.spring.social.security.stormpath.authentication.StormPathSocialUserDetailsService"&gt;
    &lt;beans:property name="client" ref="stormpathClient" /&gt;
    &lt;beans:property name="applicationRestUrl" value="${stormpath.sdk.app.rest.url}"/&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="providerAccountRequestLocator"
     class="com.spring.social.security.stormpath.authentication.ProviderAccountRequestServiceLocator"/&gt;
  &lt;beans:bean id="socialAuthenticationProvider" class="com.spring.social.security.stormpath.authentication.StormPathSocialAuthenticationProvider"&gt;
    &lt;beans:constructor-arg index="0" ref="usersConnectionRepository"/&gt;
    &lt;beans:constructor-arg index="1" ref="socialUserDetailsService"/&gt;
  &lt;beans:property name="client" ref="stormpathClient" /&gt;
  &lt;beans:property name="providerAccountRequestLocator" ref="providerAccountRequestLocator" /&gt;
  &lt;beans:property name="applicationRestUrl" value="${stormpath.sdk.app.rest.url}"/&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="socialFailureRedirectStrategy" class="com.spring.social.security.stormpath.authentication.SocialFailureRedirectStrategy"/&gt;
  &lt;beans:bean id="socialAuthenticationFailureDelegateHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"&gt;
    &lt;beans:constructor-arg index="0" value="/login/social/failure"/&gt;
    &lt;beans:property name="redirectStrategy" ref="socialFailureRedirectStrategy" /&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="socialAuthenticationFailureHandler" class="org.springframework.social.security.SocialAuthenticationFailureHandler"&gt;
    &lt;beans:constructor-arg index="0" ref="socialAuthenticationFailureDelegateHandler"/&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="socialAuthenticationFilter" class="org.springframework.social.security.SocialAuthenticationFilter"&gt;
    &lt;beans:constructor-arg index="0" ref="authenticationManager"/&gt;
    &lt;beans:constructor-arg index="1" ref="userIdSource"/&gt;
    &lt;beans:constructor-arg index="2" ref="usersConnectionRepository"/&gt;
    &lt;beans:constructor-arg index="3" ref="connectionFactoryLocator"/&gt;
    &lt;beans:property name="defaultFailureUrl" value="/login/social/failure"/&gt;
    &lt;beans:property name="postLoginUrl" value="/"/&gt;
    &lt;!-- Sets the url of the registration form. --&gt;
    &lt;beans:property name="signupUrl" value="/user/register"/&gt;
    &lt;beans:property name="authenticationFailureHandler" ref="socialAuthenticationFailureHandler"/&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="userIdSource" class="org.springframework.social.security.AuthenticationNameUserIdSource"/&gt;
  &lt;beans:bean id="connectionFactoryLocator"
    class="org.springframework.social.security.SocialAuthenticationServiceRegistry"&gt;
    &lt;beans:property name="authenticationServices"&gt;
      &lt;beans:list&gt;
          &lt;beans:bean class="org.springframework.social.facebook.security.FacebookAuthenticationService"&gt;
            &lt;beans:constructor-arg value="${facebook.clientId}" /&gt;
            &lt;beans:constructor-arg value="${facebook.clientSecret}" /&gt;
            &lt;beans:property name="defaultScope" value="email,public_profile" /&gt;
        &lt;/beans:bean&gt;
        &lt;beans:bean class="org.springframework.social.google.security.GoogleAuthenticationService"&gt;
           &lt;beans:constructor-arg value="${google.clientId}" /&gt;
          &lt;beans:constructor-arg value="${google.clientSecret}" /&gt;
          &lt;beans:property name="defaultScope" value="https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email" /&gt;
        &lt;/beans:bean&gt;
      &lt;/beans:list&gt;
    &lt;/beans:property&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="usersConnectionRepository"
    class="org.springframework.social.connect.mem.InMemoryUsersConnectionRepository"&gt;
    &lt;beans:constructor-arg ref="connectionFactoryLocator" /&gt;
    &lt;beans:property name="connectionSignUp" ref="socialConnectionSignUp" /&gt;
  &lt;/beans:bean&gt;
  &lt;beans:bean id="socialConnectionSignUp"
    class="com.spring.social.security.stormpath.authentication.SocialConnectionSignUp"/&gt;
  &lt;beans:bean id="connectionRepository"
    class="org.springframework.social.connect.mem.InMemoryConnectionRepository"&gt;
    &lt;beans:constructor-arg ref="connectionFactoryLocator" /&gt;
  &lt;/beans:bean&gt;
&lt;/beans:beans&gt;

Creating the Social Configuration

Facebook application configuration looks as follows:

Google application configuration looks as follows:

Google application permissions looks as follows:

 

Test Application

The application does not implement users sign up. Before testing we should create three Stormpath components. These components are:

  • Stormpath directory with name ”My Application Directory”.
  • Strompath group with name “ROLE_USER” .
  • Create account for test and add this account to the “My Application Directory” directory and to the “ROLE_USER” group.

Account should  has email, user name and password. The Stormpath account email should match to Facebook and Google accounts.

The content of login page looks as follows:

The html of login page looks as follows:

&lt;div id="mask"&gt;
  &lt;section class="login-area"&gt;
   &lt;section class="social-login"&gt;
    &lt;figure&gt;
     &lt;a href="${pageContext.request.contextPath}/auth/facebook"&gt;
      &lt;img src="${pageContext.request.contextPath}/images/facebook-login.png" alt=""&gt;
     &lt;/a&gt;
    &lt;/figure&gt;
    &lt;figure&gt;
     &lt;a href="${pageContext.request.contextPath}/auth/google"&gt;
      &lt;img src="${pageContext.request.contextPath}/images/google-login.png" alt=""&gt;
     &lt;/a&gt;
    &lt;/figure&gt;
   &lt;/section&gt;
   &lt;c:if test="${not empty social_login_error}"&gt;
    &lt;p class="login-social-error"&gt;${social_login_error}&lt;/p&gt;
   &lt;/c:if&gt;
   &lt;p class="line-with-text"&gt;&lt;span&gt;or&lt;/span&gt;&lt;/p&gt;
   &lt;section class="login-box"&gt;
     &lt;c:if test="${not empty stormpath_login_error}"&gt;
      &lt;p class="login-error"&gt;&lt;spring:message key="login.error"/&gt;&lt;/p&gt;
     &lt;/c:if&gt;
     &lt;form name="login" method="post" class="signin"
       action="${pageContext.request.contextPath}/j_spring_security_check"&gt;
       &lt;fieldset class="textbox"&gt;
        &lt;label class="username"&gt;
          &lt;spring:message key="username.placeholder" var="username_placeholder"/&gt;
          &lt;input id="username" name="j_username" type="text" autocomplete="on" placeholder="${username_placeholder}" required&gt;
        &lt;/label&gt;
        &lt;label class="password"&gt;
         &lt;spring:message key="password.placeholder" var="pass_placeholder"/&gt;
         &lt;input id="password" name="j_password" type="password" placeholder="${pass_placeholder}" required onKeyPress="return submitenter(this,event)"&gt;
        &lt;/label&gt;
        &lt;button id="submit-login-form" class="submit button" type="button" onclick="document.login.submit();"&gt;&lt;spring:message key="login.button.label"/&gt;&lt;/button&gt;
       &lt;/fieldset&gt;
     &lt;/form&gt;
   &lt;/section&gt;
  &lt;/section&gt;
&lt;/div&gt;

The content of secured page looks as follows:

 

The source code for downloading available here:  spring-social-security-stormpath-src.zip.

The post Spring security social and Stormpath users repository appeared first on Softteco.



This post first appeared on SoftTeco - Enterprise Mobile App Development Company, please read the originial post: here

Share the post

Spring security social and Stormpath users repository

×

Subscribe to Softteco - Enterprise Mobile App Development Company

Get updates delivered right to your inbox!

Thank you for your subscription

×