Mastering Grails: Autenticação e Autorização

Protegendo seu aplicativo Grails

Grails fornece todos os blocos de construção básicos necessários para formar um aplicativo da Web seguro, de uma infraestrutura de login simples até autorização baseada em função, e neste fascículo do Mastering Grails, Scott Davis fornece uma lição prática sobre como proteger seu aplicativo Grails. Você também aprenderá sobre alguns plug-ins que podem ajudá-lo a estender os recursos de segurança de seus aplicativos em novas direções.

Scott Davis, Senior IT Architect, IBM

Scott Davis photoScott Davis é um autor, palestrante e desenvolvedor de software reconhecido internacionalmente. Ele é fundador de ThirstyHead.com, uma empresa de treinamento para Groovy e Grails. Seus livros incluem Groovy Recipes: Greasing the Wheels of Java, GIS for Web Developers: Adding Where to Your Application, The Google Maps API e JBoss At Work. Ele escreve dois artigos contínuos para o IBM developerWorks: Mastering Grails e Practically Groovy.



28/Abr/2009

Neste artigo, continuo construindo um "blog bem pequeno" denominado Blogito. Eu apaguei Users no artigo anterior ("Rewiring Grails with custom URIs and codecs"), porque o campo nome era uma parte integral do URI. Agora está na hora de implementar o subsistema User integralmente. Você aprenderá como ativar logins, limitar a atividade baseada em se User efetuou login ou não e, até mesmo, incluir alguma autorização baseada na função User.

Para iniciar, Users precisam de uma maneira para efetuar login, de forma que possam postar novas entradas.

Autenticação

Autenticação é provavelmente uma boa ideia para um servidor de blog que suporta diversos usuários. Você certamente não deseja que o Zé da Silva poste entradas de blog como Jane Smith, acidentalmente ou não. Configurar uma infraestrutura de autenticação responde a pergunta: "Quem é você?" Em apenas um momento, você irá incluir um pouco de autorização também. A autorização responde a pergunta: "O que você tem permissão de fazer?"

A Lista 1 mostra o arquivo grails-app/domain/User.groovy criado da última vez:

Lista 1. A Classe User
class User {
  static constraints = {
    login(unique:true)
    password(password:true)
    name()
  }

  static hasMany = [entries:Entry]

  String login
  String password
  String name

  String toString(){
    name
  }
}

Os campos login e password estão nos devidos lugares. Tudo que se precisa fazer agora é fornecer um controlador e um formulário. Crie grails-app/controllers/UserController.groovy e inclua o código, conforme mostrado na Lista 2:

Lista 2. Incluindo os Encerramentos login, authenticate e logout em UserController
class UserController {
  def scaffold = User

  def login = {}

  def authenticate = {
    def user = User.findByLoginAndPassword(params.login, params.password)
    if(user){
      session.user = user
      flash.message = "Hello ${user.name}!"
      redirect(controller:"entry", action:"list")
    }else{
      flash.message = "Sorry, ${params.login}. Please try again."
      redirect(action:"login")
    }
  }

  def logout = {
    flash.message = "Goodbye ${session.user.name}"
    session.user = null
    redirect(controller:"entry", action:"list")
  }
}

O encerramento login vazio simplesmente significa que visitar http://localhost:9090/blogito/user/login em seu navegador renderizará o arquivo grails-app/views/user/login.gsp. (Você criará esse arquivo em apenas um momento.)

O encerramento authenticate usa um método GORM conveniente — findByLoginAndPassword() — para fazer exatamente o que diz fazer: localizar o User no banco de dados cujo login e password correspondem aos valores digitados nos campos do formulário e disponibilizados através do hashmap params . Se o User existir, inclua-o na sessão. Caso contrário, redirecione de volta ao formulário de login para dar ao User outra chance de fornecer as credenciais corretas. O encerramento logout dá adeus ao User, remove o mesmo da sessão e, em seguida, redireciona de volta à ação list em EntryController.

Agora, chegou a hora de criar o login.gsp. É possível digitar o código mostrado na Lista 3 manualmente ou:

  1. Digitar grails generate-views User na linha de comando.
  2. Copiar create.gsp para login.gsp.
  3. Cortar o código resultante.
Lista 3. login.gsp
<html>
  <head>
    <meta name="layout" content="main" />
    <title>Login</title>
  </head>
  <body>
    <div class="body">
      <h1>Login</h1>
      <g:if test="${flash.message}">
        <div class="message">${flash.message}</div>
      </g:if>
      <g:form action="authenticate" method="post" >
        <div class="dialog">
          <table>
            <tbody>
              <tr class="prop">
                <td class="name">
                  <label for="login">Login:</label>
                </td>
                <td>
                  <input type="text" id="login" name="login"/>
                </td>
              </tr>

              <tr class="prop">
                <td class="name">
                  <label for="password">Password:</label>
                </td>
                <td>
                  <input type="password" id="password" name="password"/>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
        <div class="buttons">
          <span class="button">
            <input class="save" type="submit" value="Login" />
          </span>
        </div>
      </g:form>
    </div>
  </body>
</html>

Observe que action do formulário é authenticate, que corresponde ao nome do encerramento em UserController.groovy. os nomes nos elementos de entrada — login e password — correspondem a params.login e params.password no encerramento authenticate .

Digite grails run-app e leve sua infraestrutura de autenticação para dar uma volta. Tente efetuar login como jsmith com uma senha igual a foo. (Lembre-se de que em "Rewiring Grails with custom URIs and codecs" você forneceu o valor inicial ao Blogito com dois usuários em grails-app/conf/BootStrap.groovy.) Seu login deve falhar, conforme mostrado na Figura 1:

Figura 1. Falha na Tentativa de Login com Mensagem de Erro
Falha na Tentativa de Login com Mensagem de Erro

Tente novamente como jsmith com uma senha igual a wordpass. Desta vez deve-se obter êxito.

Se a mensagem de boas-vindas não aparecer em grails-app/views/entry/list.gsp — e não deve — simplesmente copie o bloco <g:if test="${flash.message}"> do login.gsp para a parte superior do arquivo list.gsp. Efetue login novamente como jsmith para verificar se a mensagem agora aparece conforme mostrado na Figura 2:

Figura 2. Mensagem Flash Confirmando um Login Bem-sucedido
Mensagem Flash Confirmando um Login Bem-sucedido

Agora que você tem certeza de que a autorização funciona, você deve criar um TagLib que facilite o login e o logout.


Criando, com Autorização, uma TagLib

Web sites como Google e Amazon oferecem um link de texto não intruso no cabeçalho que permite efetuar login e logout. O mesmo pode ser feito em Grails com apenas algumas linhas de código.

Para iniciar, digite grails create-tag-lib Login no prompt de comandos. Inclua o código na Lista 4 no novo grails-app/taglib/LoginTagLib.groovy criado:

Lista 4. LoginTagLib.groovy
class LoginTagLib {
  def loginControl = {
    if(session.user){
      out << "Hello ${session.user.name} "
      out << """[${link(action:"logout", controller:"user"){"Logout"}}]"""
    } else {
      out << """[${link(action:"login", controller:"user"){"Login"}}]"""
    }
  }
}

Agora, inclua a nova tag <g:loginControl> em grails-app/views/layouts/_header.gsp, conforme mostrado na Lista 5:

Lista 5. Incluindo a Tag <loginControl> no Cabeçalho
<div id="header">
  <p><g:link class="header-main" controller="entry">Blogito</g:link></p>
  <p class="header-sub">A tiny little blog</p>

  <div id="loginHeader">
    <g:loginControl />
  </div>
</div>

Para concluir, inclua um pouco de formatação CSS para loginHeader <div> em web-app/css/main.css, conforme mostrado na Lista 6:

Lista 6. Formatação CSS para loginHeader <div>
#loginHeader {
  float: right;
  color: #fff;
}

Ao reiniciar Grails e efetuar login como jsmith, sua tela deve ter a aparência da Figura 3:

Figura 3. TagLib de Login na Ação
TagLib de Login na Ação

Autorização Básica

Agora que o Blogito sabe quem você é, a próxima etapa é limitar o que se pode fazer. Por exemplo, qualquer pessoa deve ser capaz de ler uma Entry, mas somente os usuários que efetuaram login devem ter permissão para criar, atualizar e excluir uma Entry. Para realizar isso, Grails oferece um beforeInterceptor que, como o nome sugere, fornece um gancho perfeito para autorizar atividades antes do encerramento de destino ser chamado.

Inclua o código da Lista 7 em EntryController:

Lista 7. Incluindo Autorização em EntryController
class EntryController {

  def beforeInterceptor = [action:this.&auth, except:["index", "list", "show"]]

  def auth() {
    if(!session.user) {
      redirect(controller:"user", action:"login")
      return false
    }
  }

  def list = {
    //snip...
  }
}

Uma diferença sutil, mas importante, entre auth e list é que list é um encerramento, enquanto que auth é um método privado. (Encerramentos usam um sinal de igual na definição; métodos usam parênteses.) Encerramentos são expostos para o usuário final como um URI; métodos não podem ser acessados a partir do navegador.

Um método auth verifica se um User está na sessão. Se não estiver, redireciona à tela de login e retorna falso, bloqueando a chamada de encerramento original.

Um método auth é chamado antes de cada chamada de encerramento pelo beforeInterceptor. A ação usa nota Groovy para apontar para o método auth da classe this usando o caractere e comercial (&). A except contém os encerramentos que devem ficar fora da chamada auth . Você pode substituir except por only se quiser interceptar apenas algumas chamadas de encerramento. (Para obter informações adicionais sobre beforeInterceptor, consulte Recursos.)

Reinicie Grails e teste o beforeInterceptor. Tente visitar http://localhost:9090/blogito/entry/create sem efetuar login. Você deve ser redirecionado à tela de login. Efetue login como jsmith e tente novamente. Desta vez, deve ser possível criar uma nova Entry com êxito.


Autorização com Baixa Granularidade

A autorização com alta granularidade oferecida pelo beforeInterceptor é um início, mas é possível incluir ganchos de autorização em encerramentos individuais também. Por exemplo, como as coisas estão agora, qualquer User — que tenha efetuado login, não apenas o autor original — pode editar qualquer Entry. É possível fechar esse buraco na segurança incluindo quatro linhas de código bem posicionadas no encerramento edit em EntryController.groovy, conforme mostrado na Lista 8:

Lista 8. Incluindo Autorização no Encerramento edit
def edit = {
    def entryInstance = Entry.get( params.id )

    //limite a edição para o autor original
    if( !(session.user.login == entryInstance.author.login) ){
      flash.message = "Sorry, you can only edit your own entries."
      redirect(action:list)
    }

    if(!entryInstance) {
        flash.message = "Entry not found with id ${params.id}"
        redirect(action:list)
    }
    else {
        return [ entryInstance : entryInstance ]
    }
}

Você pode (e deve) bloquear os encerramentos delete e update com as mesmas quatro linhas de código. Se a possibilidade de copiar e colar código duplicado for desagradável (e deve ser), é possível criar um único método privado e chamá-lo em todos os três encerramentos. Se perceber que está usando o mesmo beforeInterceptor e métodos privados em muitos controladores, é possível fatorar o comportamento comum em um único controlador principal e fazer com que os outros controladores o estendam, como faria normalmente com qualquer classe Java.

É possível incluir mais uma coisa em sua infraestrutura de autorização para torná-la mais robusta: funções.


Incluindo Funções

Designar uma função a Users é uma maneira conveniente de agrupá-los. É possível, então, designar permissões ao grupo, em vez de a indivíduos. Por exemplo, agora, qualquer pessoa pode criar um novo User. Apenas verificar se a pessoa efetuou login não é seguro o suficiente. Gostaria de limitar a capacidade de gerenciar contas de User a um administrador.

A Lista 9 inclui um campo de função no User, assim como uma restrição que limita os valores para author ou admin:

Lista 9. Incluindo um Campo de Função no User
class User {
  static constraints = {
    login(unique:true)
    password(password:true)
    name()
    role(inList:["author", "admin"])
  }

  static hasMany = [entries:Entry]

  String login
  String password
  String name
  String role = "author"

  String toString(){
    name
  }
}

Observe que role usa como padrão author. A restrição inList apresenta uma caixa de combinação com as únicas duas opções válidas. A Figura 4 mostra isso em ação:

Figura 4. Limitando Novas Funções de Usuário para author ou admin
Limitando Novas Funções de Usuário para author ou admin

Crie um admin User em grails-app/conf/BootStrap.groovy, conforme mostrado na Lista 10. Não se esqueça de incluir author role nos dois Users existentes.

Lista 10. Incluindo um admin User
import grails.util.GrailsUtil

class BootStrap {
  def init = { servletContext ->
    switch(GrailsUtil.environment){
      case "development":
        def admin = new User(login:"admin",
                             password:"password",
                             name:"Administrator",
                             role:"admin")
        admin.save()

        def jdoe = new User(login:"jdoe",
                            password:"password",
                            name:"John Doe",
                            role:"author")
        //snip...

        def jsmith = new User(login:"jsmith",
                            password:"wordpass",
                            name:"Jane Smith",
                            role:"author")
        //snip...

      break

      case "production":
      break
    }

  }
  def destroy = {
  }
}

E, por fim, inclua o código da Lista 11 para limitar toda a atividade de conta de User a pessoas que tenham a função admin :

Lista 11. Limitando o Gerenciamento de Conta de User à Função admin
class UserController {

  def beforeInterceptor = [action:this.&auth,
                           except:["login", "authenticate", "logout"]]

  def auth() {
    if( !(session?.user?.role == "admin") ){
      flash.message = "You must be an administrator to perform that task."
      redirect(action:"login")
      return false
    }
  }

  //snip...
}

Para testar a autorização baseada em função, efetue login como jsmith e, em seguida, tente visitar http://localhost:9090/blogito/user/create. você deve ser redirecionado à tela de login, conforme mostrado na Figura 5:

Figura 5. Bloqueando Acesso para Não-administradores
Bloqueando Acesso para Não-administradores

Agora, efetue login como o usuário admin . Você deve poder acessar todos os encerramentos.


Levando ao Próximo Nível com Plug-ins

O sistema de autenticação e autorização "bem pequeno" para este aplicativo de blog "bem pequeno" agora está em vigor. É possível estendê-lo facilmente em nova direções. Possivelmente, você gostaria que Users pudessem gerenciar suas próprias contas, mas não outras. Talvez admins devem ter a capacidade de editar todas as Entries, não apenas suas próprias. Em cada caso, apenas duas linhas de código posicionadas estrategicamente é tudo que se precisa para incluir a nova funcionalidade.

É comum confundir simplicidade com falta de poder. Blogito ainda possui menos de 200 linhas de código — e isso inclui os testes de unidade e integração. Digite grails stats na linha de comando para confirmar isso. Os resultados são mostrados na Lista 12. Mas a falta de complexidade do Blogito não significa que não esteja pronto para o horário nobre.

Lista 12. O Tamanho do Aplicativo "Bem Pequeno"
$ grails stats

	+----------------------+-------+-------+
	| Name                 | Files |  LOC  |
	+----------------------+-------+-------+
	| Controllers          |     2 |    95 |
	| Domain Classes       |     2 |    32 |
	| Tag Libraries        |     2 |    21 |
	| Unit Tests           |     5 |    20 |
	| Integration Tests    |     1 |    10 |
	+----------------------+-------+-------+
	| Totals               |    12 |   178 |
	+----------------------+-------+-------+

Minha meta desde o primeiro artigo desta série tem sido mostrar o poder nativo de Grails principal e a expressividade concisa da linguagem Groovy. Por exemplo, assim que entender codecs em Grails, fica fácil efetuar hash das senhas armazenadas no banco de dados em vez de exibi-las livremente. (Para obter informações adicionais sobre HashCodec, consulte Recursos.) Crie grails-app/utils/HashCodec.groovy e inclua o código da Lista 13:

Lista 13. Criando um Simples HashCodec
import java.security.MessageDigest
import sun.misc.BASE64Encoder
import sun.misc.CharacterEncoder

class HashCodec {
  static encode = { str ->
    MessageDigest md = MessageDigest.getInstance('SHA')
    md.update(str.getBytes('UTF-8'))
    return (new BASE64Encoder()).encode(md.digest())
  }
}

Com o HashCodec em vigor, simplesmente altere as referências para User.password nos encerramentos login, save e update em UserController para User.password.encodeAsHash(). É incrível que com meramente 10 linhas de código foi incluída todo um novo nível de sofisticação em seu aplicativo.

Mas uma linha de retornos reduzidos é cruzada em algum ponto. No caso de Grails, a clássica pergunta "construir ou comprar" é convertida em "construir ou fazer download de um plug-in". Diversos plug-in em http://grails.org/plugin/list#security+tags tentam solucionar o desafio de autenticação e autorização de maneira rápida, usando grails install-plugin .

Por exemplo, o plug-in Authentication oferece alguns recursos interessante, como permitir que Users inscrevam-se para uma conta em vez de forçar um admin a criar uma em nome deles. É possível, então, configurar o plug-in para enviar uma mensagem de confirmação ao User informando: "Uma nova conta de usuário foi criada usando este endereço de e-mail. Clique neste link para verificar sua nova conta."

O plug-in OpenID usa uma abordagem diferente. Em vez de forçar os usuários finais a criarem uma nova combinação de nome de usuário e senha que, com certeza, esquecerão, a autenticação é delegada ao provedor OpenID de sua opção. O plug-in Lightweight Directory Access Protocol (LDAP) usa uma abordagem semelhante, permitindo que o aplicativo Grails use a infraestrutura LDAP existente.

Os plug-ins Authentication e OpenID limitam-se à questão de autenticação. Outros oferecem soluções de autorização também. O plug-in JSecurity fornece uma estrutura de segurança completa, oferecendo classes de domínio padrão para Users, Roles e Permissions. O plug-in Spring Security usa as bibliotecas Spring Security (anteriormente Acegi Security), permitindo a reutilização de seu conhecimento e código de origem existente de Spring Security.

Como pode ver, diversas estratégias de autenticação e autorização estão disponíveis em Grails, pois os requisitos variam muito entre os aplicativos. Com cada passo acima em recursos, seu aplicativo dá um passo acima inevitável correspondente em complexidade. Usei muitos dos plug-ins listados aqui em aplicativos de produção, mas somente após ter certeza de que os benefícios superavam a simples estratégia manual apresentada primeiro.


Conclusão

Agora o Blogito está protegido. Users têm uma maneira de efetuar login e logout e um conjunto de links conveniente para fazer isso, graças ao LoginTagLib criado. Em algumas situações, simplesmente efetuar login no aplicativo é segurança suficiente, conforme demonstrado pelo beforeInterceptor em EntryController que verifica a autenticação. Em outros casos, as funções fornecem outra camada de sofisticação no formato de autorização. Incluir uma função simples no User permite limitar o acesso de gerenciamento de usuário a administradores.

Agora que o Blogito está seguro, o próximo artigo Mastering Grails pode focar na tarefa principal em questão — fornecendo uma maneira para que usuários autenticados façam upload de arquivos e uma maneira para que usuários finais subscrevam um Atom feed. Posto isso, o Blogito será realmente um aplicativo de blog. Até lá, divirta-se dominando Grails.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=391264
ArticleTitle=Mastering Grails: Autenticação e Autorização
publish-date=04282009