Atualizado em 17 de Setembro de 2011.
Em uma aplição com diferentes níveis de acesso à funcionalidades, é fundamental termos controle das ações efetuadas pelos usuários. Podemos citar, por exemplo, exclusão de dados, alteração de informações ou visualização de certas telas que apenas os usuários com privilégio mais alto poderiam executar.
Objetivo:
Criar funcionalidades de um sistema, nas quais cada uma só poderá ser executada por um determinado tipo de usuário.
O que será tratado?:
- Controle de acesso a métodos específicos; e
- Controle de acesso a um controller como um todo.
Recomendo a leitura do artigo Controle de Login com VRaptor 3, pois introduz o conceito de [http://vraptor.caelum.com.br/documentacao/interceptadores/Interceptor) e explica como criar uma annotation.
Quando falamos em permissões, logo pensamos em perfis, já que uma permissão estará ligada diretamente a estes. Com isso criaremos um Enum com alguns nomes de perfil para usarmos como indicadores das permissões.
Criando os perfis (Perfil.java):
public enum Perfil {
MEMBRO, MODERADOR, ADMINISTRADOR;
}
Veja que temos 3 (três) tipos de perfis, cada um deles poderá ter um privilégio diferente.
Criando o usuário (Usuario.java):
public class Usuario {
private Long id;
private String nome;
private Perfil perfil;
// getters e setters
}
No objeto usuário, além dos atributos normais que o mesmo possa ter, também teremos o atributo que manterá o perfil do usuário.
Criando a anotação pública (Public.java):
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface Public {
}
Esta anotação servirá para indicar que o recurso é público e não possui restrições.
Criando o controller inicial (IndexController.java):
@Resource
public class IndexController {
...
@Public
@Get("/")
public void index() {
Usuario usuario = new Usuario();
usuario.setNome("Washington Botelho");
usuario.setPerfil(Perfil.MODERADOR);
session.setAttribute("user", usuario);
}
}
No controller de entrada criamos um mock do usuário com tipo de perfil (moderador) já o colocando na sessão para o utilizarmos no exemplo. Em uma aplicação real você pegaria esse usuário do banco de dados quando o mesmo fizesse o login. Repare que este método esta sendo anotado com @Public para indicar que ele não entrará no controle de permissão.
Agora precisamos de criar a camada de negócios que simulará as ações feitas no banco de dados.
Criando o Business (UsuarioBusiness.java):
@Component
@SessionScoped
public class UsuarioBusiness {
private Collection<Usuario> manager = new ArrayList<Usuario>();
public void save(Usuario usuario) {
manager.add(usuario);
}
public void remove(Usuario usuario) {
manager.remove(usuario);
}
public Collection<Usuario> all() {
return manager;
}
// getters e setters
}
Esta camada de negócio simula a inserção, remoção e recuperação dos usuários em cima de uma lista que ficará na sessão por conta da classe estar anotada com @SessionScoped
. Em uma aplicação real você acessaria o banco e não teria esta anotação.
O próximo passo é criar uma anotação para podermos utilizá-las nos métodos e controllers que queremos restringir o acesso. Com esta anotação podemos identificar qual usuário tem direito de acesso ao método ou controller anotado.
Criando a anotação de permissão (Permission.java):
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface Permission {
Perfil[] value();
}
Esta anotação possui um único elemento que é um array de Perfil
que por ser o único atributo existente nessa anotação, é chamado de value
por boas práticas. Com isso podemos utilizar anotações em classes e métodos de forma simples. Vejamos um exemplo de um método anotado com a permissão apenas para administrador:
@Permission(Perfil.ADMINISTRADOR)
public void foo() {
}
Aqui utilizamos a anotação Permission
passando o valor de administrador. Quando passamos apenas um valor para a anotação podemos omitir o atributo value
, que será equivalente a:
@Permission(value = Perfil.ADMINISTRADOR)
Caso queira utilizar mais de um valor na anotação deverá passá-la em formato de hash, entre chaves separando os valores por vírgula:
@Permission({ Perfil.ADMINISTRADOR, Perfil.MODERADOR })
Com o conceito já maduro de annotation, já é possível anotar métodos ou controllers que necessitarem de restrições.
Criando o controller do usuário (UsuarioController.java):
@Resource
public class UsuarioController {
...
@Get("/usuario")
public void listagem() {
return business.all();
}
@Post("/usuario")
@Permission(Perfil.MODERADOR)
public void salvar(Usuario usuario) {
business.save(usuario);
result
.include("message", "Usuário adicionado com sucesso!")
.redirectTo(this).listar();
}
@Delete("/usuario/{entity.id}")
@Permission({ Perfil.MODERADOR, Perfil.ADMINISTRADOR })
public void remover(Usuario usuario) {
business.remove(usuario);
result
.include("notice", "Usuário removido com sucesso!")
.redirectTo(this).listar();
}
}
Temos 4 (quatro) métodos:
listagm
enegado
: sem anotação. Podem ser acessados livrementes;salvar
: com permissão apenas para moderador.remover
: com permissão para moderador e administrador.
Lembre-se que em nível de programação os perfis não são cumulativos. Não é porque um usuário é administrador que ele poderá executar ações de um método anotado para membros. Se você quiser acesso para o administrador, deverá adicioná-lo na anotação.
Também criaremos um controller administrativo, no qual todas as funcionalidades contidas nele só poderão ser executadas pelo administrador.
Criando o controller administrativo (AdminController):
@Resource
@Permission(Perfil.ADMINISTRADOR)
public class AdminController {
}
Repare que agora a anotação esta na classe, indicando que o acesso a qualquer método desta só poderá ser feito pelo administrador.
Vamos criar as telas do sistema.
Criando a página inicial (index.jsp):
...
<a href="${pageContext.request.contextPath}/">Início)
<a href="${pageContext.request.contextPath}/usuario">Listar usuários)
<a href="${pageContext.request.contextPath}/admin">Administração)
Seja bem vindo: ${userSession.user.nome}
...
Acima criamos simples os links de navegação do sistema e a apresentação do nome do usuário que esta na sessão.
Criando a tela administrativa (admin.jsp):
...
${notice}
...
Na tela administrativa iremos esperar apenas uma mensagem de boas vindas.
Criando a tela de listagem (listagem.jsp):
...
${notice}
<form action="${pageContext.request.contextPath}/usuario" method="post">
<input type="text" name="usuario.nome" />
<input type="submit" value="Salvar" />
</form>
<c:forEach items="${usuarioList}" var="usuario">
${usuario.nome}
<form action="${pageContext.request.contextPath}/usuario/${usuario.id}" method="post">
<input type="hidden" name="_method" value="delete" />
<input type="submit" value="Remover" />
</form>
</c:forEach>
...
Na tela de listagem temos um formulário para salvar o usuário e logo abaixo a listagem dos usuários já cadastrados com opção de removê-los.
Com isso preparamos todo o nosso ambiente, faltando apenas o controle de permissão de fato. Esse controlador será um Interceptor.
Criando o interceptador de permissão (PermissionInteceptor.java):
Primeiramente devemos excluir as classes que não desejamos que sejam interceptadas.
public boolean accepts(ResourceMethod method) {
return
!(method.getMethod().isAnnotationPresent(Public.class) ||
method.getResource().getType().isAnnotationPresent(Public.class));
}
No método accepts
não iremos interceptar nem os métodos nem os controllers que estiverem anotados com @Public.
E então em nosso método intercepts
vamos criar a lógica das restrições.
public void intercept(InterceptorStack stack, ResourceMethod method, Object resource) {
Permission methodPermission = method.getMethod().getAnnotation(Permission.class);
Permission controllerPermission = method.getResource().getType().getAnnotation(Permission.class);
if (this.hasAccess(methodPermission) && this.hasAccess(controllerPermission)) {
stack.next(method, resource);
} else {
result.use(http()).sendError(403, "Você não tem permissão para tal ação!");
}
}
Através do argumento ResourceMethod
capturamos todas os atributos da anotação do tipo Permission
do método acessado pegando o método usando o getMethod
e em seguida a anotação passando seu tipo.
Para pegar a anotação do controller fazemos praticamente o mesmo processo alterando apenas o método getMethod
para getResource
e getType
. - Se lembra que os nossos controllers são anotados com @Resource
?
Então estas duas listas são passadas para o método hasAccess
que responde se o usuário tem permissão de acesso ao recurso requisitado. Caso tenha, é chamado o método next
do InterceptorStack
, que da continuidade ao fluxo, caso contrário o usuário é redirecionado para a página negado.jsp.
O método que verifica se o usuário tem acesso ficará o seguinte:
private boolean hasAccess(Permission permission) {
if (permission == null) {
return true;
}
Collection<Perfil> perfilList = Arrays.asList(permission.value());
return perfilList.contains(userSession.getUser().getPerfil());
}
Se não houver anotação na lista recebida como argumento, quer dizer que não há restrições, logo, é retornado true
, liberando o acesso. Caso contrário, é verificado se o usuário possui o perfil adequado. O método hasAccess
é usado para analisar tanto a permissão do método quanto ao do controller, retornando true
ou false
para indicar a liberação do acesso.
Contribuição
O Rodrigo Ramalho contribuiu com o seguinte código para testar as anotações:
@Test
@SuppressWarnings("rawtypes")
public void createPermission() throws SecurityException, NoSuchMethodException{
UserController controller = new UserController(result, userService, validator);
Class c = controller.getClass();
Method m = c.getMethod("create", User.class);
Permission p = m.getAnnotation(Permission.class);
Assert.assertNotNull(p);
Assert.assertEquals(2, p.value().length);
Assert.assertEquals(Role.ADMIN, p.value()[0]);
Assert.assertEquals(Role.MEMBER, p.value()[1]);
}
Com isso nosso controle de permissão esta pronto e qualquer classe que necessite de monitoramente poderá ser anotada e entrará automaticamente no controle de acesso.
Se você quiser ver como fica o controle login junto com o controle de permissão visite o projeto VRaptor Starting Project: http://github.com/wbotelhos/vraptor-starting-project
Olá Washington
Primeiramente gostaria de parabenizar pelo post, me ajudou e muito, ótimo tutorial
Porém, só estou com uma dúvida:
Como fazer com que o usuário já logado não acesse a página de login?
obrigado desde já
Washington, muito interessante, gostaria de saber se essas informações continuam valida para o atual VRaptor v2.5.3, se ocorreu alguma alteração que atrapalhe este controle. (vou testa-lo quando chegar em casa somente)
mas tenho outras dúvidas ainda:
Nesse caso eu controlaria o metodo e então se o cara acessa um página de admin, vai ser inutil ele não fará nada, provavelmente será negado, mas gostaria de saber o que você acha mais produtivo, ou melhor, criar varios menus com as opções de cada perfil, por exemplo
menuAdmin.jsp (contendo o link do admin)
menuUsuario.jsp(contendo links do usuarios, mas não do admin)
menuPublico.jsp(contendo links só para quem não está logado)
seguindo essa lógica, eu teria que fazer um comando qualquer em cada tipo de menu, para verificar se aquela página pode ser acessada por ele, algo como .isAdmin, caso não redireciona para página de login (caso por algum motivo, erro ou usuário consegue o link do menuAdmin.jsp, sei que ele não fara execução de metodos, mas queria que ele nem tivesse acesso)
ou se é melhor fazer um menu só, e em cada "Link" do menu coloco uma verificação para tornar hidden ou não a opção?
Obs: utilizando bootstrap e vraptor
qual tecnica você acharia mais viável, sei que existe a preferencia, mas uma que seja mais válida e segura claro.
Bom dia, eu implementei o post de controle de login e agora o de permissão. Está beleza, ótimos blog por sinal.
Mas, como juntei o controle de login e permissão em um projeto somente acredito que os dois interceptors estejam conflitando e atrapalhando o TOMCAT.
Ao ir para o jsp inicial o navegador aparece o seguinte erro "Erro 310 (net::ERR_TOO_MANY_REDIRECTS)"
Já aconteceu isso com alguém?
Oi Carlos,
Você precisa verificar se um interceptor não esta interceptando o outro, senão ficará em loop infinito mesmo.
Cara, assim funciona bacana, porem e se eu quiser que as permissoes sejam dinamicas, por exemplo, que em tempo de execuca eu possa decidir que o usuario visitante pode passar a executar uma acao e eu queira gerencia isso por uma tela administrativa? Sua solucao e estatica, tem ideia de algo dinamica? Abraco!
Oi Renato,
De alguma forma você terá que dar uma identificação ao método que você esta chamando, pois só assim poderá vincular essa "key" com algo cadastrado no seu banco.
Talvez usar o nome do próprio método e pegá-lo no interceptor, ou usar códigos, que é um dos casos em um projeto que trabalho.
Algo como:
Washington, coloquei os dois posts de controle de login e permissão. Fiquei com uma dúvida referente a utilização do interceptor. Preciso dos dois "Login e Permission" ou somente o Permission resolve? Fiz os teste com os dois e tem acontecido o ERRO 310 TO MANY REDIRECT, tem alguma dica?
Oi Carlos,
Você precisa só de um interceptor e neste você pode colocar a lógica dos dois.
Normalmente utilizo apenas o PermissionInterceptor e a primeira verificação é a do login, que não passa de um
user != null
."MANY REDIRECT" seria porque no seu fluxo de dados há um redirect para uma URI que novamente entra no interceptor e faz novamente o redirect.
Se foi feito um redirect, então este deve ir para um lugar que o usuário tenha permissão, senão ele ficara em loop, pois nunca terá permissão.
Olá Washington,
Estive usando este modelo de login para verificar na minha aplicação, mas observei que você fez um outro modelo nos posts recentes na publicação devmedia.
Qual seria melhor a anotação public ou leitura de classes para liberação.
Oi Charles,
Hoje em dia uso a anotação Public, pois assim você pode espalhá-la por diversos controllers sem se preocupar em qual exatamente ela esta. (:
Excelente Post! Parabéns! Preciso desenvolver o seguinte. Devo criar um perfil, depois atribuir permissões a esse perfil(Acesso a tela de Cliente, ocultando ou mostrando os botões de editar cliente, excluir cliente...) depois ligar os usuários a perfis. Tantos os perfis como as permissões devem ser criadas dinamicamente, guardando no banco. Você teria algum exemplo assim para apresentar? Obrigado pela atenção.
Oi Donizete,
Antes da solução você precisa saber:
Perfil tem vários acesso?
Acesso tem vários perfis?
Usuário tem vários perfis?
Perfils tem vários usuário?
Faz o MER e depois pense na solução Hibernate.
Também esqueci mencionar que teremos perfis pai e perfis filhos...
Oi Donizete,
Sem saber exatamente o seu MER, creio que será necessário um ManyToMany com atributos devido a complexidade dos relacionamentos.
Assim dentro de cada tabela ManyToMany você poderá ter outros relacionamentos.
A segunda parte deste artigo mostrará como fazê-lo, terça-feira estará no ar.
Oi Donizete,
Você precisa fazer uma análise primeiro do que você quer e fazer os relacionamentos, pra depois ver como resolver no Hibernate.
Ver se um perfil tem vários acessos e esse mesmo acessos podem estar ligados a vários perfis.
Se o usuário poderá ter vários perfis e da mesma forma se esses perfis podem estar ligados a vários usuários etc.
Sem essa análise não tem como saber a sua necessidade.
Massa botelhos, pra quem quiser testar as anotações do método pode fazer algo do tipo assim:
Muito bom Rodrigo!
Obrigado pela contribuição. (:
Ótimo post. O uso de anotaçoes facilitou e deixou o codigo limpo demais
Concordo, por isso estou lendo todo material da Caelum ! o negocio é que tenho que por isso para rodar no sistema que temos aqui.. e estou sozinho para Strus jsf gwt e Vraptor.. a formula para isso e Ctrl + C e Ctrl + V !
Obrigado
sim eu executei com esta url mas está dando erro na hora de iniciar o tomcat estava aparecendo o erro na ultima tag context do arquivo server.xml segue a tag
<Context docBase
so que arrumei com o nome do projeto e tudo mas ainda não funciona, da erro.
clayton
Este é o meu projeto, ta faltando 1%
por favor da uma olhada!
http://www.4shared.com/file/LfJsAteW/cp4.html
brigado ai cara , se não for pedir de mais vc pode me mostrar um jeito de criar um login com esse seu projeto para quando a pagina for interceptada aparece um pop up para o usuario digitar usuario e senha do admin para entrar nas paginas do administrador senão da erro, ou seja quero que as paginas do administrador abram somente se o usuario digitar o usuario e a senha previamente cadastrados, pode ser no codigo fonte mesmo , pois é uma medida simples estou sem tempo para entregar este projeto e falta somente a autenticação e outras coisas que já estou resolvendo , se vc puder me ajudar ou indicar algum material .
Cara acho que JQuery faz issso Thicicbox!
Oi Clayton,
Vou colocar aqui uma idéia que me veio a cabeça para você entender o caminho das pedras:
A idéias seria mais ou menos essa.
Boa sorte. (:
wbotelhos, Acho que o perfil tem que vim do banco junto com o usuário e senha, pois não conseguir implementar os 2 DEMOS ! Se conseguir integrar os 2 poste por favor para nos !
Grande abraço
junior.
Oi Júnior,
O perfil tem que ser buscado no banco mesmo, dentro do objeto
Usuario
.Para exemplo foi criado o usuário no
IndexController
, mas na vida real você pegaria ele do banco.Juntar os dois posts seria apenas juntar os códigos, tirando isso é lógica particular de cada aplicação.
BLZ vou tentar , mas vou fazer sem a consulta a banco , quero deixar somente um usuario padrao para a area administrativa , pois estou sem tempo e a parte de login principal do sistema já está pronta tenho que entrega este projeto na segunda ... obrigado pelos exclarecimentos e pelas ideias , não coloquei em pratica ainda mas irão ser muito uteis .
E junior eu bem que queria te ajudar mais estou sem tempo e ainda não sei muita coisa sobre programação para web , estou terminando meu projeto na raça mesmo ... espero que da proxima vez eu possa te ajudar, ate mais …
clayton
É sobre o que teu projeto ? poderia disponibilizar teu projeto para estudos ?
Grande abraço e boa sorte !
Oi Clayton,
Então o erro não é no projeto e sim nas configurações do Tomcat.
Não é preciso fazer nenhuma configuração adicional diretamente no Tomcat para os projetos rodarem.
Tenta reinstalá-lo ou tenta dar uma Googada sobre o erro.
Aqui fala um pouco sobre: http://tomcat.apache.org/tomcat-5.5-doc/config/context.html
Estou confuso nisto:
O Lucas do Guj falou para eu anotar :
na entidade !
Oi Júnior,
Como o exemplo é didático o usuário é criado quando é acessado a raiz do projeto, ou seja, "/".
Se você não acessar essa url e ir direto para outra página, poderá dar esse
NullPointer
, pois não terá sido criado o usuário na sessão.Antes de ir para qualquer outra página, se certifique de ter acessado a URL absoluta para ser criado o usuário no sessão.
tipo assim:
bom dia cara peguei seu exemplo estou me baseando nele para terminar um projeto de faculdade mais está dando esse erro:
HTTP Status 404 - /controle-permissao-vraptor-3
o que pode ser que está errado .
Oi Clayton,
Esse erro 404 é um erro que ocorre quando a página requisitada não é encontrada.
Podem ser dois motivos: o servidor não esta rodando a aplicação ou a URL esta incorreta.
Baixei o projeto e o executei com essa url:
http://localhost:8080/controle-permissao-vraptor-3/
gostaria de pegar o NOME / SENHA / PERFIL do banco e jogar para a determinada area..
tipo:
Administrador /admin/
Usuario /Usua/
etc..
Oi Júnior,
Você já tem todos os dados do usuário na sessão, basta usá-los:
Oi Júnior,
O
perfil
dentro do for é cada anotação em cima do método que você esta tentado acessar, ou seja, os perfis que têm permissão de usar tal método.O
user.getPerfil()
é o perfil do usuário, então você pergunta se o perfil do usuário é igual algum que tenha permissão no método que ele esta tentando executar.Se esta dando
NullPointer
, é porque o usuário da sessão é nulo, ou o usuário que esta na sessão não tem um perfil, o que é nulo também.Gostaria de saber como se faz para pegar tudo isso do banco na hora de um login ?
Controle de Permissão com VRaptor 3
Controle de Login com VRaptor 3
Grande abraço
SHOW CARA !
Oi Júnior,
O que exatamente você precisa pegar no banco na hora do login? Não entendi direito.
Mas você pode consultar o banco a partir do método de login mesmo ou fazer isso diretamente no interceptor, coisa que não recomendo, já que a intenção do interceptor é fazer verificações de coisa já consultadas no banco.
Ótimo post. Uma duvida:
Você saberia me dizer como eu poderia faser em PHP, uma condição para somente exibir determinado link se o acesslevel do usuario foi =100
Eu ja criei um painel de login, porem gostariaque se fosse acesslevel normal exibir o link para o PAINEL NORMAL e se fosse acesslevel = 100 exibir o link para o painel de ADM.
Oi Jean,
Para aparecer um menu ou outro de acordo com o valor de uma variável chamada
acesslevel
da sessão você pode fazer da seguinte forma:Caso não seja possível, existe algum framework para auxiliar no controle de permissões no java SE
Valeu!
Fala Walter,
O controle que faço é manual. Para cada janela (jFrame, jDialog...) eu tenho um método que recebe o objeto
Usuario
e de acordo com seu perfil, então desabilito ou sumo com os componentes.Se você for utilizar esse método em seu código Java fora das janelas, então poderá mudar estes components para
static
e acessá-los, por exemplo, assim:Infelizmente não conheço nenhuma framework para se trabalhar com permissão em Java SE, mas também nunca parei para pesquisar.
Olá Washington,
Parabéns achei muito interessante esse artigo, eu trabalho com JAVA SE, e gostaria de saber se e possível aplicar essa idéia a Desktop?
Abraço, ate mais!
Ótimo post. Uma duvida:
Como eu faria para exibir na view somente os links para os recursos que um determinado usuario tem acesso? Por exemplo, um membro nao poderia ver o link para o admin.
Alguma ideia?
Fala Daniel,
Para apresentar ou não o menu na página, já foge do interceptor, sendo tratado na tela mesmo.
Isso depende da linguagem, mas vamos dizer que esta utilizando JSP com um usuário na sessão chamado "user":
Verifica se o perfil do usuário é igual a ADM:
Verifica se dentre a lista de perfis do usuário, contém ao menos um que seja ADM:
Isso faz esconder o menu, porém a página pode ser acessada pela URL, nesse caso devemos interceptar o método que chama a página. Vamos dar exemplo do método
listar
que chama a página de listagem:Graaande post, Washington (não no sentido literal =P)
Ficou muito bacana, man! Parabéns ;)
Olá Washington,
Meus parabéns parabéns seu blog,tem muito post's bem aplicaveis ;)
E esse Post caiu como luva para mim, pois ainda esses dias na empresa onde eu trabalho estavamos discutindo como seria isso com o vraptor.
Colocarei em práticia, assim que terminar, voltarei com as dúvida! =)
abraços
É muito bom saber que estou ajudando Alciara.
Obrigado pelos parabéns. (: