Olá pessoal, boa noite.
Estou desenvolvendo um sistema estilo rede social, no qual permite a integração com o Facebook. A motivação para escrever esse artigo foi a dificuldade em encontrar materiais que permitissem a integração entre o Spring Social e o Spring Security.
O primeiro passo é entrar na sua conta do Facebook e criar uma aplicação na área de desenvolvedores. Com a aplicação configurada, você está permitindo o canal de comunicação entre o usuário que está realizando a autenticação com o Facebook e a aplicação vinculada à sua conta no servidor. Para criar a aplicação, basta seguir os passos abaixo:
– Com seu usuário logado, acesse a URL https://developers.facebook.com/apps. Clique na opção “Criar novo aplicativo”.
– Uma janela é exibida, onde você deverá informar os detalhes da aplicação. Se desejar editar as informações, basta clicar na opção “Editar aplicativo”. A sua configuração deve ficar bem parecida com esta:
App ID/API Key: xxxxxxxxxxxxxxxxxxxxxxxxx (Seu identificador gerado pelo Facebook)
App Secret: xxxxxxxxxxxxxxxxxxxxxxxxx (Sua chave secreta gerada pelo Facebook)
App Namespace: encontronoivas (nome da sua aplicação)
Site URL: http://localhost:8080/ (Sua URL local para testes, no caso localhost e a porta do servidor de aplicações)
Página Canvas: http://apps.facebook.com/encontronoivas/ (URL da sua aplicação)
Canvas URL: http://localhost:8080/ (Sua URL local para testes, no caso localhost e a porta do servidor de aplicações)
Canvas FBML/iframe: iFrame
O segundo passo é baixar o Spring Social em: http://s3.amazonaws.com/dist.springframework.org/release/SOCIAL/spring-social-1.0.2.RELEASE.zip e o Spring Social Facebook em: http://s3.amazonaws.com/dist.springframework.org/release/SOCIAL-FACEBOOK/spring-social-facebook-1.0.1.RELEASE.zip.
Obs: Entende-se que o usuário já possua o Spring Security no projeto. Caso não possua, verificar como configurar o Spring Security em outros posts aqui no Blog. Esse passo é essencial para realizar essa integração, visto que algumas libs são obrigatórias (dependentes).
Com as libs inseridas no projeto em WEB-INF/lib, o próximo passo é realizar a comunicação entre a conta Facebook do usuário e a aplicação web. Para isso, duas páginas foram criadas.
A primeira é uma página simples (login.xhtml) com um botão responsável por chamar a página do Facebook para que o usuário informe o login e a senha. Ao informar os dados, uma mensagem é exibida ao usuário perguntando se o mesmo deseja permitir que a aplicação acesse os dados de sua conta Facebook. Ao permitir, o usuário é redirecionado para a página (loginAuthenticate.xhtml) da aplicação web, onde o usuário informa sua senha de acesso, para a criação de sua conta.
Abaixo temos a página login.xhtml com o botão responsável por realizar a conexão entre a aplicação web e o Facebook. Esse botão realiza essa conexão através da chamada do método autenticarSpringComFacebook(). Após a entrada com os dados válidos de login e senha e a confirmação da permissão de acesso na conta do Facebook, o usuário é redirecionado para a página loginAuthenticate.xhtml.
<?xml version='1.0' encoding='UTF-8' ?>
<!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:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:pe="http://primefaces.org/ui/extensions">
<h:head>
<title>#{msg['sistema.titulo']}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="${facesContext.externalContext.requestContextPath}/resources/css/layout.css" rel="stylesheet"
type="text/css" />
<link href="${facesContext.externalContext.requestContextPath}/resources/css/reset.css" rel="stylesheet"
type="text/css" />
<!-- [if IE]>
<link href="${facesContext.externalContext.requestContextPath}/resources/css/ie.css" rel="stylesheet" type="text/css" />
<![endif]-->
<script type="text/javascript" src="${facesContext.externalContext.requestContextPath}/resources/scripts/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript">
function loadPage() {
jQuery(".c1").css("width", "60");
jQuery(".r1").css("heigth", "100");
jQuery('#j_password').focus();
}
</script>
</h:head>
<h:body onload="loadPage()">
<h:form id="form">
<f:view>
<div>
<p:messages id="msgAviso" showDetail="false" showSummary="true" globalOnly="false" />
</div>
<h:panelGrid id="pgrLogin" columns="2" cellspacing="5" columnClasses="c1,c2" rowClasses="r1,r2">
<h:outputText id="optLoginUsuario" styleClass="fonteMyriad" value="#{msg['usuario.login']}" />
<h:inputText id="j_username" required="true" requiredMessage="#{msg['usuario.login.requerido']}" maxlength="30" size="30" tabindex="1" />
<h:outputText id="optSenhaUsuario" styleClass="fonteMyriad" value="#{msg['usuario.senha']}" />
<h:inputSecret id="j_password" tabindex="2" required="true" requiredMessage="#{msg['usuario.senha.requerido']}" size="30" maxlength="30" />
</h:panelGrid>
<div id="botoes" style="position: relative; margin-top: 20px;">
<p:commandButton id="cbtLogar" styleClass="botaoArredondado" title="Logar no Sistema" value="Entrar" ajax="false" async="false" action="#{autenticacaoController.autenticar}" tabindex="3" />
<p:commandButton id="cbtLogarFace" ajax="false" async="false" title="Logar Face" immediate="true" value="Facebook" style="width: 106px; height: 30px;" action="#{autenticacaoController.autenticarSpringComFacebook}" tabindex="4" />
<h:commandButton id="cbtLimpar" styleClass="botaoArredondado" type="reset" immediate="true" value="Limpar" title="Limpar Campos" tabindex="5" />
</div>
</f:view>
</h:form>
</h:body>
</html>
Abaixo a classe Java vinculada à página index.xhtml e loginAuthenticate.xhtml
package br.com.pyramides.controller;
import br.com.pyramides.jsf.FacesUtils;
import br.com.pyramides.model.Usuario;
import br.com.pyramides.service.UsuarioService;
import br.com.pyramides.util.Constantes;
import br.com.pyramides.util.Funcoes;
import java.io.IOException;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Map;
import java.util.ResourceBundle;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.application.NavigationHandler;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.social.connect.Connection;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.social.facebook.api.FacebookProfile;
import org.springframework.social.facebook.connect.FacebookConnectionFactory;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.GrantType;
import org.springframework.social.oauth2.OAuth2Operations;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.stereotype.Controller;
/**
*
* @author Lessandro
*/
@Named("autenticacaoController")
@Controller
@Scope("view")
public class AutenticacaoController implements Serializable {
private static final long serialVersionUID = 1L;
private FacebookConnectionFactory connectionFactory;
private UsuarioService usuarioService;
private Usuario usuario;
private boolean exibeMensagem;
private boolean exibeCriaContaFacebook = true;
@PostConstruct
public void init() {
//Chamado só quando o managed bean é colocado no escopo view, e não a cada requisição como acontecia com o
//escopo request
}
@PreDestroy
public void destroy() {
//Chamado quando outra view for chamada através do UIViewRoot.setViewId(String viewId)
}
@Autowired
public AutenticacaoController(UsuarioService usuarioService) {
this.usuarioService = usuarioService;
}
public void autenticar() {
try {
FacesContext ctx = FacesContext.getCurrentInstance();
ctx.getExternalContext().dispatch("/j_spring_security_check");
ctx.responseComplete();
HttpSession session = (HttpSession) ctx.getExternalContext().getSession(false);
HttpServletRequest request = (HttpServletRequest) ctx.getExternalContext().getRequest();
session.setAttribute("ip", request.getRemoteAddr());
} catch (Exception ex) {
FacesUtils.addErrorMessage(ResourceBundle.getBundle(Constantes.MESSAGE_PROPERTIES_PATH).getString("usuario.login.erroAutenticacao"));
}
}
public void autenticarSpringComFacebook() {
try {
connectionFactory = new FacebookConnectionFactory(Constantes.APP_ID, Constantes.APP_SECRET);
OAuth2Operations oauthOperations = connectionFactory.getOAuthOperations();
OAuth2Parameters oAuth2Parameters = new OAuth2Parameters();
oAuth2Parameters.setScope("user_about_me,user_birthday,user_likes,user_status,publish_stream");
oAuth2Parameters.add("display", "popup");
String serverPath = FacesUtils.getApplicationURI() + Constantes.PAGINA_AUTENTICACAO_LOGIN;
oAuth2Parameters.setRedirectUri(serverPath);
String authorizeUrl = oauthOperations.buildAuthorizeUrl(GrantType.AUTHORIZATION_CODE, oAuth2Parameters);
HttpServletResponse response = FacesUtils.getServletResponse();
response.sendRedirect(authorizeUrl);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
public void processLoginFacebook() throws IOException, ServletException {
try {
Map<String, String> paramMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
String code = paramMap.get("code");
if (code != null && !code.isEmpty()) {
FacebookConnectionFactory connFactory = new FacebookConnectionFactory(Constantes.APP_ID, Constantes.APP_SECRET);
String serverPath = FacesUtils.getApplicationURI() + Constantes.PAGINA_AUTENTICACAO_LOGIN;
AccessGrant accessGrant = connFactory.getOAuthOperations().exchangeForAccess(code, serverPath, null);
Connection<Facebook> connection = connFactory.createConnection(accessGrant);
Facebook facebook = connection.getApi();
if (facebook.isAuthorized()) {
FacebookProfile fp = facebook.userOperations().getUserProfile();
String login = new String();
if (fp.getUsername() != null && !fp.getUsername().isEmpty()) {
login = checkLoginFacebook(fp.getUsername());
} else {
login = Funcoes.recuperaNomeUsuario(fp.getEmail());
}
usuario = new Usuario();
usuario.setUsername(login);
usuario.setNome(fp.getName());
usuario.setEmail(fp.getEmail());
usuario.setSexo(Funcoes.retornaSexo(fp.getGender()));
usuario.setCidade(fp.getLocation() != null ? fp.getLocation().getName() : null);
usuario.setDataNascimento(Funcoes.formataStringEmData(fp.getBirthday()));
}
}
} catch (IllegalArgumentException iae) {
FacesContext context = FacesContext.getCurrentInstance();
FacesUtils.addErrorMessage(ResourceBundle.getBundle(Constantes.MESSAGE_PROPERTIES_PATH).getString("usuario.loginExistente"));
NavigationHandler nh = context.getApplication().getNavigationHandler();
nh.handleNavigation(context, null, Constantes.PAGINA_LOGIN);
} catch (Exception ex) {
FacesUtils.addErrorMessageComponent("msgAviso", ResourceBundle.getBundle(Constantes.MESSAGE_PROPERTIES_PATH).getString("usuario.login.erroAutenticacao"));
}
}
private String checkLoginFacebook(String username) {
if (!usuarioService.checaUsuario(username)) {
return username;
} else {
throw new IllegalArgumentException(ResourceBundle.getBundle(Constantes.MESSAGE_PROPERTIES_PATH).getString("usuario.loginExistente"));
}
}
public void salvarUsuarioFacebook() {
try {
usuarioService.salva(usuario, true);
autenticar();
FacesUtils.addSuccessMessage(MessageFormat.format(ResourceBundle.getBundle(Constantes.MESSAGE_PROPERTIES_PATH).getString("usuario.criadoSucessoFacebook"), usuario.getNome()));
} catch (Exception ex) {
setExibeCriaContaFacebook(true);
System.out.println(ex.getMessage());
FacesUtils.addErrorMessageComponent("msgAviso", ResourceBundle.getBundle(Constantes.MESSAGE_PROPERTIES_PATH).getString("usuario.erroIncluir"));
}
}
public void encerraSessao() {
try {
FacesContext ctx = FacesContext.getCurrentInstance();
ctx.getExternalContext().redirect(FacesUtils.getServletRequest().getContextPath() + "/j_spring_security_logout");
} catch (Exception e) {
}
}
public Usuario getUsuarioAutenticado() {
return usuarioService.recoverAuthenticatedUser();
}
public boolean isExibeMensagem() {
return exibeMensagem;
}
public void setExibeMensagem(boolean exibeMensagem) {
this.exibeMensagem = exibeMensagem;
}
public Usuario getUsuario() {
return usuario;
}
public void setUsuario(Usuario usuario) {
this.usuario = usuario;
}
public boolean isExibeCriaContaFacebook() {
return exibeCriaContaFacebook;
}
public void setExibeCriaContaFacebook(boolean exibeCriaContaFacebook) {
this.exibeCriaContaFacebook = exibeCriaContaFacebook;
}
}
// Método getApplicationURI() referenciado na classe FacesUtils.java
public static String getApplicationURI() {
try {
URI uri = new URI(getExternalContext().getRequestScheme(), null, getExternalContext().getRequestServerName(), getExternalContext().getRequestServerPort(), getExternalContext().getRequestContextPath() + getExternalContext().getRequestServletPath(), null, null);
return uri.toASCIIString();
} catch (Exception ex) {
}
return null;
}
// Constantes referenciadas na classe Constantes.java
public static final String MESSAGE_PROPERTIES_PATH = "br.com.pyramides.boundle.message";
public static final String PAGINA_AUTENTICACAO_LOGIN = "/loginAuthenticate.xhtml";
public static final String APP_ID = "SEU_APP_ID";
public static final String APP_SECRET = "SEU_APP_SECRET";
public static final String PAGINA_LOGIN = "/login.xhtml";
Após o redirecionamento para a segunda página (loginAuthenticate.xhtml), o método autenticacaoController.processLoginFacebook() será disparado durante a renderização da mesma, definido pela tag . Esse método será responsável por capturar o código de confirmação do Facebook, dizendo que a conexão entre a aplicação web e o Facebook foi realizada com sucesso. Diante dessa questão, é possível capturar os dados do usuário, como e-mail, amigos, etc. Essa segunda página também possui um botão que realmente irá persistir o usuário na base de dados da aplicação web, para isso o usuário precisa informar a senha desejada.
<?xml version='1.0' encoding='UTF-8' ?>
<!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:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:pe="http://primefaces.org/ui/extensions">
<h:head>
<title>#{msg['sistema.titulo']}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="${facesContext.externalContext.requestContextPath}/resources/css/layout.css" rel="stylesheet"
type="text/css" />
<link href="${facesContext.externalContext.requestContextPath}/resources/css/reset.css" rel="stylesheet"
type="text/css" />
<!-- [if IE]>
<link href="${facesContext.externalContext.requestContextPath}/resources/css/ie.css" rel="stylesheet" type="text/css" />
<![endif]-->
<script type="text/javascript" src="${facesContext.externalContext.requestContextPath}/resources/scripts/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript">
function loadPage() {
jQuery(".c1").css("width", "60");
jQuery(".r1").css("heigth", "100");
jQuery('#j_password').focus();
}
</script>
</h:head>
<h:body onload="loadPage()">
<h:form id="form">
<f:view>
<f:metadata>
<f:event type="preRenderView" listener="#{autenticacaoController.processLoginFacebook}" />
</f:metadata>
<pe:messages id="messages" showDetail="true" showSummary="false" escape="false" globalOnly="true" />
<div>
<h:outputLabel styleClass="letraTitulo" value="Crie sua conta com o Facebook" />
</div>
<div style="padding-top: 10px;">
<h:panelGrid id="pgrCadastro" columns="2" cellspacing="5" columnClasses="c1,c2" rowClasses="r1,r2">
<h:outputText id="optEmailUsuario" styleClass="fonteMyriad" value="#{msg['usuario.email']}" />
<h:inputText id="iptEmail" required="true" value="#{autenticacaoController.usuario.email}"
requiredMessage="#{msg['usuario.email.requerido']}" maxlength="30" size="50" tabindex="1" readonly="true" />
<h:outputText id="optSenhaUsuario" styleClass="fonteMyriad" value="#{msg['usuario.senha']}" />
<h:inputSecret id="j_password" tabindex="2" required="true" value="#{autenticacaoController.usuario.password}"
requiredMessage="#{msg['usuario.senha.requerido']}" size="30" maxlength="30" />
</h:panelGrid>
<h:inputText id="j_username" style="display: none;" value="#{autenticacaoController.usuario.username}" />
<div id="botoes" style="position: relative; margin-top: 20px;">
<h:commandButton id="cbtCriarConta" type="submit" title="Crie sua conta" value="Criar conta"
action="#{autenticacaoController.salvarUsuarioFacebook}" tabindex="3" />
</div>
</div>
</f:view>
</h:form>
</h:body>
</html>
A persistência é realizada após o usuário informar a sua senha. Reparem que os id’s dos campos username e password são exatamente os mesmos utilizados pelo Spring Security. Com isso, ao realizar a gravação, o método que realiza o login manualmente utilizado na primeira página autenticar(), é chamado pelo método autenticacaoController.salvarUsuarioFacebook() garantindo assim a autenticação com o Spring Security. É possível realizar o download o projeto em: http://depositfiles.com/files/1lk31lmol
Em breve estarei disponibilizando o mesmo no Github.