Quero lançar um plugin de controle de acesso, mas queria saber algumas coisas antes, como padrões de nome, diretório, etc. E também opiniões e sugestões das pessoas
Já tinha dado uma olhada em alguns posts do GUJ sobre plugins de controle de acesso pro VRaptor, mas acheis todos complicados e não atendiam todas as minhas necessidades, então decidi fazer uma mais simples.
Eu já estou usando o código em uma projeto meu, até onde sei está funcionando, mas ainda faltam testes unitários, testes de performance, essas coisas.
Minhas dúvidas
[list]Como lanço meu códgo como plugin, basta eu exportar para um jar e postar em algum lugar?[/list]
[list]Quais arquivos são necessários?[/list]
[list]Existe algum padrão de nomes? Por exemplo, estou chamando as classes de Resource e os métodos de métodos mesmo.[/list]
Resumo de como funciona
O controle de acesso é baseado em papéis (roles), então está bem genérico, podendo ser utilizado não só para logado/não-logado, mas para qualquer papel.
O plugin é basicamente um interceptor que lê as anotações dos Controllers e métodos anotados com @AllowAccessTo(“papel1, papel2, papel3”) e @DenyAccessTo(“papel4, papel5, papel 6”).
A princípio, todos os métodos e classes são livres para serem acessados. Caso a classe ou o método seja anotado, então vai ser controlado.
Se um método estiver anotado com o @AllowAccessTo(“loggedUser”), então somente usuário com esse papel poderão acessar o método.
Se um método estiver anotado com o @DenyAccessTo(“notLoggedUser”), então todos os usuários com um papel diferente poderá acessar aquele método.
Se uma classe Controller estiver anotada, essa regra vale para todos os métodos que não estejam anotados. Ou seja, mesmo que uma classe esteja anotada com @AllowAccessTo(“admin”), se o método estiver anotado com @AllowAccessTo(“client”), então esse método só poderá ser acessado pelo usuário com o papel “client”. Eu decidi que o método anotado tem prioridade sobre a classe anotada.
É possível adicionar mais de um papel para o método ou a classe. Só usar @AllowAccessTo(“client, admin”). Sendo assim, o usuário com o papel “client” ou com o papel “admin” poderá acessar.
A lógica de autenticação ainda tem que ser implementada pelo programador, pois não tem como o plugin saber quem é de uma papel e quem é do outro. Então o programador tem que injetar o @Component UserAccessDefinitions para poder adicionar os papeis ao usuário.
Por exemplo:
@Post
public void authenticate(User user){
if(this.userDao.retrieveUserWith(user.getLogin(), user.getPassword()!=null){
this.userAccessDefinitions.getRoles().add("loggedUser");
// Mais coisas aqui
}else{
//Mais coisas aqui
}
}
Ainda vai ter a opção de adicionar permissões especificando os métodos e as classes, e não por papel. Só chamar o método do @Component UserAccessDefinitions, que adicionando métodos e classes neles.
Por exemplo, se o usuário tentar entrar com SQLInjection em um dos campos, se adiciona o método que leva o usuário para a página de login para os métodos negados:
@Post
public void authenticate(User user){
if(sqlInjectionIsPresentOn(user.getLogin()) || sqlInjectionIsPresentOn(user.getPassword()){
try {
this.userAccessDefinition.getMethodDenials().add(UserController.class.getMethod("login"));
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
if(this.userDao.retrieveUserWith(user.getLogin(), user.getPassword()!=null){
this.userAccessDefinitions.getRoles().add("loggedUser");
// Mais coisas aqui
}else{
//Mais coisas aqui
}
}
}
Seguem as classes que eu criei para o plugin:
-Anotações
[code]/**
*
*/
package com.eAccessCon.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- @author maiconfz
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.TYPE, ElementType.METHOD })
public @interface DenyAccessTo {
public String value();
}[/code]
[code]/**
*
*/
package com.eAccessCon.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- @author maiconfz
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.TYPE, ElementType.METHOD })
public @interface AllowAccessTo {
public String value();
}[/code]
-Classe de permissões da sessão do usuário (Serve para definir os papeis do usuário, permissões para métodos e Controllers específicos)
[code]package com.eAccessCon.session;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.SessionScoped;
import com.eAccessCon.provider.EAccessConInitialUserAccessDefinitionProvider;
/**
- @author maiconfz
*/
@Component
@SessionScoped
public class UserAccessDefinitions implements Serializable {
private static final long serialVersionUID = 5301380832545035596L;
private List<Method> methodDenials;
private List<Method> methodPermissions;
private List<Class<?>> resourceDenials;
private List<Class<?>> resourcePermissions;
private List<String> roles;
/**
* @param valuesProvider
*/
public UserAccessDefinitions(
EAccessConInitialUserAccessDefinitionProvider valuesProvider) {
if (valuesProvider.getMethodDenials() != null) {
this.methodDenials = valuesProvider.getMethodDenials();
} else {
this.methodDenials = new ArrayList<Method>();
}
if (valuesProvider.getMethodPermissions() != null) {
this.methodPermissions = valuesProvider.getMethodPermissions();
} else {
this.methodPermissions = new ArrayList<Method>();
}
if (valuesProvider.getResourceDenials() != null) {
this.resourceDenials = valuesProvider.getResourceDenials();
} else {
this.resourceDenials = new ArrayList<Class<?>>();
}
if (valuesProvider.getResourcePermissions() != null) {
this.resourcePermissions = valuesProvider.getResourcePermissions();
} else {
this.resourcePermissions = new ArrayList<Class<?>>();
}
if (valuesProvider.getRoles() != null) {
this.roles = valuesProvider.getRoles();
} else {
this.roles = new ArrayList<String>();
}
}
// GETTERS e SETTERS
}
[/code]
-Interceptor com a lógica de controle
[code]/**
*
*/
package com.eAccessCon.interceptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.Intercepts;
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 com.eAccessCon.annotation.AllowAccessTo;
import com.eAccessCon.annotation.DenyAccessTo;
import com.eAccessCon.session.UserAccessDefinitions;
/**
- @author maiconfz
*/
@Intercepts
public class EAccessConInterceptor implements Interceptor {
private UserAccessDefinitions userAccessDefinitions;
private Result result;
/**
*
* @param userAccessDefinitions
* @param result
*/
public EAccessConInterceptor(UserAccessDefinitions userAccessDefinitions,
Result result) {
this.userAccessDefinitions = userAccessDefinitions;
this.result = result;
}
/*
* (non-Javadoc)
*
* @see
* br.com.caelum.vraptor.interceptor.Interceptor#accepts(br.com.caelum.vraptor
* .resource.ResourceMethod)
*/
@Override
public boolean accepts(ResourceMethod arg0) {
return true;
}
/*
* (non-Javadoc)
*
* @see
* br.com.caelum.vraptor.interceptor.Interceptor#intercept(br.com.caelum
* .vraptor.core.InterceptorStack,
* br.com.caelum.vraptor.resource.ResourceMethod, java.lang.Object)
*/
@Override
public void intercept(InterceptorStack arg0, ResourceMethod arg1,
Object arg2) throws InterceptionException {
Method requestedMethod = arg1.getMethod();
Class<?> requestedResource = arg1.getResource().getType();
List<Method> userMethodDenials = this.userAccessDefinitions
.getMethodDenials();
List<Method> userMethodPermissions = this.userAccessDefinitions
.getMethodPermissions();
List<Class<?>> userResourceDenials = this.userAccessDefinitions
.getResourceDenials();
List<Class<?>> userResourcePermissions = this.userAccessDefinitions
.getResourcePermissions();
List<String> userRoles = this.userAccessDefinitions.getRoles();
// By default, user is allowed to access
Boolean userAllowed = true;
if (!userMethodPermissions.isEmpty()
&& !userMethodPermissions.contains(requestedMethod)) {
userAllowed = false;
} else if (!userMethodDenials.isEmpty()
&& userMethodDenials.contains(requestedMethod)) {
userAllowed = false;
} else if (!userResourcePermissions.isEmpty()
&& !userResourcePermissions.contains(requestedResource)) {
userAllowed = false;
} else if (!userResourceDenials.isEmpty()
&& userResourceDenials.contains(requestedResource)) {
userAllowed = false;
} else if (requestedMethod.isAnnotationPresent(AllowAccessTo.class)
|| requestedMethod.isAnnotationPresent(DenyAccessTo.class)
|| requestedResource.isAnnotationPresent(AllowAccessTo.class)
|| requestedResource.isAnnotationPresent(DenyAccessTo.class)) {
String annotationRoles;
List<String> annotationRolesList;
Boolean methodOrClassIsAnnotatedWithAllowAccessTo;
Boolean roleMatch = false;
// Set the method or class roles to variable annotationRoles
if (requestedMethod.isAnnotationPresent(AllowAccessTo.class)) {
annotationRoles = requestedMethod.getAnnotation(
AllowAccessTo.class).value();
methodOrClassIsAnnotatedWithAllowAccessTo = true;
} else if (requestedMethod.isAnnotationPresent(DenyAccessTo.class)) {
annotationRoles = requestedMethod.getAnnotation(
DenyAccessTo.class).value();
methodOrClassIsAnnotatedWithAllowAccessTo = false;
} else if (requestedResource
.isAnnotationPresent(AllowAccessTo.class)) {
annotationRoles = requestedResource.getAnnotation(
AllowAccessTo.class).value();
methodOrClassIsAnnotatedWithAllowAccessTo = true;
} else {
// It only reachs here if Class is annotated with @DenyAccessTo
annotationRoles = requestedResource.getAnnotation(
DenyAccessTo.class).value();
methodOrClassIsAnnotatedWithAllowAccessTo = false;
}
// By default, the method or resource roles are merged
// ("role1, role2, role3"), so it removes white spaces and splits
// the roles
annotationRolesList = Arrays.asList(annotationRoles.replaceAll(" ",
"").split(","));
if (methodOrClassIsAnnotatedWithAllowAccessTo) {
for (String role : annotationRolesList) {
if (userRoles.contains(role)) {
roleMatch = true;
break;
}
}
if (!roleMatch) {
userAllowed = false;
}
} else {
// if method or class is annotated with DenyAccessTo
for (String role : annotationRolesList) {
if (userRoles.contains(role)) {
roleMatch = true;
break;
}
}
if (roleMatch) {
userAllowed = false;
}
}
} else {
// If class or method is not annotated with AllowAccessTo neither
// DenyAccessTo, then user is allowed to access
userAllowed = true;
}
if (userAllowed) {
arg0.next(arg1, arg2);
} else {
this.result.use(Results.http()).sendError(
HttpServletResponse.SC_FORBIDDEN);
}
}
}
[/code]
- Interface e Classe padrão para definir papeis e permissões no início da sessão do usuário(Essa parte está meio gambiarrada ainda, estou procurando soluções.
package com.eAccessCon.provider;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author maiconfz
*
*/
public interface EAccessConInitialUserAccessDefinitionProvider {
public List<String> getRoles();
public List<Method> getMethodDenials();
public List<Method> getMethodPermissions();
public List<Class<?>> getResourceDenials();
public List<Class<?>> getResourcePermissions();
}
[code]package com.eAccessCon.provider;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
- @author maiconfz
*/
public class DefaulEAccessInitialDefaultValueProvider implements
EAccessConInitialUserAccessDefinitionProvider {
/*
* (non-Javadoc)
*
* @see
* com.eAccessCon.provider.EAccessConInitialUserAccessDefinitionProvider
* #getRoles()
*/
@Override
public List<String> getRoles() {
return new ArrayList<String>();
}
/*
* (non-Javadoc)
*
* @see
* com.eAccessCon.provider.EAccessConInitialUserAccessDefinitionProvider
* #getMethodDenials()
*/
@Override
public List<Method> getMethodDenials() {
return new ArrayList<Method>();
}
/*
* (non-Javadoc)
*
* @see
* com.eAccessCon.provider.EAccessConInitialUserAccessDefinitionProvider
* #getMethodPermissions()
*/
@Override
public List<Method> getMethodPermissions() {
return new ArrayList<Method>();
}
/*
* (non-Javadoc)
*
* @see
* com.eAccessCon.provider.EAccessConInitialUserAccessDefinitionProvider
* #getResourceDenials()
*/
@Override
public List<Class<?>> getResourceDenials() {
return new ArrayList<Class<?>>();
}
/*
* (non-Javadoc)
*
* @see
* com.eAccessCon.provider.EAccessConInitialUserAccessDefinitionProvider
* #getResourcePermissions()
*/
@Override
public List<Class<?>> getResourcePermissions() {
return new ArrayList<Class<?>>();
}
}
[/code]
-Exemplo de Controller com as anotações (O Controller não faz parte do plugin, isso deve ser feito por quem vai usá-lo
/**
*
*/
package com.exemplo.vraptor.controller;
import br.com.caelum.vraptor.Get;
import br.com.caelum.vraptor.Post;
import br.com.caelum.vraptor.Resource;
import com.eAccessCon.annotation.AllowAccessTo;
import com.eAccessCon.session.UserAccessDefinitions;
/**
* @author maiconfz
*
*/
@Resource
public class UserController {
private UserAccessDefinitions userAccessDefinitions;
/**
* @param userAccessDefinitions
*/
public UserController(UserAccessDefinitions userAccessDefinitions) {w
this.userAccessDefinitions = userAccessDefinitions;
}
/**
*
*/
@Get
@AllowAccessTo("NotAuthenticatedUser")
public void login() {
// Lógica
}
/**
*
*/
@Post
@AllowAccessTo("NotAuthenticatedUser")
public void authenticate(
AuthenticationRequestInput authenticationRequestInput) {
// Lógica de autenticação
// É aqui que, normalmente, você vai definir o papel para o usuário.
// Chamando o método this.userAccessDefinitions.getRoles().add("AuthenticatedUser");
}
@Get
@AllowAccessTo("AuthenticatedUser")
public void home() {
// lógica
}
@Get
@AllowAccessTo("NotAuthenticatedUser")
public void signup() {
//Lógica
}
@Post
@AllowAccessTo("NotAuthenticatedUser")
public void register(
UserRegistrationRequestInput userRegistrationRequestInput) {
// Lógica
}
}
– Caso queira definir valores padrões para os papeis e permissões do usuário, o programador deve implementar a interface EAccessConInitialUserAccessDefinitionProvider, mencionada anteriormente. Então definir um customProvider para o VRaptor e registrar a sua implementação para a minha interface. (Eu sei, eu sei, ficou um pouco complicado. Estou procurando soluções para isso.)
@Component
@ApplicationScoped
public class MyEAccessConInitialUserAccessDefinitionProvider implements
EAccessConInitialUserAccessDefinitionProvider {
/*
* (non-Javadoc)
*
* @see com.eAccessCon.provider.
* EAccessConInitialUserAccessDefinitionProvider#getRoles()
*/
@Override
public List<String> getRoles() {
List<String> roles = new ArrayList<String>();
roles.add("NotAuthenticatedUser");
return roles;
}
/*
* (non-Javadoc)
*
* @see com.eAccessCon.provider.
* EAccessConInitialUserAccessDefinitionProvider#getMethodDenials()
*/
@Override
public List<Method> getMethodDenials() {
// TODO Auto-generated method stub
return null;
}
/*
* (non-Javadoc)
*
* @see com.eAccessCon.provider.
* EAccessConInitialUserAccessDefinitionProvider#getMethodPermissions()
*/
@Override
public List<Method> getMethodPermissions() {
// TODO Auto-generated method stub
return null;
}
/*
* (non-Javadoc)
*
* @see com.eAccessCon.provider.
* EAccessConInitialUserAccessDefinitionProvider#getResorceDenials()
*/
@Override
public List<Class<?>> getResourceDenials() {
// TODO Auto-generated method stub
return null;
}
/*
* (non-Javadoc)
*
* @see com.eAccessCon.provider.
* EAccessConInitialUserAccessDefinitionProvider#getResourcePermissions()
*/
@Override
public List<Class<?>> getResourcePermissions() {
// TODO Auto-generated method stub
return null;
}
}
public class CustomProvider extends SpringProvider {
/*
* (non-Javadoc)
*
* @see
* br.com.caelum.vraptor.ioc.spring.SpringProvider#registerCustomComponents
* (br.com.caelum.vraptor.ComponentRegistry)
*/
@Override
protected void registerCustomComponents(ComponentRegistry registry) {
registry.register(EAccessConInitialUserAccessDefinitionProvider.class,
MyEAccessConInitialUserAccessDefinitionProvider.class);
super.registerCustomComponents(registry);
}
}
Colocar no web.xml
<context-param>
<param-name>br.com.caelum.vraptor.provider</param-name>
<param-value>com.exemplo.vraptor.custom.CustomProvider</param-value>
</context-param>