Autenticação e autorização com Grails

Este blog deixou de ser mantido, mas o autor continua escrevendo aqui. Não deixe de assinar o novo feed!

108591969_2c7a4303cd_m.jpgAqui vai mais um daqueles posts onde conto um pouco das experiências que estou tendo com o Grails, o pequeno-grande framework web. Para ser mais específico, trago aqui algum código que mostra como implementar um esquema simples de autenticação e autorização em uma aplicação Grails. Mas, antes de mostrar qualquer código, deixe-me introduzí-lo ao “problema”.

Imagine que você esteja desenvolvendo uma aplicação pequena em Grails e que tal aplicação tenha controle de usuários, onde cada usuário tem um papel (administrador e convidado, por exemplo).

Para que um usuário tenha acesso à aplicação, primeiramente ele deverá fazer login. Feito o login, o usuário está liberado para utilizar todas as funções compatíveis com o seu papel. Por exemplo, um usuário convidado pode apenas executar operações de leitura, como pesquisas e relatórios, enquanto que um usuário administrador pode, hmm, fazer qualquer coisa.

A chance de você já ter visto (e implementado) uma aplicação com esses requisitos é bem grande. Então, para facilitar as coisas para quando você estiver encarando o Grails em um projeto, mostrarei neste post algumas dicas (e códigos) sobre como implementar tais requisitos, de uma forma simples e elegante.

Autenticação

A parte da autenticação é bem fácil de fazer sendo que, para a maioria dos casos, a criação de um Controller especial para demarcar as “opções protegidas” resolve bem o problema:

  1.  
  2. abstract class RestrictedController {
  3.     // todos os controllers protegidos estenderão este controller
  4. }
  5.  

Em seguida, definimos um beforeInterceptor para checar se o usuário se logou no sistema. Nada de anormal aqui:

  • Usuário logado? A Action requisitada é invocada;
  • Usuário não logado? A página Login é mostrada.
  1.  
  2. abstract class RestrictedController {
  3.  
  4.     def beforeInterceptor = [action: this.&auth, except: ‘login’]
  5.  
  6.     protected def auth() {
  7.         def result = true
  8.         if (!session.user) {
  9.             /* dados da requisição armazenados para serem usados após o login */
  10.             session.reqAction = actionUri
  11.             session.reqParams = params
  12.             redirect(controller: ‘login’, action: ’show’)
  13.             result = false
  14.         }
  15.     }
  16. }
  17.  

Mais detalhes sobre a utilização de Interceptors no Grails podem ser vistos na documentação do framework. O código do Controller Login não será mostrado por não ser tão importante no contexto deste post.

Autorização

Quase tudo o que é preciso para implementar a autenticação foi mostrado. Agora, precisamos definir um esquema simples para que possamos liberar o acesso às funções do sistema de acordo com o papel de um usuário.

Primeiramente, vamos definir um valor numérico para cada papel, onde quanto menor o valor, maior é o nível de acesso. Para este exemplo:

  • Papel administrador = 0;
  • Papel convidado = 1;
  • Papel xxxx = 2;
  • etc…

Desta forma, caso um usuário convidado (nível 1) tente acessar uma função no sistema cujo nível mínimo de acesso é zero (administrador), a única coisa que tal usuário verá no seu browser é uma página com os dizeres:

Access denied.

Veja abaixo um exemplo de como poderíamos definir os níveis de acesso para as nossas Actions dentro de um Controller qualquer:

  1.  
  2. class UserController extends RestrictedController {
  3.  
  4.     /* map nome_da_action: nivel_minimo */
  5.     def constraints = [create: 0, save: 0, delete: 0, list: 1]
  6.  
  7.     /* as actions abaixo só podem ser invocadas por administradores */
  8.     def create = {}
  9.     def save   = {}
  10.     def delete = {}
  11.  
  12.     /* a action abaixo só pode ser invocada por administradores e convidados */
  13.     def list = {}
  14.  
  15.     /* a action abaixo pode ser invocada por qualquer usuário autenticado */
  16.     def find = {}
  17. }
  18.  

Não seria legal se pudéssemos controlar o acesso às Actions da forma que foi mostrada no código? O bom é que isso é possível… nada que a boa e velha reflection não resolva!

Segue abaixo o código modificado do Controller RestrictedController, que permite o uso das constraints da forma que foi mostrada na listagem anterior:

  1.  
  2. abstract class RestrictedController {
  3.  
  4.     def beforeInterceptor = [action: this.&auth, except: ‘login’]
  5.  
  6.     protected def auth() {
  7.         if (!checkSessionForLogin()) {
  8.             return false // usuário tem que estar autenticado para prosseguir com a checagem
  9.         }
  10.  
  11.         /* obtém as constraints da action sendo invocada */
  12.         def constr = getControllerConstraints()
  13.         def role = constr[actionName]
  14.  
  15.         /* verifica se o usuário tem acesso a tal action */
  16.         def result = true
  17.         if (role != null) {
  18.             result = (session.user.role <= role)
  19.         }
  20.  
  21.         if (!result) {
  22.             sendAccessDeniedRedirect() // oooooops!
  23.         }
  24.         result
  25.     }
  26.  
  27.     private def getControllerConstraints() {
  28.  
  29.         def getter
  30.         def value = [:]
  31.  
  32.         try {
  33.             /* um pouquinho de reflection aqui… */
  34.             getter = getClass().getMethod(‘getConstraints’)
  35.             if (getter) {
  36.                 def temp = getter.invoke(this, null)
  37.                 if (temp) value = temp
  38.             }
  39.         }
  40.         catch (Throwable t) { }
  41.         value
  42.     }
  43.  
  44.     protected def checkSessionForLogin() {
  45.         def result = true
  46.         if (!session.user) {
  47.             session.reqAction = actionUri
  48.             session.reqParams = params
  49.             redirect(controller: ‘login’, action: ’show’)
  50.             result = false
  51.         }
  52.         result
  53.     }
  54.  
  55.     protected void sendAccessDeniedRedirect(String controller = ‘adminMain’) {
  56.         flash.message = ‘Access denied’
  57.         redirect(‘controller’: controller)
  58.     }
  59. }
  60.  

Pronto. Agora todo Controller que estenda RestrictedController pode declarar uma propriedade constraints com os dados para autorização. Ok, é meio babaca, mas basta para a maioria das aplicações.

Então é isso… até o próximo post!

Foto por: Semih Hazar

Tags: , , , , ,