Controle de acesso baseado em permissões com Apache Shiro
1. Introdução
Neste tutorial, veremos como implementarfine-grained Permissions-Based Access Control com a estrutura de segurança JavaApache Shiro.
2. Configuração
Usaremos a mesma configuração de nossa introdução ao Shiro - ou seja, adicionaremos apenas o móduloshiro-core às nossas dependências:
org.apache.shiro
shiro-core
1.4.1
Além disso, para fins de teste, usaremos um reino INI simples, colocando o seguinte arquivoshiro.ini na raiz do caminho de classe:
[users]
jane.admin = password, admin
john.editor = password2, editor
zoe.author = password3, author
[roles]
admin = *
editor = articles:*
author = articles:create, articles:edit
Em seguida, inicializaremos Shiro com o domínio acima:
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
SecurityManager securityManager = new DefaultSecurityManager(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
3. Funções e permissões
Geralmente, quando falamos de autenticação e autorização, nos concentramos nos conceitos de usuários e funções.
Em particular,roles are cross-cutting classes of users of an application or service. Portanto, todos os usuários que têm uma função específica terão acesso a alguns recursos e operações e podem ter acesso restrito a outras partes do aplicativo ou serviço.
O conjunto de funções geralmente é projetado com antecedência e raramente muda para acomodar novos requisitos de negócios. No entanto, as funções também podem ser definidas dinamicamente - por exemplo, por um administrador.
Com o Shiro, temos várias maneiras de testar se um usuário tem uma função específica. A maneira mais direta é usar o métodohasRole:
Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")) {
logger.info("Welcome Admin");
}
3.1. Permissões
No entanto, há um problema se verificarmos a autorização testando se o usuário tem uma função específica. Na verdade,we’re hardcoding the relationship between roles and permissions. Em outras palavras, quando queremos conceder ou revogar o acesso a um recurso, teremos que alterar o código-fonte. Obviamente, isso também significa reconstruir e reimplementar.
Podemos fazer melhor; é por isso que agora vamos apresentar o conceito de permissões. Permissions represent what the software can do que podemos autorizar ou negar enot who can do it. Por exemplo, "editar o perfil do usuário atual", "aprovar um documento" ou "criar um novo artigo".
Shiro faz muito poucas suposições sobre permissões. No caso mais simples, as permissões são cadeias simples:
Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:create")) {
//Create a new article
}
Observe quethe use of permissions is entirely optional in Shiro.
3.2. Associação de permissões aos usuários
Shiro tem um modelo flexível de associação de permissões a funções ou usuários individuais. No entanto, domínios típicos, incluindo o domínio INI simples que estamos usando neste tutorial, apenas associam permissões a funções.
Portanto, um usuário, identificado porPrincipal,, possui várias funções, e cada função possui váriasPermissions.
Por exemplo, podemos ver que em nosso arquivo INI, o usuáriozoe.author tem a funçãoauthor, e isso dá a ele as permissõesarticles:createearticles:edit:
[users]
zoe.author = password3, author
#Other users...
[roles]
author = articles:create, articles:edit
#Other roles...
Da mesma forma, outros tipos de região (como a região JDBC interna) podem ser configurados para associar permissões a funções.
4. Permissões de curinga
The default implementation of permissions in Shiro is wildcard permissions, uma representação flexível para uma variedade de esquemas de permissão.
Representamos permissões curinga no Shiro com strings. Uma cadeia de permissão é composta de um ou mais componentes separados por dois pontos, como:
articles:edit:1
O significado de cada parte da string depende da aplicação, pois Shiro não impõe nenhuma regra. No entanto, no exemplo acima, podemos interpretar claramente a string como uma hierarquia:
-
A classe de recursos que estamos expondo (artigos)
-
Uma ação sobre esse recurso (editar)
-
O ID de um recurso específico no qual queremos permitir ou negar a ação
Esta estrutura de três camadas de resource: action: id é um padrão comum em aplicativos Shiro, pois é simples e eficaz para representar muitos cenários diferentes.
Portanto, poderíamos revisitar nosso exemplo anterior para seguir este esquema:
Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:edit:123")) {
//Edit article with id 123
}
Observe quethe number of components in a wildcard permissions string does not have to be three, even though three components is the usual case.
4.1. Implicação de permissão e granularidade em nível de instância
As permissões curinga brilham quando as combinamos com outro recurso das permissões Shiro - implicação.
When we test for roles, we test for exact membership: aSubject tem uma função particular ou não. Em outras palavras, Shiro testa papéis para igualdade.
Por outro lado,when we test for permissions, we test for implication: as permissões deSubject implicam o que estamos testando?
O significado de implicação depende concretamente da implementação da permissão. De fato, para permissões curinga, a implicação é uma correspondência parcial de string, com a possibilidade de componentes curinga, como o nome sugere.
Então, digamos que atribuímos as seguintes permissões à funçãoauthor:
[roles]
author = articles:*
Então, todos com oauthor role terão permissão para todas as operações possíveis nos artigos:
Subject subject = SecurityUtils.getSubject();
if (subject.isPermitted("articles:create")) {
//Create a new article
}
Ou seja, a stringarticles:* corresponderá a qualquer permissão curinga cujo primeiro componente éarticles.
Com esse esquema, podemos atribuir permissões muito específicas - uma certa ação em um determinado recurso com um determinado ID - ou permissões amplas, como editar qualquer artigo ou executar qualquer operação em qualquer artigo.
Claro, por motivos de desempenho, uma vez que a implicação não é uma simples comparação de igualdade,we should always test against the most specific permission:
if (subject.isPermitted("articles:edit:1")) { //Better than "articles:*"
//Edit article
}
5. Implementações de permissões personalizadas
Vamos falar brevemente sobre as personalizações de permissões. Embora as permissões curinga abranjam uma ampla variedade de cenários, convém substituí-las por uma solução personalizada para nosso aplicativo.
Suponha que precisamos modelar permissões em caminhos para que a permissão em um caminhoimplies permissões em todos os subcaminhos. Na realidade, poderíamos usar permissões curinga perfeitamente para a tarefa, mas vamos ignorar isso.
Então, o que precisamos?
-
a implementação dePermission
-
para contar a Shiro
Vamos ver como alcançar os dois pontos.
5.1. Escrevendo uma implementação de permissão
Uma implementaçãoPermission é uma classe com um único método -implies:
public class PathPermission implements Permission {
private final Path path;
public PathPermission(Path path) {
this.path = path;
}
@Override
public boolean implies(Permission p) {
if(p instanceof PathPermission) {
return ((PathPermission) p).path.startsWith(path);
}
return false;
}
}
O método retornatrue sethis implica o outro objeto de permissão e retornafalse caso contrário.
5.2. Contando a Shiro sobre nossa implementação
Então, há várias maneiras de integrar uma simplificaçãoPermission em Shiro, mas a maneira mais direta éinject a custom PermissionResolver into our Realm:
IniRealm realm = new IniRealm();
Ini ini = Ini.fromResourcePath(Main.class.getResource("/com/.../shiro.ini").getPath());
realm.setIni(ini);
realm.setPermissionResolver(new PathPermissionResolver());
realm.init();
SecurityManager securityManager = new DefaultSecurityManager(realm);
OPermissionResolver é o responsável for converter a representação de string de nossas permissões em objetosPermission reais:
public class PathPermissionResolver implements PermissionResolver {
@Override
public Permission resolvePermission(String permissionString) {
return new PathPermission(Paths.get(permissionString));
}
}
Teremos que modificar nossoshiro.ini anterior com permissões baseadas em caminho:
[roles]
admin = /
editor = /articles
author = /articles/drafts
Então, poderemos verificar as permissões nos caminhos:
if(currentUser.isPermitted("/articles/drafts/new-article")) {
log.info("You can access articles");
}
Observe que aqui estamos configurando um domínio simples de forma programática. Em uma aplicação típica, usaremos um arquivoshiro.ini ou outros meios, como Spring para configurar Shiro e o reino. Um arquivoshiro.ini do mundo real pode conter:
[main]
permissionResolver = com.example.shiro.permissions.custom.PathPermissionResolver
dataSource = org.apache.shiro.jndi.JndiObjectFactory
dataSource.resourceName = java://app/jdbc/myDataSource
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource = $dataSource
jdbcRealm.permissionResolver = $permissionResolver
6. Conclusão
Neste artigo, revisamos como o Apache Shiro implementa o controle de acesso baseado em permissões.
Como sempre, as implementações de todos esses exemplos e trechos de código estão disponíveisover on GitHub.