Social Network Authentication for JavaServer Faces (JSF)

A lot has been written about the harm from passwords during authentication of site users and login through social networks. But basically it concerns either theoretical considerations, or some aspects of integrating a module into PHP into one of the CMS.

I bring to your attention an example of organizing authentication of users of the VKontakte, Facebook, My World, Yandex, Odnoklassniki, Google, Twitter, LinkedIn networks using JSF2. This is a completely authoring development, covering the most popular OAuth runet providers. It is written without using third-party libraries, servlets, jsp and springs - only JSF2 and CDI.

All this was born in the development of a paid site that provides training services. Naturally, in order to get people to part with at least a small part of their “hard-earned” ones, you really need to try hard. Complicated registration with passwords, e-mails, captchas and time zones does not greatly contribute to this. Therefore, the decision was made to be limited only to login and password (twice), no additional questions! In practice, this resulted in multiple registrations, forgetting the password, often after payment (as usual, no one remembers their personal account) and similar troubles. Almost no one filled out the questionnaire either - the support service was not sweet.

The solution suggested itself: to provide registration and subsequent authentication of users with “one click”. Social networks with OAuth technology were perfect. We already had authorization, it was only necessary to get some unique identifier. Which was done.

On the main page of our site there are two buttons "Login" and "Registration". Technically, they could be combined, since the user has no way to make a mistake - he does not enter any data manually. If we received an identifier through OAuth that is not yet in our database, it means we are registering a new user, otherwise we just let it into the system. For a new user, you can still show a greeting.

A meticulous reader will ask, why did you need to develop your solution, because for each social network there are already ready-made libraries that implement all the functionality? Yes there is. But for this “all”, in this case redundant, functionality, you have to pay an extra volume, numerous dependencies and the need to integrate and support different (for each library) approaches. Foreign bugs and vulnerabilities also do not need to be discounted.

So, let's take a closer look at how everything works


For the article on Habré, a demo project was developed - parts were bitten out of the working system and everything superfluous was removed, only the working model remained. At the bottom of the article are links to the project and the demonstration.

It all starts with login.xhtml, it looks like this:
image
Icons are taken from Habrapak . Each icon has a method call that forms a request and sends a user to the OAuth provider website using a redirect.

Sample code for Yandex:
	String plainUrl = "https://oauth.yandex.ru/authorize?" 
				+"client_id="+ yaId
				+"&response_type=code";
	FacesContext.getCurrentInstance().getExternalContext().redirect(plainUrl);


After the user has accepted the offer to give access to his data, he returns to our site, the host side is the page yaLogin.xhtml


As you can see, the received parameters code, error and state are passed to the class variables and the phase2 () method is launched, which actually conducts further processing.

Check if the error returned to us:
if (error!=null) {
	logger.info("error="+error);
	try {
	FacesContext.getCurrentInstance().getExternalContext().redirect("error.jsf");
	} catch (Exception e) {
		logger.log(Level.SEVERE, "phase2() Error: redirect failed!", e);
	}
	return "";
}


If everything is in order, we form a request for a token:
	Properties props = new Properties();
	props.put("grant_type", "authorization_code");
	props.put("code", code);
	props.put("client_id", yaId);
	props.put("client_secret", yaSecret);
	String ret1 = HttpURL.httpsPost("https://oauth.yandex.ru/token", props, "UTF-8");


And parsing the result:
	class YA{
		String access_token;
		String token_type;
	}
	YA ya = (YA) XStreamFactory.get(YA.class, ret1);


Having an access token, you can request information about the user:
String ret2 = HttpURL.httpsGet("https://login.yandex.ru/info?format=json&oauth_token="+ya.access_token);
	class PersonData {
		public String birthday;
		public String display_name;
		public String sex;
		public String id;
		public String default_email;
		public String real_name;
	}
	PersonData personData = (PersonData) XStreamFactory.get(PersonData.class, ret2);


Now you can transfer the received data to the Core object (dispatcher):
	core.getUserAutoReqProps().setEmail(personData.default_email);
	String[] fname = personData.real_name.split(" ");
	core.getUserAutoReqProps().setLastName(fname[0]);
	core.getUserAutoReqProps().setFirstName(fname[1]);
	core.getUserAutoReqProps().setSex("male".equalsIgnoreCase(personData.sex));
	DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
	core.getUserAutoReqProps().setBorn(dateFormat.parse(personData.birthday));


Remember the unique user ID and go to the protected part of the site:
	core.setValidatedId("http://my.ya.ru/"+personData.id);
	FacesContext.getCurrentInstance().getExternalContext().redirect("welcome.jsf");


All we have an authenticated user! Naturally, it still needs to be authorized, but this is beyond the scope of the article.

As you can see, to work with one OAuth provider, one java class and one xhtml page are enough. For Yandex, this will be YALogin.java and yaLogin.xhtml, for VKontakte VKLogin.java and vkLogin.xhtml, respectively, and so on, by analogy.

The problem of where to store Id and Secret obtained during registration of applications was solved as follows: in an external file on the file system of the application server. This gives the following advantages: the developers have data of test applications in the file and real ones on the server, nothing needs to be changed during the deployment and the secrecy is respected. An example oauth.properties file is in the project.
The file path is stored in web.xml:
OAuthPropertiesPath/opt/oauth/oauth.properties


When the application starts, the Action bean opens and parses this file.
@Named
@ApplicationScoped
public class OAuthDAO implements Serializable {
	private static final long serialVersionUID = 1L;
	private Properties prop;
	private static Logger logger = Logger.getLogger(OAuthDAO.class.getName());
	public OAuthDAO() {
		load();
	}
	public boolean load(){
		try (FileInputStream fis = new FileInputStream(FacesContext.getCurrentInstance().getExternalContext().getInitParameter("OAuthPropertiesPath"))){
			prop = new Properties();
			prop.load(fis);
			if (prop.isEmpty()) {
				return false;
			}
		} catch (Exception ex) {
			return false;
		}
		return true;
	}
	public String getProperty(String key) {
		return prop.getProperty(key);
	}
}


In the presented test application, after authentication is completed, the welcome.xhtml page is displayed, which symbolizes the protected part of the application. To prevent an unauthorized user from accessing this page, the core.fiscal () method is called by the preRenderView event, which checks.


The demo is running a GlassFish application server. For parsing JSON and XML, the XStream library was used.

See the application here .
Download the project under Eclipse here .

Also popular now: