Minha contribuição - Controle de acesso VRaptor 3

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!

Galera, a classe RestrictionChecker é a classe responsável por verificar se há restrições e, se houver, verificar se o usuário em questão possui permissão para acessar os recursos.
A classe encontra-se no jar que enviei, então vcs podem criar um ComponentFactory para essa classe e passá-la no construtor do Interceptor. Ou se quiserem, podem dar new quando forem usá-la tbm…quem manda é o freguês

O método que verifica se há restrições no método é o hasRestriction. O método accepts do Interceptor ficaria assim:

	@Override
	public boolean accepts(ResourceMethod method) {
		//this.restrictionChecker = RestrictionChecker(); ***prefiram injetar o objeto...é mais elegante! ^^
		return this.restrictionChecker.hasRestriction(method.getMethod());
	}

O hasRestriction recebe um java.lang.reflect.Method, e não um ResourceMethod do VRaptor!!!

Feito isso, se houver restrição, então vc deve checar se o usuário logado está restrito. O responsável por isso é o método checkRestrictions da RestrictionChecker (derr rs)…
Ele recebe dois parâmetros: java.lang.reflect.Method, e a interface br.com.bronx.accesscontrol.interfaces.Login. Essa interface é implementada pelo usuário. Já explico.

O método checkRestrictions lança uma Exception, a RestrictionAnnotationException. Essa exceção é lançada quando as annotations são utilizadas indevidamente. As mensagens são explicativas e ajudam muito a resolver o problema.

O retorno do método checkRestrictions é um objeto do tipo RestrictionResult. Esse objeto possui dois atributos: um boolean informando se o usuário tem ou não permissão de acesso (private boolean restricted) e uma String contendo o destino a ser seguido, caso o acesso seja negado (private String destination). O atributo destination guarda o caminho informado na annotation @AccessDenied. Pode ser tanto o loginPage ou o accessDenied, dependendo do motivo da restrição (usuário não está logado ou não possui as credenciais para acessar o recurso).

O Interceptor que utilizo aqui é esse em anexo.

Agora, o atributo Login.

Essa interface Login é uma interface que informa se o usuário está ou não logado no sistema.

Ela descreve 4 métodos:

	public boolean isLoggedIn();
	public void setLoggedIn(boolean loggedIn);
	public Profile getProfile();
	public void setProfile(Profile profile);

O método setLoggedIn(boolean loggedIn) geralmente é chamado no momento do login. Após realizar com sucesso o login do usuário, esse valor é setado como true, indicando que o usuário está logado.
Além disso, o setProfile(Profile profile) define o profile do usuário logado, que contém os papéis (roles) e nível de acesso do referido usuário.
Profile é a segunda e última interface a ser implementada pelo desenvolvedor. Possui apenas dois métodos:

	public List<String> getRoles();
	public int getAccessLevel();

Serve basicamente para a ferramenta conseguir extrair os dados do usuário.
Preferi utilizar uma lista de String ao invés de uma lista de Roles para ser o menos intrusivo possível no código do cara. Me lembraria o SelectItem do JSF!

Em anexo, implementação da interface Login e implementação da interface Profile, a título de exemplo. Tudo bem simples e comentado.

Uma possível maneira de usá-los, seria no método de login:

	//Na vida real, o método receberia um Usuario 
	public void logar(String login, String senha){
		//Busca o usuário com base nos dados informados, por exemplo
		login.setLoggedIn(true);//se usuário logou com sucesso
		login.setProfile(usuarioLogado);//idem
		//outras operações
	}

Resumindo: passem um Login para o método checkRestrictions da classe RestrictionChecker. A forma como vc vai implementar é irrelevante para a ferramenta.
É comum ocorrer umas NullPointerExceptions caso as interfaces não tenham sido implementadas adequadamente. Mas isso foge do controle da ferramenta.

É isso.

Testem aí. Se tiverem dificuldades, estamos aí para ajudar.
Depois de tudo configurado adequadamente, não tive problemas na utilização desse solução.
Espero que entendam o que escrevi e que façam bom proveito de tudo. hehe

Abs!

Muito bom, bronx =)

Isso deixa a autenticação/autorização bem fácil de usar…

No entanto, tenho dúvidas e sugestões:

  • O usuário da ferramenta precisa implementar quais interfaces? É suficiente que a entidade Usuario dele implemente Profile? ou ele tem que implementar o loginControl tb?
  • O interceptor da última mensagem está dentro do jar? Se adicionarmos o pacote br.com.bronx na configuração do VRaptor no web.xml, ele conseguiria ler as suas classes tranquilamente…
  • Tem algum jeito de setar páginas padrão pra login e para accessDenied? a meu ver seriam (quase) sempre as mesmas… qto menos configuração melhor

Além disso, se te interessa colocar esse seu código no código principal do vraptor, adoraríamos a contribuição =) Se não, tudo bem, podemos linkar para o seu projeto da página principal do VRaptor, para todo mundo que precisar de controle de acesso…

Abraços e parabéns =)

Hey man, valeu!!
Estava com medo de receber um “que merda”, ou “isso nem ajuda tanto”…hehehe

Quanto aos teus questionamentos:

  • O desenvolvedor teria que implementar as interfaces Login (para passar pro RestrictionChecker) e Profile (para passar para Login). Mas agora, posto da maneira que vc colocou, seria mais interessante ele implementar somente uma interface, não? Alteração [muito] simples. rs
  • Essas classes que enviei separadamente não estão no jar. Seriam implementadas pelo usuário. Podemos discutir se deixamos ela (Interceptor) no jar ou não, pois caso ela esteja dentro, não sei como o Interceptor receberia a implementação do desenvolvedor da interface Login ou de alguma outra que possamos criar, caso alteremos o código…
  • Esse recurso (páginas padrões) eu acabei não considerando, e pretendia deixar para a próxima versão. Mas é xuxú no palito, [muito mais] simples de alterar. ^^

Postar isso tudo aqui já mostra que quero deixar essa ferramenta a disposição da galera.
Colocá-la dentro do VRaptor seria style. Pouparia algum trabalho para a galera.

Vamos conversar sobre a melhor maneira de fazer isso.
Aliás, vamos discutir todos esses pontos que vc levantou.
Mas antes, seria interessante que você (e todos que quiserem ^^) testasse(m) a parada. Para ter um feeling do que foi feito, como funciona, e de possíveis melhorias além dessas que você sugeriu. As sugestões da galera são muito importantes nessas horas.

Mas enfim, usem! Não fiz pra ficar empoeirando no meu HD…rs

se vc receber o Login no construtor, e a implementação dele estiver anotada com @Component, o VRaptor vai saber injetar sem problemas, mesmo que esteja tudo dentro do jar…

Por enquanto, vc poderia colocar o código que vc já tem no github? http://github.com
Pra isso vc precisa saber mexer um pouco no GIT… tutoriais em http://github.com/guides

[]'s

Olá Bronx! Primeiramente parabéns pelo projeto. Ele vem a simplificar muito o controle de acesso da aplicação e já de início contando com recursos como roles e níveis de acesso.

Gostaria de fazer uma observação sobre a interface Profile. Ela me obriga a implementar o método:

Isto me complicou a principio, pois eu gostaria que estas roles fossem implementadas numa outra classe, o que facilitaria a persistência das mesmas no banco de dados, ou mesmo num diretorio LDAP.

Minha sugestão seria algo como:

Onde UserRole seria uma interface bem simples com apenas um método como:

Fica ai a sugestão. E mais uma vez parabéns!

Então Lucas, não tinha certeza quanto ao comportamento do VRaptor / Spring quanto à injeção de interfaces.

Sendo assim, fica simples deixar tudo pronto dentro do jar.
Só dificultaria caso o usuário precisasse tomar alguma ação mais específica caso o acesso fosse negado. Sei lá, gravar um log, ou algo do tipo. Mas aí ele poderia criar o próprio interceptor sem problemas. :smiley: :smiley:

Pensei em acrescentar os métodos setDefaultLoginPage(String defaultLoginPage) e setDefaultAccessDeniedPage(String defaultAccessDeniedPage) na classe RestrictionChecker. Assim, antes de executar o método checkRestrictions(…), o desenvolvedor informaria à ferramenta quais são as páginas de login e acesso negado padrões. Hard-coded, .properties…whatever. Ele só teria q fazer isso antes da verificação. E ainda assim as regras de utilização da annotation @AccessDenied seriam válidas. ^^
Mas isso só seria viável caso o Interceptor não esteja dentro do jar. Para que funcionasse com ele (Interceptor) dentro do jar, teríamos que passar essas informações através do Login que seria injetado…

Anyway, vou colocar a parada no github. Essa semana dou uma estudada e já lanço tudo lá.
Aliás, requisitos não funcionais: sugerem algum nome pro projeto?

Por ora, temos essas melhorias a serem feitas: páginas de login e acesso negado defaults, Interceptor padrão dentro do jar e a obrigatoriedade de implementar apenas uma interface, right?? Continuem sugerindo, e por favor, TESTEM!!

Hail sobreira!

Cara, juro que pensei nesse esquema de List, até porquê na implementação da ferramenta os papéis extraídos da annotation @Roles são armazenados num List. Porém, pensei que isso deixasse a implementação mais acoplada ainda à ferramenta. Fica mais simples devolver somente a lista com os nomes dos papéis.

Mas brother, a grande sacada do uso de interfaces é que ela fica totalmente independente da implementação. O que quero dizer, é que você pode implementar esse esquema que vc disse sem o menor problema. Faça da forma que vc propôs, criando sua própria classe UserRole e criando seu List. Na implementação do getRoles, poderia fazer algo do tipo:

        public List<String> getRoles() {
                List<String> stringRoles = new ArrayList<String>();
                for (UserRole userRole : this.userRoles) {
                      stringRoles.add(userRole.getRoleName());
                }
                return stringRoles;
        }

Enfim, só uma sugestão. Mas você pode fazer da maneira que quiser!!!
Por favor, teste a parada e dê um feedback. É minha primeira contribuição em algum projeto open-source, então as opiniões são extremamente importantes para mim. :thumbup:

Valeu galera. Novamente: TESTEM! rs

[]'s

Tem um jeito mais legal: anotar as lógicas de loginPage e accessDeniedPage:

@Resource
public class LoginController {
    //...
    @LoginPage
    public void login() {...}

    @AccessDeniedPage
    public void accessDenied() {...}
}

Tem como ler isso no startUp da aplicação e configurar sua ferramenta =)
Só mexer um pouquinho com a api interna do VRaptor, posso te ajudar com isso =)

Mesmo que a implementação do Login esteja fora do jar, o VRaptor consegue injetar sem problemas… A única coisa importante é que o Interceptor seja lido e gerenciado pelo vraptor, não importam nem onde ele está, nem onde as dependências dele estão…

bom… VAuth (VRaptor Authentication & Authorization), VAAS (VRaptor Authentication & Authorization System)…
mas acho que um nome mais aleatório seria legal tb…

Parabéns pela iniciativa Bronx.
Só uma dúvida, ele precisa de alguma lib fora as que vem com o VRaptor?
Estou testando aqui mas ao quando anoto a classe AccessControllerInterceptor com @Intercepts o sistema retorna o seguinte erro:

SEVERE: Servlet.service() for servlet default threw exception
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accessControllerInterceptor' defined in file [/Users/danielkist/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/loja/WEB-INF/classes/testes/AccessControllerInterceptor.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [br.com.bronx.accesscontrol.restriction.RestrictionChecker]: : No matching bean of type [br.com.bronx.accesscontrol.restriction.RestrictionChecker] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [br.com.bronx.accesscontrol.restriction.RestrictionChecker] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:698)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:192)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:984)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:886)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:479)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:450)
	at org.springframework.beans.factory.support.AbstractBeanFactory$2.getObject(AbstractBeanFactory.java:328)
	at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:43)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:385)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:375)
	at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1069)
	at org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors(BeanFactoryUtils.java:221)
	at br.com.caelum.vraptor.ioc.spring.VRaptorApplicationContext.getBean(VRaptorApplicationContext.java:240)
	at br.com.caelum.vraptor.ioc.spring.SpringBasedContainer.instanceFor(SpringBasedContainer.java:58)
	at br.com.caelum.vraptor.util.collections.Functions$1.apply(Functions.java:32)
	at br.com.caelum.vraptor.util.collections.Functions$1.apply(Functions.java:30)
	at com.google.common.collect.Lists$TransformingRandomAccessList.get(Lists.java:431)
	at java.util.AbstractList$Itr.next(AbstractList.java:345)
	at com.google.common.collect.Iterators$7.computeNext(Iterators.java:602)
	at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:135)
	at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:130)
	at com.google.common.collect.Lists.newArrayList(Lists.java:131)
	at com.google.common.collect.Collections2$FilteredCollection.toArray(Collections2.java:219)
	at br.com.caelum.vraptor.interceptor.DefaultInterceptorRegistry.interceptorsFor(DefaultInterceptorRegistry.java:50)
	at br.com.caelum.vraptor.interceptor.InterceptorListPriorToExecutionExtractor.intercept(InterceptorListPriorToExecutionExtractor.java:42)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
	at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:80)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
	at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:67)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:48)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59)
	at br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:62)
	at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91)
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:55)
	at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
	at java.lang.Thread.run(Thread.java:637)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [br.com.bronx.accesscontrol.restriction.RestrictionChecker] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:896)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:765)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:680)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:771)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:691)
	... 52 more

[]'s

Daniel

yorgan, tenta colocar isso no seu web.xml:

<context-param>
    <param-name>br.com.caelum.vraptor.packages</param-name>
    <param-value>br.com.bronx</param-value>
</context-param>

se já tiver um param com esse nome, coloque o valor acima, separado por ; sem espaços

Não deu certo.
Mas a classe RestrictionChecker não deveria estar anotada como @Component?

[]'s

Daniel

É sempre que precisamos de algo relacionado a perfil de acesso sempre acaba vindo o cliente e suas loucuras

“que tal perfil pode fazer isso mais não pode fazer aquilo e ele também é gerente mais esse perfil era o perfil de administrador aquilo ,mas ainda tem que quem era administrador nao pode fazer aquilo mais a nao ser que pelo menos blablabla”

o cliente te passa a regra de negocio mais cabeluda do mundo ligado ao perfil de acesso que nem sempre o pensamento “hierarquia” resolve…

Parabens pela iniciativa …para quem ta começando pensar num metodo de implementação de perfil é sempre complicado…

Vo postar um modelo bem simples que eu uso a mais de 5 anos pra agregar mais ao topico e que sempre me atendeu em TODOS os caso tanto pra criação,quanto pra mudança e o legal é que ele desacopla seu sistema totalmente das hierarquias de acesso ele nao usa o velho roles que pra mim sempre foi algo que mais atrapalhou do que ajudou

ele consiste em 2 classes(pensando que no meu caso sempre utilizo banco de dados para esse acesso,pra outros casos como ldap por exemplo seria pouca mudança a ser feita) vamo lá

então vão ser 2 classes entidades (3 tabelas de banco) , 2 interceptor , 2 anotações
lembrando que estou postando exemplo com o hibernate

Acao.java


import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="acoes")
public class Acao {	
	@Id
    @GeneratedValue
    @Column(name="aco_id")
    Long id;
	@Column(name="aco_titulo")
	String titulo;
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getTitulo() {
		return titulo;
	}
	public void setTitulo(String titulo) {
		this.titulo = titulo;
	}
	
}

Perfil.java


import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;


@Entity
@Table(name="perfil")
public class Perfil {
	@Id
    @GeneratedValue
    @Column(name="per_id")
	Long id;
	@Column(name="per_titulo")
	String titulo;
	
	
	@ManyToMany(cascade = {javax.persistence.CascadeType.ALL})
	@JoinTable(   
            name="acesso",   
            joinColumns=@JoinColumn(name="per_id", referencedColumnName="per_id"),   
            inverseJoinColumns=@JoinColumn(name="aco_id", referencedColumnName="aco_id")   
        )  
    @Fetch(FetchMode.JOIN) 
	private  List<Acao> acoes;
	

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getTitulo() {
		return titulo;
	}

	public void setTitulo(String titulo) {
		this.titulo = titulo;
	}

	public List<Acao> getAcoes() {
		return acoes;
	}

	public void setAcoes(List<Acao> acoes) {
		this.acoes = acoes;
	}	
	
}

Beleza… explicando as classe eu sempre falo que um determinado perfil pode realizar x acoes …
a ideia é exatamente igual ao pensamento
a pessoa pode fazer isso e isso mais num pode fazer aquilo , se amanha muda fica facil pq apenas retiro aquela ação daquele determinado perfil
que resolve o problema para o PERFIL todo diferente se amanha acontece de mudar apenas uma regra para esse perfil que va entrar em conflito apenas crio outro perfil com as mesmas ações diferenciando o que pode o nao fazer a mais ou a menos…

vamo pensar em exemplo prático vamo lá cadastro de cliente…

vamos supor que o seu sistema contenha 6 tipo de ações diferentes a serem feitas com um cliente

Poder listar
Poder inserir
Poder alterar
Poder desativar
Poder deletar
Poder ver detalhes sobre o cliente

são ações isoladas…ou seja ele pode ou não pentencer a um perfil de acesso ou a mais de 1 correto??

agora chega o cliente e te fala entao
o admin pode fazer tudo ok ??

ai a gente
Perfil : admin
*Poder listar
*Poder inserir
*Poder alterar
*Poder desativar
*Poder deletar
*Poder ver detalhes sobre o cliente

ai ele fala o gerente pode fazer quase tudo so num pode deletar do banco mas pode desativar

ai a gente
Perfil : gerente
*Poder listar
*Poder inserir
*Poder alterar
*Poder desativar
*Poder ver detalhes sobre o cliente

se amanha aparece uma regra nova com funcionalidade nova ou alguma funcionalidade ja implementada se divida em pontos distindos em duas
imagine que voce monte uma listagem que exiba dados em valores ai o cliente te pede olha esse valor ai tem que ser colocado no perfil ltamem se ele pode ver e gerente não pode ver mais diretor pode blz?

ai a gente cria a ação de poder ver valores e atribui esse poder só a quem deve…assim falando parece ser algo complicado…mas vamos continuar

pra exemplificar vamos fazer uns inserts no banco

//Primeiro voce cadastra todas as ações que seu sistema possui
Acao a1=new Acao();
a1.setTitulo("Listar clientes");
Acao a2=new Acao();
a2.setTitulo("Inserir clientes");
Acao a3=new Acao();
a3.setTitulo("Alterar clientes");
Acao a4=new Acao();
a4.setTitulo("Desativar clientes");
Acao a5=new Acao();
a5.setTitulo("Deletar clientes");
Acao a6=new Acao();
a6.setTitulo("Visualizar detalhes de cliente ");
.
.
.
.
//continuando com todas as ações possiveis

//Ai vamos cadastrar um perfil de exemplo
Perfil gerente=new Perfil();
gerente.setTitulo("Gerente");
List<Acao> acoes=new ArrayList<Acao>();
acoes.add(a1);
acoes.add(a2);
acoes.add(a3);
acoes.add(a4);
acoes.add(a6);
gerente.setAcoes(acoes);

//ai manda salvar no hibernate talz isso fazendo na mão eu uso uma interface que ja faz esse crud pra facilitar

//Beleza perfil criado agora vamos aos interceptors e anotações

primeiro as anotações
NotCheckSession.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface NotCheckSession {}

VerifyAccess.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface VerifyAccess 
{
	int action();
	
}

e finalmente os dois interceptor um primeiro que intercepta todos os recursos pensando que para fazer algo seu usuario TEM que estar logado

AuthenticationInterceptor.java

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.core.InterceptorStack;
import br.com.caelum.vraptor.interceptor.Interceptor;
import br.com.caelum.vraptor.resource.ResourceMethod;
import br.com.caelum.vraptor.view.Results;
import br.com.jslsolucoes.granchef.annotations.NotCheckSession;
import br.com.jslsolucoes.granchef.controllers.UsuarioController;
import br.com.jslsolucoes.granchef.dao.UsuarioDao;
import br.com.jslsolucoes.granchef.models.Acao;
import br.com.jslsolucoes.granchef.models.Usuario;
import br.com.jslsolucoes.granchef.others.MySession;


public class AuthenticationInterceptor implements Interceptor {
	MySession sessao;
	Result result;
	UsuarioDao dao;
	public AuthenticationInterceptor(MySession sessao,Result result,UsuarioDao dao)
	{
		this.result=result;
		this.sessao=sessao;
		this.dao=dao;
	}
   
    //Verifica se a anotação @NotCheckSession está presente ..por exemplo no seu metodo login ele iria ficar num loop infinito
   //pois eu redireciono pra login so que antes ele vai ver tem usuario na sessao redireciono de novo ai fica assim eternamente
//pra evitar isso criei essa anotação que é no caso de um metodo não precisar ser checkado sessão o que dificilmente acontece
    public boolean accepts(ResourceMethod method) {
    	return  !method.getMethod().isAnnotationPresent(NotCheckSession.class);
    }

    public void intercept(InterceptorStack stack, ResourceMethod method, 
                        Object resourceInstance) throws InterceptionException {
    	//aqui eu pego se tenho um objeto usuario na sessao e vejo se ele é nulo que no caso nao esta logado
        if(this.sessao.getObjectSession("usuario")==null)	result.use(Results.logic()).redirectTo(UsuarioController.class).login();
    	else stack.next(method, resourceInstance);    
    }
}

AccessInterceptor.java

package br.com.jslsolucoes.granchef.interceptors;

import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.core.InterceptorStack;
import br.com.caelum.vraptor.interceptor.Interceptor;
import br.com.caelum.vraptor.resource.ResourceMethod;
import br.com.caelum.vraptor.view.Results;
import br.com.jslsolucoes.granchef.annotations.VerifyAccess;
import br.com.jslsolucoes.granchef.controllers.GranchefController;
import br.com.jslsolucoes.granchef.models.Acao;
import br.com.jslsolucoes.granchef.models.Usuario;
import br.com.jslsolucoes.granchef.others.MySession;


public class AcessInterceptor implements Interceptor {
	MySession sessao;
	Result result;
	int action;
	public AcessInterceptor(MySession sessao,Result result)
	{
		this.result=result;
		this.sessao=sessao;
	}
	
    public boolean accepts(ResourceMethod method) {
    	if(method.getMethod().isAnnotationPresent(VerifyAccess.class))
    	{
    		action=method.getMethod().getAnnotation(VerifyAccess.class).action();
    		return true;
    	}
    	else return false;
    }

    public void intercept(InterceptorStack stack, ResourceMethod method, 
                        Object resourceInstance) throws InterceptionException {
    	
    	boolean hasAction=false;
    	for(Acao a : ((Usuario) sessao.getObjectSession("usuario")).getPerfil().getAcoes())	if(a.getId()==action) hasAction=true;
    	if(!hasAction) result.use(Results.logic()).redirectTo(GranchefController.class).noAction();
    	else stack.next(method, resourceInstance);
    }
}

Prontinho com esses 2 interceptor estou pronto pra utilizar qualquer recurso
agora vamos colocar meus exemplos praticos

@Resource
public class ClienteController{
         //atributos
         //construtor


        //ai começo a brincadeira
       //eu falo que pra listar precisa tem o id 1 ligado ao seu perfil seja ele qual for eu nao falo o perfil que acessa mais sim a ação que o acessa
        @VerifyAccess(action=1)
        public void listar(){

        }

 @VerifyAccess(action=2)
        public void inserir(){

        }

 @VerifyAccess(action=3)
        public void editar(){

        }
       //assim pra qualquer metodo ou seja fica mais simples pq acabo usando apenas 1 anotação
       //ai o esquem se vira com o resto...
      //caso eu queira que não precise checar sessao ou ação é so colocar a anotação em cima que ele passa batido em tudo

      @NotCheckSession
      public void teste(){
     
      }
}

Bom pessoal esse é so mais um exemplo de como implementar um perfil de acesso eu particularmente gosto …
alguma duvida da um toque ai mas sempre atendeu e muito bem o que precisei fazer ligado a essa area

bom, boneazul… esse seu modelo é bem genérico, mas é bastante mais complexo também, e mais difícil de se trabalhar…

uma dica:
@VerifyAccess(action=1) não é nem um pouco bom… assim vc é obrigado a saber que a ação de id=1 é XXX, e se o cara que adicionou isso no banco não souber, ferrou, e se o desenvolvedor não souber ferrou… o desenvolvedor vai ter que ter um “catálogo de ações” enquanto estiver desenvolvendo…

Tem um boa prática chamada YAGNI (You ain’t going to need It ou Vc não vai precisar disso)… ou seja, nunca faça uma solução complicada, se uma simples for o suficiente por um simples “pode ser que o cliente fique louco e mude de idéia no meio do caminho” ou “acho que vou precisar disso mais pra frente”…

Sua solução é legal (fora a referência por ID) para qdo vc precisa dessa generalização…

Senão, a solução do bronx parece suficiente, simples e fácil de usar, e é isso que importa na maioria dos casos…

[quote=Lucas Cavalcanti]bom, boneazul… esse seu modelo é bem genérico, mas é bastante mais complexo também, e mais difícil de se trabalhar…

uma dica:
@VerifyAccess(action=1) não é nem um pouco bom… assim vc é obrigado a saber que a ação de id=1 é XXX, e se o cara que adicionou isso no banco não souber, ferrou, e se o desenvolvedor não souber ferrou… o desenvolvedor vai ter que ter um “catálogo de ações” enquanto estiver desenvolvendo…

Tem um boa prática chamada YAGNI (You ain’t going to need It ou Vc não vai precisar disso)… ou seja, nunca faça uma solução complicada, se uma simples for o suficiente por um simples “pode ser que o cliente fique louco e mude de idéia no meio do caminho” ou “acho que vou precisar disso mais pra frente”…

Sua solução é legal (fora a referência por ID) para qdo vc precisa dessa generalização…

Senão, a solução do bronx parece suficiente, simples e fácil de usar, e é isso que importa na maioria dos casos…

[/quote]

é Lucas realmente concordo quanto ao id preciso sempre saber a referencia…ponto negativo ainda não pensei numa solução dessa referencia como poderia ser…pq o desenvolvedor realmente tem que saber o id ligado a ação mas como tudo tem um porem ele não tem a necessidade de saber qual o perfil precisa ter essa ação perde-se de um lado ganha-se de outro…

o modelo de hierarquia eu nao precisaria saber o que faz ,mas se muda eu preciso buscar todos os que fazem e mudar…a ideía é que é bem mais flexivel…pq fica a cargo do proprio cliente definir seus perfis e tirar essa responsabilidade de código entende???eu nunca mais mudo uma linha de codigo pq a permissao é vista em tempo de execução se amanha muda um perfil todo nenhuma linha é modificada…quanto ao id realmente é algo a se pensar em algo mais programatico do que ids…

quanto a do bronx ele realmente é mais simples só que necessita sempre repetição de anotações na maioria dos casos e sempre fica na mão do programador saber o que cada pessoa tem que fazer o que na pratica pelo menos na minha empresa nunca rolou não sei outras …a minha ideia é justamente inversão de papeis o cliente ajusta como quer e o sistema se vira com o resto…longe de mim estar criticando algo … apenas coloquei outra solução só pra quem interessar ou ate ter alguma ideia relativa a esse id …postei apenas para enriquecer o tópico…

obrigado pelas dicas…

Estou tendo a seguinte excessão no redirecionamento ao acessar diretamente uma uri anotada com as restrições:

br.com.caelum.vraptor.view.ResultException: Given uri /user/accessDenied responds to method: public void br.eti.sobreira.bookstore.controller.UserController.accessDenied(). Use result.use(logic()).redirectTo(UserController.class).accessDenied() instead. br.com.caelum.vraptor.view.DefaultPageResult.checkForLogic(DefaultPageResult.java:102) br.com.caelum.vraptor.view.DefaultPageResult.redirect(DefaultPageResult.java:92) br.eti.sobreira.bookstore.interceptor.AccessControllerInterceptor.intercept(AccessControllerInterceptor.java:46) br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:41) br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59) br.com.caelum.vraptor.util.hibernate.HibernateTransactionInterceptor.intercept(HibernateTransactionInterceptor.java:45) br.com.caelum.vraptor.core.InstantiatedInterceptorHandler.execute(InstantiatedInterceptorHandler.java:41) br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59) br.com.caelum.vraptor.interceptor.InterceptorListPriorToExecutionExtractor.intercept(InterceptorListPriorToExecutionExtractor.java:46) br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46) br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59) br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:80) br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46) br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59) br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:67) br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:46) br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:59) br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:62) br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91) br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:55) br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)

Meu método list() anotado está assim:

@LoggedIn @Roles(roles="admin") @AccessDenied(loginPage="/user/accessDenied", accessDeniedPage="/user/accessDenied") public List<User> list() { return dao.list(); }

O Interceptor que estou usando é AccessControllerInterceptor fornecido pelo Bronx.

bronx ! poupou muito tempo de trabalho meu !

Muito Agradecido Irmão … precisando tamos ai !

Otima iniciatica !

bronx …

Eu estou com um projeto aqui que é assim !

CONTROLE DE USUARIOS

USUÁRIOS------------------------GRUPOS-------------------PERMISSÕES------------------SISTEMA-----------MENU---------------PÁGINAS
Junior ADMIN Ler/Pesquisar/Editar CAC/NOTAS L/P/E x.jsp/y.jsp/h.jsp

O que você acha ?

Achei bacana esse esquema de autenticação. As sugestões do pessoal também estão muito boas. Eu só vou dar umas dicas que podem ajudar:

  1. Aquele esquema do separador é meio estranho. Acho que, ao invés de:
@Roles(roles="gerente;supervisor")

Poderia mudar para:

@Roles(roles={"gerente","supervisor"})

Não é difícil a implementação (roles se tornaria um array de String), e quando só há um parâmetro, as chaves são opcionais. Ficaria assim:

@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
	String[] roles();
	RolesPolicy policy() default RolesPolicy.DISJUNCTION;
}
  1. A aplicação VRaptor com a qual trabalho, existe a necessidade de ter logout. Dei uma olhada rápida no seu código, mas ainda não sei como acrescentaria essa “feature”. Mas basicamente seria uma action anotada assim:
@Resource
public class MeuRecursoController {  
    
    @LoggedOut
    public void tchau() {
    
    }
}

E que por trás dos panos, removeria o usuário profile presente na sessão.

  1. Pra quem usa Javascript, enxertar uma tela de login quando se esperava um pequeno bloco de html é frustrante. O interessante é retornar o erro HTTP (401) para que o método Ajax saiba tratá-lo. Acredito que é só acertar isso no interceptor que verifica a autenticação.

É isso. Espero ter ajudado.

Perfect! Assim ficaria muito mais fácil e elegante fazer a parada.
De qualquer forma, ainda assim deixaria os métodos setDefaultLoginPage e setDefaultAccessDeniedPage, pois as vezes a página de login do cara pode nao se referenciar a uma página/recurso controlado pelo VRaptor.

A implementação do Login estará obrigatoriamente fora do jar. 8) O que quis dizer é que caso o desenvolvedor precise utilizar os métodos setDefaultLoginPage e setDefaultAccessDeniedPage ao invés de anotar os recursos da maneira que você falou, eles terão que criar sua própria implementação do Interceptor, pois a que viria dentro do jar nao conseguiria receber essas informações, a não ser que elas estivessem dentro do Login que seria injetado!! :wink:

Hahaha…Criatividade passou longe no “VAAS”…hehehehe
Pensei em RestricTRex…sei lá…
Daqui a pouco vão sugerir “Cadillac & Dinossaurs”…kkkk

yorgan,

Eu até tinha anotado o RestrictionChecker com @Component, mas como não sabia que os pacotes dentro dos jars também poderiam ser gerenciados pelo VRaptor, acabou não funcionando e eu tive que tirar a anotação e acabei criando um ComponentFactory para essa classe.
Faça o mesmo: crie seu ComponentFactory para o RestrictionChecker, ou dê “new” diretamente no Interceptor. Corrigiremos isso next time! ^^

boneazul,

Bela contribuição.
Muitos controles de acesso utilizam a idéia de roles. Por isso que dei atenção a esse esquema.
Sua abordagem é diferente, mas achei interessante.
De qualquer forma, agregou ao tópico! :smiley: :smiley: Valeu.

sobreira,

Então, o VRaptor não permite “self-redirecting” dessa maneira.
Entendo que nesses casos é interessante que o usuário utilize o redirecionamento da maneira descrita, mas lançar uma exception por ele nao ter feito assim acaba limitando um pouco a parada. :frowning:
Porém, não me lembro o que fiz para solucionar isso :frowning: . Estou na casa da minha namorada e meu note não está aqui. Shit.
Mais tarde eu posto o que fiz.
Porém, como um workaround rápido e fácil, faça o seguinte: altere o AccessControllerInterceptor para que, no lugar do Result, ele receba um HttpServletResponse. Se não me engano o VRaptor consegue injetar sem precisar criar ComponentFactory para isso.
Feito isso, na linha onde o result realizava o redirecionamento, use o método sendRedirect do HttpServletResponse.
Como disse, mais tarde eu falo o que fiz pra contornar este problema. :thumbup:

Mas é isso galera. Postem as dúvidas, e principalmente, as sugestões!!!
Abs!!!

Nice. Confesso que foi minha primeira experiência com annotations. Não tinha (tenho) muita experiência com isso. Aliás, com Java mesmo tenho menos de um ano… :oops:
Mas é bem simples deixar dessa maneira, sem contar que fica bem mais elegante. Boa sugestão! :wink:

Cara, posso estar enganado, mas acho que isso foge um pouco do objetivo da ferramenta. Vamos ter um feeling sobre isso conforme utilizarmos a parada. Mas a sugestão já tá registrada. ^^

Entendo. Como você disse, bastaria fazer um Interceptor personalizado. Caso o restrictionResult.isRestricted() seja verdadeiro, basta forçar um 401. Mas ai, como é muito específico, seria melhor cada um criar sua própria implementação.

[quote=Leonardo3001]
É isso. Espero ter ajudado.[/quote]

Não tenha dúvidas!! :smiley: :smiley: