Antes de continuar lendo, saiba que já há algo mais atualizado!
Galera, buenas!!
Desenvolvi uma parada aqui que acredito que possa ser útil para bastante gente (ou não… =S).
Já era para eu ter postado isso aqui há algum tempo. Mas ando meio enrolado, por isso estou fazendo isso só hj…
Vou explicar o que se trata, bem como a maneira de utilizar. Mas o intuito do post é pedir opinião sobre o que acharam da idéia, se realmente ajuda, ou se é algo para se ver e soltar um belo e caloroso “FODA-SE”. hehehe
Imaginem que vocês tem um controlador do VRaptor, com um método (recurso) qualquer:
package br.com.bronx.accesscontrol;
import br.com.caelum.vraptor.Resource;
@Resource
public class MinhaClasseController {
public void meuMetodo(){
//
}
}
Do jeito que está, supondo que nenhum Interceptor tenha sido criado para controlar o acesso a esse recurso, qualquer um que tentasse conseguiria acessar tranquilamente esse recurso, bastando acessar a URI: http://meuserver/meuContexto/minhaClasse/meuMetodo.
E se quiséssemos que somente usuários logados tivessem acesso ao referido recurso?
Teríamos que criar um Interceptor com as regras de acesso aos recursos. Nada muito complicado.
Porém, e se fosse tão simples quanto anotar um método?
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.caelum.vraptor.Resource;
@Resource
public class MeuRecursoController {
@LoggedIn
public void meuMetodo(){
//
}
}
Caso o método (recurso) solicitado possua tal annotation, somente usuários logados poderão acessá-lo.
Simples, não?
Essa é a idéia do que fiz: uma maneira de restringir os acessos aos recursos desenvolvidos com o VRaptor 3, sem a preocupação com a implementação dessas restrições.
Após configurar tudo, basta desenvolver sua aplicação de forma a não se preocupar com a implementação de restrições de acesso ao seu sistema.
Você simplesmente desenvolve as classes e os métodos, e se achar necessário, anota-os com as annotations de restrições. O resto é controlado pelo componente que criei, sem a menor intervenção do desenvolvedor.
Atemo-nos a utilização. rs
Aí surge a pergunta: “Tá, legal! O cara precisa estar logado. Mas o que eu faço caso ele não esteja?”.
Ahááá
Simplesmente, redirecione-o para a página de login. Ou para qualquer outra página que vc quiser.
Para isso, basta fazer:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.caelum.vraptor.Resource;
@Resource
public class MeuRecursoController {
@LoggedIn
@AccessDenied(loginPage="/login")
public void meuMetodo(){
//
}
}
Com a annotation @LoggedIn, vc diz que somente usuários logados possuem acesso àquele recurso.
Já a annotation @AccessDenied diz o que fazer caso o acesso ao recurso seja negado. O atributo loginPage diz para onde redirecionar a navegação caso o usuário não esteja logado.
O interessante é que nao temos que fazer isso para cada método. Podemos anotar as classes também:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.caelum.vraptor.Resource;
@Resource
@LoggedIn
@AccessDenied(loginPage="/login")
public class MeuRecursoController {
public void meuMetodo(){
//
}
}
Com isso, TODOS os métodos dessa classe só seriam acessados caso o usuário estivesse logado no sistema.
Podemos ir além, e definir um comportamento único para todos os métodos restritos em caso de o usuário não estar logado, mesmo se não quisermos que todos os métodos estejam restritos (nossa…essa nem eu entendi):
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.caelum.vraptor.Resource;
@Resource
@AccessDenied(loginPage="/login")
public class MeuRecursoController {
@LoggedIn
public void meuMetodo() {
//
}
public void meuOutroMetodo() {
//
}
@LoggedIn
public void meuTerceiroMetodo() {
//
}
}
Nesse caso, os métodos meuMetodo() e meuTerceiroMetodo() estão restritos, mas ambos adotam o mesmo comportamento caso o acesso seja negado. Isso nos poupa de ter que escrever a annotation @AccessDenied em cada um dos métodos. A seguinte situação também é válida:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.caelum.vraptor.Resource;
@Resource
@AccessDenied(loginPage="/login")
public class MeuRecursoController {
@LoggedIn
public void meuMetodo() {
//
}
public void meuOutroMetodo() {
//
}
@LoggedIn
@AccessDenied(loginPage="/outraPaginaDeLogin")
public void meuTerceiroMetodo() {
//
}
}
O método meuTerceiroMetodo(), em caso de acesso negado, não assumiria o comportamento padrão (redirecionar para o local informado na annotation @AccessDenied da classe). Ele redirecionaria a navegação para a “/outraPaginaDeLogin”. Na maioria das vezes isso não faz sentido, mas se for necessário, é fácil assim que se faz.
Dado esse overview, presumo que muitos devem estar se questionando: “Ok, mas e se o meu controle de acesso for baseado em papéis?”.
Controle de acesso baseado em papéis (roles) funciona da seguinte maneira: “Quero que apenas gerentes pudessem acessar tal recurso.”. As you wish:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.caelum.vraptor.Resource;
@Resource
public class MeuRecursoController {
@LoggedIn
@Roles(roles="gerente")
@AccessDenied(loginPage="/login")
public void meuMetodo() {
//
}
}
Aumentamos a restrição ao nosso método.
Agora, além de estar logado, o usuário DEVE ser um gerente para poder ter acesso àquele recurso.
Como para saber se o usuário é ou não gerente ele obrigatoriamente deve estar logado, podemos suprimir o uso da annotation @LoggedIn:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.caelum.vraptor.Resource;
@Resource
public class MeuRecursoController {
@Roles(roles="gerente")
@AccessDenied(loginPage="/login")
public void meuMetodo() {
//
}
}
Porém, o que fazer se o usuário está logado mas não é um gerente?
Utilize o atributo accessDeniedPage da annotation @AccessDenied:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.caelum.vraptor.Resource;
@Resource
public class MeuRecursoController {
@Roles(roles="gerente")
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public void meuMetodo() {
//
}
}
Agora, caso o usuário logado não possua as credenciais necessárias para acessar tais recursos, ele pode ser gentilmente redirecionado para uma página informando que ele não possui acesso ao requerido recurso. Cool, isn’t it? ^^
O legal é que, como mostrado anteriormente, essa restrição pode ser aplicada tanto na classe, quanto no recurso. Mas, como o nome sugere, o atributo roles pode receber mais de um papel. Saca só:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.caelum.vraptor.Resource;
@Resource
public class MeuRecursoController {
@Roles(roles="gerente;supervisor")
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public void meuMetodo() {
//
}
}
Esse recurso só seria acessado por quem fosse gerente OU supervisor.
O ponto-e-vígula é o que distingue o “gerente” do “supervisor”. Mas se precisar alterar o caracter de separação, basta definir outro no atributo “separator” (momento tabajara.rs):
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.caelum.vraptor.Resource;
@Resource
public class MeuRecursoController {
@Roles(roles="gerente/supervisor", separator="/")
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public void meuMetodo() {
//
}
}
Combinando entre classe e método:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.caelum.vraptor.Resource;
@Resource
@Roles(roles="aluno")
@AccessDenied(loginPage="/login")
public class MeuRecursoController {
@Roles(roles="professor")
@AccessDenied(accessDeniedPage="/acessoRestrito")
public void meuMetodo() {
//
}
public void meuOutroMetodo() {
//
}
@AccessDenied(loginPage="/outraPaginaDeLogin")
public void meuTerceiroMetodo() {
//
}
}
Podemos notar pela situação acima que a solução permite múltiplos papéis para cada usuário. No exemplo, somente alunos tem acesso aos recursos da classe MeuRecursoController. Porém para acessar o método meuMetodo(), o usuário deve ser, além de aluno, professor também.
Vejam o poder que essa ferramenta dá na hora de definir as restrições aos recursos do sistema!
Além disso, a página de login está indicada na classe, enquanto a página de acesso negado está no próprio método. Nice!
E se nem todos os métodos da classe fossem restritos, mas mesmo assim houvéssem recursos que só pudessem ser acessados por usuários que fossem alunos E professores?
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.caelum.vraptor.Resource;
@Resource
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public class MeuRecursoController {
@Roles(roles="aluno;professor")
public void meuMetodo() {
//
}
public void meuOutroMetodo() {
//
}
@AccessDenied(loginPage="/outraPaginaDeLogin")
public void meuTerceiroMetodo() {
//
}
}
A solução acima não restringiria o recurso de maneira adequada, pois alunos OU professores tem acesso ao recurso. Para alterar esse comportamento, alterem o valor do atributo policy da annotation @Roles, que indica a política a ser adotada para restringir o acesso ao recurso:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.bronx.accesscontrol.annotation.RolesPolicy;
import br.com.caelum.vraptor.Resource;
@Resource
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public class MeuRecursoController {
@Roles(roles="aluno;professor", policy=RolesPolicy.CONJUNCTION)
public void meuMetodo() {
//
}
public void meuOutroMetodo() {
//
}
@AccessDenied(loginPage="/outraPaginaDeLogin")
public void meuTerceiroMetodo() {
//
}
}
Agora sim. “RolesPolicy.CONJUNCTION” informa que somente usuários que possuem TODOS os papéis descritos no atributo roles, e não “ao menos um” dos papéis. “RolesPolicy.DISJUNCTION” é o valor padrão do atributo policy, portanto não precisa ser informado.
Eis a última abordagem de controle de acesso: por níveis de acesso.
Por vezes, é conveniente atribuirmos um nível de acesso para cada usuário.
Com isso, podemos fazer restrições do tipo: recurso x só pode ser acessado por usuários com nível de acesso maior ou igual a K.
Isso é pertinente em situações hierárquicas, onde, por exemplo, o gerente pode acessar todos os recursos, mas o desenvolvedor não (¬¬).
Pensando nisso, criei a anotação @AccessLevel, que informa o nível de acesso mínimo requerido para acessar determinado recurso:
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.AccessLevel;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.caelum.vraptor.Resource;
@Resource
@LoggedIn
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public class MeuRecursoController {
@AccessLevel(minimumAccessLevel=3)
public void meuMetodo() {
//
}
public void meuOutroMetodo() {
//
}
@AccessDenied(loginPage="/outraPaginaDeLogin")
public void meuTerceiroMetodo() {
//
}
}
No exemplo: para acessar os recursos dessa classe, o usuário deve estar logado. Mas para acessar o método meuMetodo(), o nível de acesso dele deve ser maior ou igual a 3. Caso nível de acesso do usuário seja menor que 3, a navegação é redirecionada para o local informado no atributo accessDeniedPage.
Tudo o que foi dito anteriormente é válido: pode-se usar indiscriminadamente a annotation @AccessLevel, tanto nos métodos quanto na classe, de forma que, para conseguir acesso ao recurso, TODAS as restrições presentes (na classe e no método) devem ser satisfeitas.
É possível definir um range de níveis de acesso. Basta preencher o atributo maximumAccessLevel:
@Resource
@LoggedIn
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public class MeuRecursoController {
@AccessLevel(minimumAccessLevel=3, maximumAccessLevel=10)
public void meuMetodo() {
//
}
public void meuOutroMetodo() {
//
}
@Roles(roles="estagiario")
@AccessDenied(loginPage="/outraPaginaDeLogin")
public void meuTerceiroMetodo() {
//
}
}
Na maioria dos casos essas restrições resolvem a maior parte dos problemas.
Mas é possível fazer restrições complexas.
Ex.: Permitir acesso aos recursos somente para usuários logados, que sejam professores E alunos, mas que para deletarem devem ser, além de alunos e professores, diretores ou reitores com nível de acesso entre 4 e 15. Se usuário não estiver logado, use a mesma página de login para todos. Mas se ele tentar deletar e não possuir as devidas credenciais, mandem-no para a pqp.
package br.com.bronx.accesscontrol;
import br.com.bronx.accesscontrol.annotation.AccessDenied;
import br.com.bronx.accesscontrol.annotation.AccessLevel;
import br.com.bronx.accesscontrol.annotation.LoggedIn;
import br.com.bronx.accesscontrol.annotation.Roles;
import br.com.bronx.accesscontrol.annotation.RolesPolicy;
import br.com.caelum.vraptor.Resource;
@Resource
@LoggedIn //desnecessário utilizar essa annotation
@Roles(roles="alunos;professores", policy=RolesPolicy.CONJUNCTION)
@AccessDenied(loginPage="/login", accessDeniedPage="/acessoRestrito")
public class MeuRecursoController {
public void meuMetodo() {
//
}
@Roles(roles="reitor#diretor", separator="#")
@AccessLevel(minimumAccessLevel=4, maximumAccessLevel=15)
@AccessDenied(accessDeniedPage="/putaQuePariu")
public void deletar() {
//
}
}
Shit! Escrevi bagarai! Acho que poucos lerão…rs
A parada está bem legal. Como disse, depois de feitas as devidas configurações, agiliza bastante o controle de acesso em suas aplicações. O cara que desenvolve vai passar longe de se preocupar em como implementará as restrições. A única preocupação vai ser com os perfis de acesso, que ele teria de qualquer maneira.
O tendão de Aquiles disso tudo é um tal de refactor friendly. Do jeito que está, se precisássemos trocar a página de login de todos os recursos, teríamos que entrar em cadae alterar um a um.
Esse problema será sanado na próxima versão. Fazer com que, ao invés de passar o endereço direto no @AccessDenied, colocar uma chave de um map que contém todas essas configurações. Afora os detalhes, acho que resolveria e bem esse problema.
Aos que lerem até aqui, por favor, comentem! Para mim, os comentários e sugestões serão de grande valia.
Aliás, sintam-se a vontade pra fazerem o que quiser com o código. Se a galera curtir e começar a utilizar, podemos criar um projeto no source forge para irmos corrigindo e melhorando.
Enfim, espero que essa seja apenas minha primeira contribuição com a comuna open source e com o VRaptor. Digo, espero que seja de fato uma contribuição rs. De qualquer forma, o que vale é a intenção. Estou utilizando isso em minhas aplicações e está ajudando. Espero que o mesmo ocorra com outras pessoas.
A seguir (próximo post), vou explicar como configurar.
That’s [almost] all, folks!