Grails Logo

Autenticação Facebook no Grails

Recentemente comecei a brincar com o grails e precisei implementar a funcionalidade de login usando uma conta do facebook. Na minha busca sobre autenticação conheci o plugin spring-security-core do qual eu gostei muito e comecei a usar. Para autenticação no facebook existe o plugin facebook-graph que também é muito bom.
Pensando nisso resolvi mostrar como utilizar esses dois plugins juntos para implementar autenticação no facebook com spring-security-core e facebook-graph no grails.
Pra começar vou criar o projeto facebookauth, instalar os plugins e criar as classes SecUser e SecRole referentes à autenticação e controle de acesso:

$ grails create-app facebookauth
$ cd facebookauth
facebookauth$ grails install-plugin spring-seucrity-core
facebookauth$ grails install-plugin facebook-graph
facebookauth$ grails s2-quickstart org.example SecUser SecRole

O plugin spring-security-core provê um novo comando, o s2-quickstart, que serve para criar as classes de domínio referentes ao usuário e suas permissões de acesso. Não entrarei em detalhes sobre seu funcionamento pois há um artigo explicando o básico em http://blog.springsource.com/2010/08/11/simplified-spring-security-with-grails/.
Com isso foram criadas as classes SecUser, SecRole e SecUserSecRole, os controllers LoginController e LogoutController e a view auth.gsp que contém o formulário de login.
Antes de começar a brincar é preciso efetuar algumas configurações e registar sua aplicação no facebook…

  1. Crie uma conta (se não tiver) no facebook
  2. Acesse https://www.facebook.com/developers/createapp.php e cadastre sua aplicação com o nome fbauth
  3. Após o cadastro, vá na página de edição e clique no menu Web Site, então configure Site URL como www.localhost.com e Site Domain como localhost.com. Altere o /etc/hosts mapeando o ip 127.0.0.1 para www.localhost.com
  4. Pegue sua Application ID e Application Secret e adicione as linhas no arquivo grails-app/conf/Config.groovy
    facebook.applicationSecret='sua application secret aqui'
    facebook.applicationId='sua application id aqui'
    facebook.secure=true

Agora precisamos adicionar o atributo facebookId na classe SecUser.

package org.example
 
class SecUser {
 
    String username
    String password
    boolean enabled
    boolean accountExpired
    boolean accountLocked
    boolean passwordExpired
 
    String facebookId
 
    static constraints = {
        username blank: false, unique: true
        password blank: false
        facebookId blank: false, unique: true
    }
 
    static mapping = { 
        password column: '`password`'
    }   
 
    Set<SecRole> getAuthorities() {
        SecUserSecRole.findAllBySecUser(this).collect { it.secRole } as Set
    }   
}

Quando o usuário se autenticar pelo facebook, a aplicação vai receber o facebook id desse usuário e salvar no banco de dados. Então alteramos o comportamento da aplicação passando a buscar o usuário por esse id no banco, não mais por username e password.

O próximo passo é alterar a view de login para inserir um botão para autenticação via facebook. Altere o arquivo grails-app/views/login/auth.gsp inserindo o código abaixo:

<fbg:resources/>
<script type="text/javascript">
    function facebookLogin() {
        FB.getLoginStatus(function(response) {
            if (response.session) {
                window.location ="${createLink(controller:'login', action:'facebookLogin')}";
            }
        });
    }
</script>
<fb:login-button perms="email" onlogin="facebookLogin();" size="large">
    <g:message code="Login"/>
</fb:login-button>

Esse código insere um botão que ao ser clicado vai abrir um popup do facebook para o usuário se autenticar. Se o login for efetuado com sucesso, o popup será fechado e o usuário será redirecionado para a action facebookLogin no controller login.
Lembrando que o controller login foi criado anteriormente quando executamos o comando grails s2-quickstart porém a action facebookLogin não existe. Então vamos criá-la!

// outros imports existentes
 
import org.example.SecUser
import org.example.SecRole
import org.example.SecUserSecRole
 
class LoginController {
    // outros atributos e actions existentes
 
    def facebookGraphService
 
    def facebookLogin = {
        def details = facebookGraphService.getDetails()
        def facebookId = details.id
 
        def user = SecUser.findByFacebookId(facebookId)?: new SecUser(
            username: details.email,
            password: springSecurityService.encodePassword(facebookId),
            enabled: true,
            facebookId: facebookId).save(failOnError: true)
 
        def role = SecRole.findByAuthority('ROLE_FACEBOOK')?: new SecRole(
            authority:'ROLE_FACEBOOK').save(failOnError: true)
        if (! user.authorities.contains(role)) {
            SecUserSecRole.create user, role
        }
 
        SpringSecurityUtils.reauthenticate(user.username, user.password)
        redirect(controller: 'controlPanel')
    }
}

O objeto facebookGraphService é injetado automaticamente pelo grails e ao chamar facebookGraphService.getDetails() é retornado um JSONObject com o seguinte formato:

{"location":{"id":"110200955653479","name":"Rio de Janeiro, Rio de Janeiro"},"link":"http://www.facebook.com/nomedousuario","locale":"en_US","updated_time":"2011-05-11T02:58:41+0000","id":"100000552012367","first_name":"xxxx","username":"xxxx","timezone":-3,"email":"xxxx@xxx.com","verified":true,"name":"Xxxx Xxxxx","last_name":"Xxxx","gender":"male"}

Então pegamos o facebook id e verificamos se existe algum cadastro no banco. Se não houver vai cadastrar um novo usuário usando o e-mail como username e o facebook id como senha. Para nível de acesso, foi adicionada a Role ROLE_FACEBOOK, que vai ser criada caso não exista.

Para testar, vamos criar um novo controller e apenas usuários autenticados podem acessar.
O spring security possui uma anotação para ser usada nas actions onde voce define a lista de Roles que podem acessar a action. Se o usuário não possuir nenhuma dessas Roles então será redirecionado para página de login.

Vamos criar o controller grails-app/controllers/ControlPanelController.groovy com o código abaixo:

import grails.plugins.springsecurity.Secured
 
class ControlPanelController {
 
    @Secured(['ROLE_FACEBOOK']) 
    def index = { 
        render "Olá! Você fez o login pelo facebook! \o/"
    }   
}

Se tudo deu certo, quando acessar a action index do controller controlPanel o usuário será redirecionado à tela de login. Ao clicar no botão do facebook, um popup será exibido para que o usuário se identifique usando suas credenciais do facebook. Login efetuado então será redirecionado para o controller controlPanel que dessa vez vai exibir a mensagem de boas vindas.

Links

http://www.grails.org/plugin/facebook-graph
https://github.com/chechu/grails-facebook-graph
http://blog.springsource.com/2010/08/11/simplified-spring-security-with-grails/
https://github.com/gustavohenrique/grails-facebook-auth-example