📜 ⬆️ ⬇️

Authentication via social networks for JavaServer Faces (JSF)

About the harm from passwords when authenticating users of the site and logging in through social networks has already been written a lot. But basically it concerns either theoretical reasoning, or some aspects of the integration of the module in PHP into one of the CMS.

I bring to your attention an example of the organization of authentication of users of the networks of VKontakte, Facebook, My World, Yandex, Odnoklassniki, Google, Twitter, LinkedIn using JSF2. This is a fully authoring, covering the most popular OAuth providers of the RuNet. Written without the use of 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 make people part with even a small part of their “hard-earned” ones, you need to really try. Difficult registration with passwords, e-mails, captcha and time zones is not very helpful. Therefore, it was decided to limit only to the 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 his personal account) and similar troubles. Almost no one filled out the questionnaire either, the support service had not sweet.

The decision was self-evident: to ensure registration and subsequent authentication of users with one click. Social networking with OAuth technology was perfect. We already had an authorization, it was necessary only to get some unique identifier. What 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 opportunity to make a mistake - he does not enter any data manually. If we received an identifier through OAuth, which is not yet in our database, it means that we register a new user, otherwise we just let it into the system. For the new user, you can still show a greeting.

A meticulous reader will ask why it was necessary to develop his own solution, because for each social network there are already ready libraries that implement all the functionality? Yes there is. But for this “all”, in this case, redundant, the functionality has to be paid with extra volume, numerous dependencies and the need to integrate and support different (for each library) approaches. Other people's bugs and vulnerabilities also do not need to be discounted.

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


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

It all starts with login.xhtml, it looks like this:
image
Icons taken from Habrapack . Each icon is assigned a call to a method that makes a request and sends the user to the OAuth provider site via 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 agreed to give access to his data, he returns to our site, the receiving party has a page yaLogin.xhtml
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head></h:head> <f:metadata> <f:viewParam id="code" name="code" value="#{yalogin.code}" /> <f:viewParam id="error" name="error" value="#{yalogin.error}" /> <f:viewParam id="state" name="state" value="#{yalogin.state}" /> <f:event type="preRenderView" listener="#{yalogin.phase2()}" /> </f:metadata> <body> </body> </html> 


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

Check if the error was 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, form a request for obtaining 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 parsim result:
  class YA{ String access_token; String token_type; } YA ya = (YA) XStreamFactory.get(YA.class, ret1); 


Having 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 secure 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 must also 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, it will be YALogin.java and yaLogin.xhtml, for VKontakte, respectively, VKLogin.java and vkLogin.xhtml, and so on, by analogy.

The problem where to store Id and Secret obtained when registering applications was solved as follows: in an external file on the application server file system. This gives the following advantages: the developers in the file have the data of test applications and on the server - the real ones, with a complete no need to change anything and secrecy is respected. An example of the oauth.properties file is in the project.
The file path is stored in web.xml:
 <context-param> <param-name>OAuthPropertiesPath</param-name> <param-value>/opt/oauth/oauth.properties</param-value> </context-param> 


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 getting to this page, the core.fiscal () method is called on the preRenderView event and performs a check.
 <f:metadata> <f:event listener="#{core.fiscal()}" type="preRenderView" /> </f:metadata> 


The demo runs on the GlassFish application server. The XStream library was used to parse JSON and XML.

View the work of the application here .
Download the Eclipse project here .

Source: https://habr.com/ru/post/187396/


All Articles