Você está na página 1de 60

Tópicos Comece a aprender Pesquise mais de 50.

000 cursos, even… O que há de novo

8 Controle de acesso baseado em


identidade
Este capítulocapas

Organizando usuários em grupos


Simplificando permissões com controle de acesso baseado em função
Implementação de políticas mais complexas com controle de acesso baseado
em atributos
Centralizando o gerenciamento de políticas com um mecanismo de políticas

À medida que Natter cresceu, o número de listas de controle de acesso(ACL; capí-


tulo 3) também cresceu. As ACLs são simples, mas à medida que aumenta o nú-
mero de usuários e objetos que podem ser acessados ​por meio de uma API, o nú-
mero de entradas de ACL cresce junto com elas. Se você tiver um milhão de usuá-
rios e um milhão de objetos, no pior dos casos, poderá acabar com um bilhão de
entradas de ACL listando as permissões individuais de cada usuário para cada ob-
jeto. Embora essa abordagem possa funcionar com menos usuários, ela se torna
um problema maior à medida que a base de usuários cresce. Esse problema é par-
ticularmente ruim se as permissões forem gerenciadas centralmente por um ad-
ministrador do sistema (controle de acesso obrigatório ou MAC, conforme discu-
tido no capítulo 7), em vez de determinado por usuários individuais (controle de
acesso discricionário, ou DAC). Se as permissões não forem removidas quando
não forem mais necessárias, os usuários podem acabar acumulando privilégios,
violando o princípio do menor privilégio. Neste capítulo, você aprenderá sobre
formas alternativas de organizar permissões no modelo de controle de acesso ba-
seado em identidade. No capítulo 9, veremos modelos alternativos de controle de
acesso não baseados em identidade.

DEFINIÇÃO O controle de acesso baseado em identidade (IBAC) determina o


que você pode fazer com base em quem você é. O usuário que executa uma solici-
tação de API é primeiro autenticado e, em seguida, é realizada uma verificação
para ver se esse usuário está autorizado a executar a ação solicitada.

8.1 Usuários e grupos

UmUma das abordagens mais comuns para simplificar o gerenciamento de per-


missões é reunir usuários relacionados em grupos, conforme mostrado na figura
8.1. Em vez de o assunto de uma decisão de controle de acesso ser sempre um
usuário individual, os grupos permitem que as permissões sejam atribuídas a
conjuntos de usuários. Existe uma relação muitos-para-muitos entre usuários e
grupos: um grupo pode ter muitos membros e um usuário pode pertencer a mui-
tos grupos. Se a participação em um grupo for definida em termos de sujeitos
(que podem ser usuários ou outros grupos), também é possível que os grupos se-
jam membros de outros grupos, criando uma estrutura hierárquica. Por exemplo,
você pode definir um grupo para funcionários e outro para clientes. Se você adici-
onar um novo grupo para gerentes de projeto, poderá adicionar esse grupo ao
grupo de funcionários: todos os gerentes de projeto são funcionários.

Figura 8.1 Os grupos são adicionados como um novo tipo de assunto. As permis-
sões podem ser atribuídas a usuários individuais ou a grupos. Um usuário pode
ser membro de muitos grupos e cada grupo pode ter muitos membros.

A vantagem dos grupos é que agora você pode atribuir permissões a grupos e cer-
tificar-se de que todos os membros desse grupo tenham permissões consistentes.
Quando um novo engenheiro de software ingressa em sua organização, você pode
simplesmente adicioná-lo ao grupo “engenheiros de software” em vez de ter que
se lembrar de todas as permissões individuais necessárias para realizar seu traba-
lho. E quando eles mudam de emprego, basta removê-los desse grupo e adicioná-
los a um novo.

Grupos UNIX

Outra vantagem dos grupos é que eles podem ser usados ​para compactar as per-
missões associadas a um objeto em alguns casos. Por exemplo, o sistema de arqui-
vos UNIX armazena permissões para cada arquivo como um simples triplo de per-
missões para o usuário atual, o grupo do usuário e qualquer outra pessoa. Em vez
de armazenar permissões para muitos usuários individuais, o proprietário do ar-
quivo pode atribuir permissões a apenas um único grupo pré-existente, redu-
zindo drasticamente a quantidade de dados que devem ser armazenados para
cada arquivo. A desvantagem dessa compactação é que, se um grupo não existir
com os membros necessários, o proprietário poderá ter que conceder acesso a um
grupo maior do que gostaria.
A implementação de grupos simples é direta. Atualmente na API Natter que você
escreveu, há uma users tabelae uma permissions mesaque atua como uma
ACL que vincula usuários a permissões dentro de um espaço. Para adicionar gru-
pos, você pode primeiro adicionar uma nova tabela para indicar quais usuários
são membros de quais grupos:

CREATE TABLE group_members(


group_id VARCHAR(30) NÃO NULO,
user_id VARCHAR(30) NÃO NULL REFERENCES users(user_id));
CREATE INDEX group_member_user_idx ON group_members(user_id);

Quando o usuário é autenticado, você pode procurar os grupos dos quais o usuá-
rio é membro e adicioná-los como um atributo de solicitação adicional que pode
ser visualizado por outros processos. A Listagem 8.1 mostra como os grupos po-
dem ser pesquisados ​no authenticate() métododentro UserController de-
pois que o usuário for autenticado com sucesso.

Listagem 8.1 Procurando grupos durante a autenticação

if (hash.isPresent() && SCryptUtil.check(senha, hash.get())) {


request.attribute("assunto", nome de usuário);

var groups = database.findAll(String.class, ❶


"SELECT DISTINCT group_id FROM group_members " + ❶
"WHERE user_id = ?", username); ❶
request.attribute("grupos", grupos); ❷
}

❶ Pesquise todos os grupos aos quais o usuário pertence.

❷ Defina os grupos do usuário como um novo atributo na solicitação.

Você pode alterar a permissions tabelapara permitir que um ID de usuário ou


grupo seja usado (descartando a restrição de chave estrangeira para a tabela de
usuários):

Permissões CREATE TABLE(


space_id INT NOT NULL REFERENCES spaces(space_id),
user_or_group_id VARCHAR(30) NOT NULL, ❶
perms VARCHAR(3) NOT NULL);

❶ Permita um ID de usuário ou grupo.

ou você pode criar duas tabelas de permissão separadas e definir uma visualiza-
ção que realize uma união das duas:

CREATE TABLE user_permissions(...);


CREATE TABLE group_permissions(...);
CREATE VIEW permissions (space_id, user_or_group_id, perms) AS
SELECT space_id, user_id, perms FROM user_permissions
UNIÃO TODOS
SELECT space_id, group_id, perms FROM permissões de grupo;

Para determinar se um usuário tem as permissões apropriadas, você deve consul-


tar primeiro as permissões de usuários individuais e, em seguida, as permissões
associadas a quaisquer grupos dos quais o usuário seja membro. Isso pode ser
feito em uma única consulta, conforme a Listagem 8.2, que ajusta o requirePer-
mission métodoin UserController para levar os grupos em consideração cri-
ando uma consulta SQL dinâmica que verifica a tabela de permissões para o
nome de usuário do atributo subject da solicitação e quaisquer grupos dos quais o
usuário seja membro. Dalesbred tem suporte para construir consultas dinâmicas
com segurança em sua QueryBuilder classe, então você pode usar isso aqui
para simplificar.

DICA Ao criar consultas SQL dinâmicas, certifique-se de usar apenas espaços re-
servados e nunca inclua a entrada do usuário diretamente na consulta que está
sendo criada para evitar ataques de injeção de SQL, que são discutidos no capítulo
2. Alguns bancos de dados suportam tabelas temporárias, que permitem inserir
valores dinâmicos na tabela temporária e, em seguida, executar um SQL JOIN na
tabela temporária em sua consulta. Cada transação vê sua própria cópia da tabela
temporária, evitando a necessidade de gerar consultas dinâmicas.

Listagem 8.2 Levando os grupos em consideração ao pesquisar permissões


public Filter requirePermission(método String, permissão String) {
return (solicitação, resposta) -> {
if (!method.equals(request.requestMethod())) {
Retorna;
}

requireAuthentication(solicitação, resposta);

var spaceId = Long.parseLong(request.params(":spaceId"));


var nome de usuário = (String) request.attribute("assunto");
List<String> grupos = request.attribute("grupos"); ❶

var queryBuilder = new QueryBuilder( ❷


"SELECT perms FROM permissions " + ❷
"WHERE space_id = ? " + ❷
"AND (user_or_group_id = ?", spaceId, username); ❷
for (var group : groups) { ❸
queryBuilder.append( " OR user_or_group_id = ?", group); ❸
} ❸
queryBuilder.append()"); ❸

var perms = database.findAll(String.class,


queryBuilder.build());
if (perms.stream().noneMatch(p -> p.contains(permission))) { ❹
halt(403); ❹
}
};
}

❶ Pesquise os grupos dos quais o usuário é membro.

❷ Crie uma consulta dinâmica para verificar as permissões do usuário.

❸ Incluir quaisquer grupos na consulta.

❹ Falha se nenhuma das permissões para o usuário ou grupos permitir esta ação.

Você pode estar se perguntando por que dividiria a pesquisa dos grupos do usuá-
rio durante a autenticação para usá-los em uma segunda consulta na permissi-
ons tabeladurante o controle de acesso. Em vez disso, seria mais eficiente execu-
tar uma única consulta que verificasse automaticamente os grupos de um usuário
usando uma JOIN ou subconsulta na tabela de associação de grupo, como a
seguinte:

SELECT perms FROM permissions


WHERE espaço_id = ?
AND (user_or_group_id = ? ❶
OR user_or_group_id IN ❷
(SELECT DISTINCT group_id ❷
FROM group_members ❷
WHERE user_id = ?)) ❷
❶ Verifique as permissões para este usuário diretamente.

❷ Verifique as permissões dos grupos dos quais o usuário é membro.

Embora essa consulta seja mais eficiente, é improvável que a consulta extra do
design original se torne um gargalo de desempenho significativo. Mas combinar
as consultas em uma tem uma desvantagem significativa, pois viola as camadas
de autenticação e controle de acesso. Na medida do possível, você deve garantir
que todos os atributos de usuário necessários para decisões de controle de acesso
sejam coletados durante a etapa de autenticação e, em seguida, decidir se a solici-
tação é autorizada usando esses atributos. Como um exemplo concreto de como a
violação dessa camada pode causar problemas, considere o que aconteceria se
você mudasse sua API para usar um armazenamento de usuário externo, como
LDAP (discutido na próxima seção) ou um provedor de identidade OpenID Con-
nect (capítulo 7). Nesses casos,

8.1.1 Grupos LDAP

DentroEm muitas grandes organizações, incluindo a maioria das empresas, os


usuários são gerenciados centralmente em um LDAP (Lightweight Directory Ac-
cess Protocol) diretório. O LDAP foi projetado para armazenar informações do
usuário e possui suporte integrado para grupos. Você pode aprender mais sobre o
LDAP em https://ldap.com/basic-ldap-concepts/ . O padrão LDAP define as duas
formas de grupos a seguir:
1. Grupos estáticossão definidos usando o groupOfNames ou groupOfUniqueNa-
mes classes de objeto, 1 que listam explicitamente os membros do grupo
usando o member ou uniqueMember atributos. A diferença entre os dois é que
groupOfUniqueNames proíbe que o mesmo membro seja listado duas vezes.
2. Grupos dinâmicossão definidos usando o groupOfURLs classe de objeto, em
que a associação do grupo é fornecida por uma coleção de URLs LDAP que defi-
nem as consultas de pesquisa no diretório. Qualquer entrada que corresponda
a um dos URLs de pesquisa é membro do grupo.

Alguns servidores de diretório também suportam grupos estáticos virtuais, que se


parecem com grupos estáticos, mas consultam um grupo dinâmico para determi-
nar a associação. Os grupos dinâmicos podem ser úteis quando os grupos se tor-
nam muito grandes, pois evitam listar explicitamente todos os membros do grupo,
mas podem causar problemas de desempenho, pois o servidor precisa realizar
operações de pesquisa potencialmente caras para determinar os membros de um
grupo.

Para localizar de quais grupos estáticos um usuário é membro no LDAP, você deve
realizar uma pesquisa no diretório para todos os grupos que possuem o nome dis-
tinto desse usuáriocomo um valor de seu member atributo, conforme a Listagem
8.3. Primeiro, você precisa se conectar ao servidor LDAP usando oJava Naming
and Directory Interface (JNDI) ou outra biblioteca cliente LDAP. Os usuários nor-
mais do LDAP geralmente não têm permissão para executar pesquisas, portanto,
você deve usar um JNDI separado InitialDirContext para pesquisar os grupos
de um usuário, configurado para usar um usuário de conexão que tenha as per-
missões apropriadas. Para localizar os grupos em que um usuário está, você pode
usar o seguinte filtro de pesquisa, que localiza todas as groupOfNames entradas
LDAP que contêm o usuário fornecido como membro:

(&(objectClass=groupOfNames)(member=uid=test,dc=example,dc=org))

Para evitar vulnerabilidades de injeção de LDAP (explicadas no capítulo 2), você


pode usar os recursos do JNDI para permitir que os filtros de pesquisa tenham pa-
râmetros. O JNDI irá então certificar-se de que qualquer entrada do usuário nes-
ses parâmetros seja escapada corretamente antes de passá-la para o diretório
LDAP. Para usar isso, substitua a entrada do usuário no campo por um parâmetro
numerado (começando em 0) no formulário {0} ou {1} ou {2} , e assim por di-
ante, e então forneça uma Object matrizcom os argumentos reais para o sear-
ch método. Os nomes dos grupos podem ser encontrados procurando o atributo
CN (Nome Comum) nos resultados.

Listagem 8.3 Procurando grupos LDAP para um usuário

importar javax.naming.*;
importar javax.naming.directory.*;
importar java.util.*;

private List<String> lookupGroups(String username)


lança NamingException {
var props = new Propriedades();
props.put(Context.INITIAL_CONTEXT_FACTORY, ❶
"com.sun.jndi.ldap.LdapCtxFactory"); ❶
props.put(Context.PROVIDER_URL, ldapUrl); ❶
props.put(Context.SECURITY_AUTHENTICATION, "simples"); ❶
props.put(Context.SECURITY_PRINCIPAL, connUser); ❶
props.put(Context.SECURITY_CREDENTIALS, connPassword); ❶

var diretório = new InitialDirContext(props); ❶

var searchControls = new SearchControls();


searchControls.setSearchScope(
SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(
nova String[]{"cn"});

var grupos = new ArrayList<String>();


var resultados = diretório.search(
"ou=grupos,dc=exemplo,dc=com",
"(&(objectClass=groupOfNames)" + ❷
"(member=uid={0},ou=people,dc=example,dc=com))", ❷
new Object[]{ username }, ❸
searchControls);

while (resultados.temMais()) {
var resultado = resultados.próximo();
groups.add((String) result.getAttributes() ❹
.get("cn").get(0)); ❹
}

diretório.close();

grupos de retorno;
}

❶ Configure os detalhes da conexão para o servidor LDAP.

❷ Pesquise todos os grupos com o usuário como membro.

❸ Use parâmetros de consulta para evitar vulnerabilidades de injeção de LDAP.

❹ Extraia o atributo CN de cada grupo do qual o usuário é membro.

Para tornar mais eficiente a pesquisa dos grupos aos quais um usuário pertence,
muitos servidores de diretório oferecem suporte a um atributo virtual na própria
entrada do usuário que lista os grupos dos quais o usuário é membro. O servidor
de diretórios atualiza automaticamente esse atributo à medida que o usuário é
adicionado e removido de grupos (estáticos e dinâmicos). Como esse atributo não
é padrão, ele pode ter nomes diferentes, mas geralmente é chamado isMemberO-
f ou algo semelhante. Verifique a documentação do seu servidor LDAP para ver se
ele fornece esse atributo. Normalmente, é muito mais eficiente ler esse atributo
do que pesquisar os grupos dos quais um usuário faz parte.membrodo.

DICA Se você precisar pesquisar grupos regularmente, pode ser útil armazenar
os resultados em cache por um curto período para evitar pesquisas excessivas no
diretório.

questionário

1. Verdadeiro ou falso: em geral, os grupos podem conter outros grupos como


membros?
2. Quais três dos seguintes são tipos comuns de grupos LDAP?
1. Grupos estáticos
2. grupos abelianos
3. Grupos dinâmicos
4. Grupos estáticos virtuais
5. Grupos estáticos dinâmicos
6. Grupos dinâmicos virtuais
3. Dado o seguinte filtro LDAP:

(&(objectClass=#A)(member=uid=alice,dc=example,dc=com))

qual das seguintes classes de objeto seria inserida na posição marcada como #A
para procurar por grupos estáticos aos quais Alice pertence?
1. group
2. herdOfCats
3. groupOfURLs
4. groupOfNames
5. gameOfThrones
6. murderOfCrows
7. groupOfSubjects

As respostas estão no final do capítulo.

8.2 Controle de acesso baseado em função

Emboragrupos podem simplificar o gerenciamento de um grande número de


usuários, eles não resolvem totalmente as dificuldades de gerenciamento de per-
missões para uma API complexa. Primeiro, quase todas as implementações de
grupos ainda permitem que as permissões sejam atribuídas a usuários individu-
ais, bem como a grupos. Isso significa que, para descobrir quem tem acesso a quê,
você ainda precisa examinar as permissões de todos os usuários, bem como os
grupos aos quais eles pertencem. Em segundo lugar, como os grupos geralmente
são usados ​para organizar usuários para uma organização inteira (como em um
diretório LDAP central), às vezes eles não podem ser distinções muito úteis para
sua API. Por exemplo, o diretório LDAP pode ter apenas um grupo para todos os
engenheiros de software, mas sua API precisa distinguir entre engenheiros de
back-end e front-end, QA e scrum masters. Se você não pode alterar os grupos ge-
renciados centralmente, então você estará de volta ao gerenciamento de permis-
sões para usuários individuais. Por fim, mesmo quando os grupos são adequados
para uma API, pode haver um grande número de permissões refinadas atribuídas
a cada grupo, dificultando a revisão das permissões.

Para resolver esses inconvenientes,o controle de acesso baseado em função


(RBAC) introduz a noção de função como um intermediário entre usuários e per-
missões, conforme mostrado na figura 8.2. As permissões não são mais atribuídas
diretamente a usuários (ou grupos). Em vez disso, as permissões são atribuídas às
funções e, em seguida, as funções são atribuídas aos usuários. Isso pode simplifi-
car drasticamente o gerenciamento de permissões, porque é muito mais simples
atribuir a alguém a função de “moderador” do que lembrar exatamente quais
permissões um moderador deve ter. Se as permissões mudarem com o tempo,
você poderá simplesmente alterar as permissões associadas a uma função sem
precisar atualizar as permissões de muitos usuários e grupos individualmente.
Figura 8.2 No RBAC, as permissões são atribuídas a funções em vez de direta-
mente aos usuários. Os usuários são atribuídos a funções, dependendo do nível de
acesso necessário.

Em princípio, tudo o que você pode realizar com o RBAC pode ser realizado com
grupos, mas na prática existem várias diferenças em como eles são usados, in-
cluindo o seguinte:

Os grupos são usados ​principalmente para organizar os usuários, enquanto as


funções são usadas principalmente como uma forma de organizar as
permissões.
Conforme discutido na seção anterior, os grupos tendem a ser atribuídos cen-
tralmente, enquanto as funções tendem a ser específicas para um determinado
aplicativo ou API. Por exemplo, toda API pode ter uma admin função, mas o
conjunto de usuários que são administradores pode diferir de API para API.
Os sistemas baseados em grupo geralmente permitem que as permissões sejam
atribuídas a usuários individuais, mas os sistemas RBAC normalmente não per-
mitem isso. Essa restrição pode simplificar drasticamente o processo de revisão
de quem tem acesso a quê.
Os sistemas RBAC dividem a definição e atribuição de permissões para funções
da atribuição de usuários a essas funções. É muito menos propenso a erros atri-
buir um usuário a uma função do que descobrir quais permissões cada função
deve ter, portanto, essa é uma separação útil de tarefas que melhora a
segurança.
As funções podem ter um elemento dinâmico. Por exemplo, alguns ambientes
militares e outros têm o conceito de oficial de serviço, que tem privilégios e res-
ponsabilidades particulares apenas durante seu turno. Terminado o plantão,
passam a vez para o próximo oficial de plantão, que assume a função.

O RBAC é quase sempre utilizado como uma forma de controle de acesso obrigató-
rio, sendo as funções descritas e atribuídas por quem controla os sistemas que es-
tão sendo acessados. É muito menos comum permitir que os usuários atribuam
funções a outros usuários da maneira que podem com permissões em abordagens
de controle de acesso discricionário. Em vez disso, é comum colocar um meca-
nismo DAC como OAuth2 (capítulo 7) sobre um sistema RBAC subjacente para que
um usuário com uma função de moderador, por exemplo, possa delegar parte de
suas permissões a terceiros. Alguns sistemas RBAC fornecem aos usuários algum
poder sobre quais funções eles usam ao executar operações de API. Por exemplo,
o mesmo usuário pode enviar mensagens para uma sala de bate-papo como ele
mesmo ou usar sua função de Diretor Financeiro quando deseja postar uma de-
claração oficial. oO modelo RBAC padrão do NIST (Instituto Nacional de Padrões e
Tecnologia) ( http://mng.bz/v9eJ ) inclui uma noção de sessão, na qual um usuário
pode escolher quais de suas funções estão ativas em um determinado momento
ao fazer solicitações de API. Isso funciona de maneira semelhante aos tokens com
escopo no OAuth, permitindo que uma sessão ative apenas um subconjunto das
funções de um usuário, reduzindo o dano se a sessão for comprometida. Dessa
forma, o RBAC também suporta melhor o princípio do menor privilégio do que os
grupos porque um usuário pode agir com apenas um subconjunto de sua autori-
dade total.

8.2.1 Mapeamento de funções para permissões

Lásão duas abordagens básicas para mapear funções para permissões de nível in-
ferior dentro de sua API. A primeira é eliminar completamente as permissões e,
em vez disso, apenas anotar cada operação em sua API com a função ou funções
que podem chamar essa operação. Nesse caso, você substituiria o requirePer-
mission filtro existentecom um novo requireRole filtroque impôs requisitos de
função em vez disso. Esta é a abordagem adotada no Java Enterprise Edition (Java
EE) e no framework JAX-RS, onde os métodos podem ser anotados com a
@RolesAllowed anotaçãopara descrever quais funções podem chamar aquele
método por meio de uma API, conforme mostrado na Listagem 8.4.
Listagem 8.4 Anotando métodos com papéis em Java EE

importar javax.ws.rs.*;
importar javax.ws.rs.core.*;
importar javax.annotation.security.*; ❶

@DeclareRoles({"dono", "moderador", "membro"}) ❷


@Path("/espaços/{espaçoId}/membros")
public class SpaceMembersResource {

@PUBLICAR
@RolesAllowed("proprietário") ❸
public Response addMember() { .. }

@PEGUE
@RolesAllowed({"dono", "moderador"}) ❸
public Response listMembers() { .. }
}

❶ As anotações de função estão no pacote javax.annotation.security.

❷ Declare funções com a anotação @DeclareRoles.

❸ Descreva as restrições de função com a anotação @RolesAllowed.


A segunda abordagem é manter uma noção explícita de permissões de nível infe-
rior, como as usadas atualmente na API do Natter, e definir um mapeamento ex-
plícito de funções para permissões. Isso pode ser útil se você quiser permitir que
administradores ou outros usuários definam novas funções do zero e também fa-
cilita ver exatamente quais permissões uma função recebeu sem precisar exami-
nar o código-fonte da API. A Listagem 8.5 mostra o SQL necessário para definir
quatro novas funções com base nas permissões existentes da API do Natter:

O proprietário do espaço social tem permissões totais.


Um moderador pode ler postagens e excluir postagens ofensivas.
Um membro normal pode ler e escrever posts, mas não deletar nenhum.
Um observador só pode ler postagens e não escrever as suas próprias.

Abra src/main/resources/schema.sql em seu editor e adicione as linhas da lista-


gem 8.5 ao final do arquivo e clique em salvar. Você também pode excluir a per-
missions tabela existente(e GRANT declarações associadas) se vocêsdesejar.

Listagem 8.5 Permissões de função para a API Natter

CREATE TABLE role_permissions( ❶


role_id VARCHAR(30) NÃO NULL PRIMARY KEY, ❶
perms VARCHAR(3) NOT NULL ❶
);
INSERT INTO role_permissions(role_id, perms)
VALUES ('dono', 'rwd'), ❷
('moderador', 'rd'), ❷
('membro', 'rw'), ❷
('observador', 'r'); ❷
GRANT SELECT ON role_permissions TO natter_api_user; ❸

❶ Cada função concede um conjunto de permissões.

❷ Definir papéis para os espaços sociais do Natter.

❸ Como as funções são fixas, a API recebe acesso somente leitura.

8.2.2 Funções estáticas

Agoraque você definiu como as funções são mapeadas para as permissões, você só
precisa decidir como mapear os usuários para as funções. A abordagem mais co-
mum é definir estaticamente quais usuários (ou grupos) são atribuídos a quais
funções. Essa é a abordagem adotada pela maioria dos servidores de aplicativos
Java EE, que definem arquivos de configuração para listar os usuários e grupos
aos quais devem ser designadas funções diferentes. Você pode implementar o
mesmo tipo de abordagem na API do Natter adicionando uma nova tabela para
mapear usuários para funções em um espaço social. As funções na API do Natter
têm como escopo cada espaço social para que o proprietário de um espaço social
não possa fazer alterações em outro.
DEFINIÇÃO Quando usuários, grupos ou funções estão confinados a um sub-
conjunto de seu aplicativo, isso é conhecido como domínio de segurançaou reino.

A Listagem 8.6 mostra o SQL para criar uma nova tabela para mapear um usuário
em um espaço social para uma função. Abra schema.sql novamente e adicione a
nova definição de tabela ao arquivo. a user_roles mesa, juntamente com a
role_permissions tabela, ocupa o lugar da velha permissions mesa. Na API
do Natter, você restringirá um usuário a ter apenas uma função em um espaço,
para poder adicionar uma restrição de chave primária
no space_id e user_id Campos. Se você quiser permitir mais de uma função,
pode deixar isso de fora e adicionar manualmente um índice nesses campos. Não
se esqueça de conceder permissões ao usuário do banco de dados da API Natter.

Listagem 8.6 Mapeando funções estáticas

CREATE TABLE user_roles( ❶


space_id INT NOT NULL REFERENCES spaces(space_id), ❶
user_id VARCHAR(30) NOT NULL REFERENCES users(user_id), ❶
role_id VARCHAR(30) NOT NULL REFERENCES role_permissions(role_id), ❶
PRIMARY KEY (space_id, user_id ) ❷
);
GRANT SELECT, INSERT, DELETE ON user_roles TO natter_api_user; ❸

❶ Natter restringe cada usuário a ter apenas uma função.


❷ Mapear usuários para funções dentro de um espaço.

❸ Conceda permissões ao usuário do banco de dados Natter.

Para conceder funções aos usuários, você precisa atualizar os dois locais onde as
permissões são concedidas atualmente dentro da SpaceController classe:

no createSpace método, o proprietário do novo espaço recebe permissões to-


tais. Isso deve ser atualizado para conceder a owner função.
no addMember método, a solicitação contém as permissões para o novo mem-
bro. Isso deve ser alterado para aceitar uma função para o novo membro.

A primeira tarefa é realizada abrindo o arquivo SpaceController.java e encon-


trando a linha dentro do createSpace métodoonde está a inserção na instrução
da tabela de permissões. Remova essas linhas e substitua-as pelo seguinte para in-
serir uma nova atribuição de função:

database.updateUnique(
"INSERT INTO user_roles(space_id, user_id, role_id)" +
"VALUES(?, ?, ?)", spaceId, proprietário, "proprietário");

A atualização addMember envolve um pouco mais de código, porque você deve


garantir a validação da nova função. Adicione a seguinte linha ao topo da classe
para definir as funções válidas:
private static final Set<String> DEFINED_ROLES =
Set.of("dono", "moderador", "membro", "observador");

Agora você pode atualizar a implementação do addMember métodopara ser base-


ado em função em vez de baseado em permissão, conforme mostrado na Lista-
gem 8.7. Primeiro, extraia a função desejada da solicitação e certifique-se de que
seja um nome de função válido. Você pode usar como padrão a member funçãose
nenhum for especificado, pois esta é a função normal para a maioria dos mem-
bros. É então simplesmente um caso de inserir o papel na user_roles tabelaem
vez da velha permissions mesae retornando a função atribuída noresposta.

Listagem 8.7 Adicionando novos membros com papéis

public JSONObject addMember(Solicitação de solicitação, Resposta de resposta)


var json = new JSONObject(request.body());
var spaceId = Long.parseLong(request.params(":spaceId"));
var userToAdd = json.getString("username");
var função = json.optString("função", "membro"); ❶

if (!DEFINED_ROLES.contains(role)) { ❶
throw new IllegalArgumentException("role inválido"); ❶
}

database.updateUnique(
"INSERT INTO user_roles(space_id, user_id, role_id)" + ❷
" VALUES(?, ?, ?)", spaceId, userToAdd, função); ❷

resposta.status(200);
retornar novo JSONObject()
.put("nome de usuário", userToAdd)
.put("função", função); ❸
}

❶ Extraia a função da entrada e valide-a.

❷ Insira a nova atribuição de função para este espaço.

❸ Retorne a função na resposta.

8.2.3 Determinando as funções do usuário

oA etapa final do quebra-cabeça é determinar quais funções um usuário tem


quando faz uma solicitação à API e as permissões que cada função concede. Isso
pode ser encontrado procurando o usuário na user_roles tabelapara descobrir
sua função para um determinado espaço e, em seguida, procurar as permissões
atribuídas a essa função na role_permissions tabela. Em contraste com a situa-
ção com grupos na seção 8.1, as funções geralmente são específicas para uma API,
portanto, é menos provável que você seja informado sobre as funções de um
usuário como parte da autenticação. Por esse motivo, você pode combinar a pes-
quisa de funções e o mapeamento de funções em permissões em uma única con-
sulta de banco de dados, unindo as duas tabelas, conforme a seguir:

SELECT rp.perms
DE role_permissions rp
JOIN user_roles ur
ON ur.role_id = rp.role_id
WHERE ur.space_id = ? E ur.user_id = ?

Pesquisar funções e permissões no banco de dados pode ser caro, mas a imple-
mentação atual repetirá esse trabalho toda vez que o requirePermission fil-
troé chamado, o que pode ocorrer várias vezes durante o processamento de uma
solicitação. Para evitar esse problema e simplificar a lógica, você pode extrair a
pesquisa de permissão em um filtro separado que é executado antes de qualquer
verificação de permissão e armazena as permissões em um atributo de solicita-
ção. A Listagem 8.8 mostra o novo lookupPermissions filtroque executa o ma-
peamento de usuário para função para permissões e, em seguida, requirePer-
mission método atualizado. Ao reutilizar as verificações de permissões existen-
tes, você pode adicionar RBAC sem precisar alterar as regras de controle de
acesso. Abra UserController.java em seu editor e atualize o requirePermissi-
on método para corresponder à listagem.

Listagem 8.8 Determinando permissões com base em papéis


public void lookupPermissions(Solicitação de solicitação, Resposta de resposta
requireAuthentication(solicitação, resposta);
var spaceId = Long.parseLong(request.params(":spaceId"));
var nome de usuário = (String) request.attribute("assunto");

var perms = database.findOptional(String.class, ❶


"SELECT rp.perms " + ❶
" FROM role_permissions rp JOIN user_roles ur" + ❶
" ON rp.role_id = ur.role_id" + ❶
" WHERE ur.space_id = ? AND ur.user_id = ?", ❶
spaceId, nome de usuário).orElse(""); ❶
request.attribute("perms", perms); ❷
}

public Filter requirePermission(método String, permissão String) {


return (solicitação, resposta) -> {
if (!method.equals(request.requestMethod())) {
Retorna;
}

var perms = request.<String>attribute("perms"); ❸


if (!perms.contains(permissão)) {
parada(403);
}
};
}
❶ Determine as permissões do usuário mapeando o usuário para a função de
permissões.

❷ Armazenar permissões em um atributo de solicitação.

❸ Recupere as permissões da solicitação antes de verificar.

Agora você precisa adicionar chamadas ao novo filtro para garantir que as per-
missões sejam consultadas. Abra o arquivo Main.java e adicione as seguintes li-
nhas ao main método, antes da definição da postMessage operação:

before("/espaços/:espaçoId/mensagens",
userController::lookupPermissions);
before("/espaços/:espaçoId/mensagens/*",
userController::lookupPermissions);
before("/espaços/:espaçoId/membros",
userController::lookupPermissions);

Se você reiniciar o servidor API, agora poderá adicionar usuários, criar espaços e
adicionar membros usando a nova abordagem RBAC. Todas as verificações de
permissão existentes nas operações da API ainda são aplicadas, só que agora elas
são gerenciadas usando funções em vez de permissão explícitaatribuições.
8.2.4 Funções dinâmicas

No entantoatribuições de funções estáticas são as mais comuns, alguns sistemas


RBAC permitem consultas mais dinâmicas para determinar quais funções um
usuário deve ter. Por exemplo, um funcionário do call center pode receber uma
função que permite acesso aos registros do cliente para que ele possa responder
às consultas de suporte ao cliente. Para reduzir o risco de uso indevido, o sistema
pode ser configurado para conceder ao funcionário essa função apenas durante o
horário de trabalho contratado, talvez com base nos horários de turno. Fora des-
ses horários, o usuário não receberia a função e, portanto, teria o acesso negado
aos registros do cliente se tentasse acessá-los.

Embora as atribuições de funções dinâmicas tenham sido implementadas em vá-


rios sistemas, não há um padrão claro de como criar funções dinâmicas. As abor-
dagens geralmente são baseadas em consultas de banco de dados ou talvez basea-
das em regras especificadas em uma forma lógica, como Prolog ou Web Ontology
Language(CORUJA). Quando regras de controle de acesso mais flexíveis são neces-
sárias, o controle de acesso baseado em atributo (ABAC) substituiu amplamente o
RBAC, conforme discutido na seção 8.3. O NIST tentou integrar ABAC com RBAC
para obter o melhor dos dois mundos ( http://mng.bz/4BMa ), mas essa abordagem
não é amplamente adotada.

Outros sistemas RBAC implementam restrições, como tornar duas funções mutua-
mente exclusivas; um usuário não pode ter as duas funções ao mesmo tempo. Isso
pode ser útil para impor a separação de tarefas, como impedir que um adminis-
trador de sistema também gerencie logs de auditoria para umconfidencialsistema.

questionário

4. Quais das opções a seguir são mais prováveis ​de se aplicar a papéis do que a
grupos?
1. Os papéis geralmente são maiores que os grupos.
2. As funções geralmente são menores que os grupos.
3. Todas as permissões são atribuídas usando funções.
4. As funções suportam melhor a separação de funções.
5. As funções são mais prováveis ​de serem específicas do aplicativo.
6. As funções permitem que as permissões sejam atribuídas a usuários
individuais.
5. Para que é usada uma sessão no modelo NIST RBAC? Escolha uma resposta.
1. Para permitir que os usuários compartilhem funções.
2. Para permitir que um usuário deixe seu computador desbloqueado.
3. Para permitir que um usuário ative apenas um subconjunto de suas funções.
4. Para lembrar o nome do usuário e outros atributos de identidade.
5. Para permitir que um usuário acompanhe por quanto tempo eles
trabalharam.
6. Dada a seguinte definição de método
@<anotação aqui>
public Response adminOnlyMethod(String arg);

qual valor de anotação pode ser usado no sistema de funções Java EE e JAX-RS
para restringir o método a ser chamado apenas por usuários com a função
ADMIN?
1. @DenyAll
2. @PermitAll
3. @RunAs("ADMIN")
4. @RolesAllowed("ADMIN")
5. @DeclareRoles("ADMIN")

As respostas estão no final do capítulo.

8.3 Controle de acesso baseado em atributos

EmboraO RBAC é um modelo de controle de acesso muito bem-sucedido que foi


amplamente implantado; em muitos casos, as políticas de controle de acesso dese-
jadas não podem ser expressas por meio de atribuições de função simples. Consi-
dere o exemplo do agente de call center da seção 8.2.4. Além de impedir que o
agente acesse os registros do cliente fora do horário de trabalho contratado, você
também pode impedir que eles acessem esses registros se não estiverem em uma
chamada com esse cliente. Permitir que cada agente acesse todos os registros do
cliente durante o horário de trabalho ainda é mais autoridade do que eles real-
mente precisam para realizar seu trabalho, violando o princípio do menor privilé-
gio. Pode ser que você possa determinar com qual cliente o agente de chamadas
está falando a partir de seu número de telefone (identificador de chamadas), ou
talvez o cliente digite um número de conta usando o teclado antes de se conectar
a um agente. Você gostaria de permitir que o agente acesse apenas o arquivo
desse cliente durante a chamada, talvez permitindo cinco minutos depois para
que ele termine de escrever as anotações.

Para lidar com esses tipos de decisões de controle de acesso dinâmico, uma alter-
nativa ao RBAC foi desenvolvida, conhecida como ABAC: controle de acesso base-
ado em atributos. No ABAC, as decisões de controle de acesso são feitas dinamica-
mente para cada solicitação de API usando coleções de atributos agrupados em
quatro categorias:

Atributos sobre o assunto; ou seja, o usuário que faz a solicitação. Isso pode in-
cluir seu nome de usuário, quaisquer grupos aos quais eles pertençam, como
foram autenticados, quando foram autenticados pela última vez e assim por
diante.
Atributos sobre o recursoou objeto sendo acessado, como o URI do recurso ou
um rótulo de segurança (TOP SECRET, por exemplo).
Atributos sobre a ação que o usuário está tentando executar, como o método
HTTP.
Atributos sobre o ambiente ou contexto em que a operação está ocorrendo. Isso
pode incluir a hora local do dia ou a localização do usuário que executa a ação.
A saída de ABAC é então uma decisão de permitir ou negar, conforme mostrado
na figura 8.3.

Figura 8.3 Em um sistema ABAC, as decisões de controle de acesso são tomadas di-
namicamente com base em atributos que descrevem o assunto, recurso, ação e
ambiente ou contexto da solicitação de API.

A Listagem 8.9 mostra um exemplo de código para coletar valores de atributo


para alimentar um processo de decisão ABAC na API Natter. O código implementa
um filtro Spark que pode ser incluído antes de qualquer definição de rota de API
no lugar do existente requirePermission filtros. A implementação real da veri-
ficação de permissão ABAC é deixada abstrata por enquanto; você desenvolverá
implementações nas próximas seções. O código coleta atributos nas quatro cate-
gorias de atributos descritas acima, examinando o Request objeto Spark e ex-
traindo o nome de usuário e quaisquer grupos preenchidos durante a autentica-
ção. Você pode incluir outros atributos, como a hora atual, nas propriedades do
ambiente. A extração desse tipo de atributo ambiental facilita o teste das regras
de controle de acesso porque você pode facilmente passar em diferentes horários
do dia em seus testes. Se você estiver usando JWTs (capítulo 6), convém incluir de-
clarações do JWT Claims Set nos atributos do assunto, como o emissor ou o horá-
rio emitido. Em vez de usar um boolean valor simplespara indicar a decisão,
você deve usar uma Decision classe personalizada. Isso é usado para combinar
decisões de diferentes regras de política, como você verá na seção 8.3.1.

Listagem 8.9 Reunindo valores de atributo

pacote com.manning.apisecurityinaction.controller;

importar java.time.LocalTime;
importar java.util.Map;
importar faísca.*;
importar spark.Spark.halt estático;
classe abstrata pública ABACAccessController {
public void applyPolicy(Solicitação de solicitação, Resposta de resposta)

var subjectAttrs = new HashMap<String, Object>(); ❶


subjectAttrs.put("usuário", request.attribute("assunto")); ❶
subjectAttrs.put("grupos", request.attribute("grupos")); ❶

var resourceAttrs = new HashMap<String, Object>(); ❶


resourceAttrs.put("caminho", request.pathInfo()); ❶
resourceAttrs.put("espaço", request.params(":espaçoId")); ❶

var actionAttrs = new HashMap<String, Object>(); ❶


actionAttrs.put("método", request.requestMethod()); ❶

var envAttrs = new HashMap<String, Object>(); ❶


envAttrs.put("timeOfDay", LocalTime.now()); ❶
envAttrs.put("ip", request.ip()); ❶

var decisão = checkPermitted(subjectAttrs, resourceAttrs, ❷


actionAttrs, envAttrs); ❷

if (!decision.isPermitted()) { ❸
halt(403); ❸
}
}

verificação de decisão abstrataPermitido(


Map<String, Object> assunto,
Recurso Map<String, Object>,
Ação Map<String, Object>,
Map<String, Object> env);

public static class Decisão { ❹


}
}
❶ Reúna atributos relevantes e agrupe-os em categorias.

❷ Verifique se a solicitação é permitida.

❸ Caso contrário, pare com um erro 403 Forbidden.

❹ A classe Decisão será descrita a seguir.

8.3.1 Combinando decisões

Quandoimplementando o ABAC, normalmente as decisões de controle de acesso


são estruturadas como um conjunto de regras independentes que descrevem se
uma solicitação deve ser permitida ou negada. Se mais de uma regra correspon-
der a uma solicitação e elas tiverem resultados diferentes, a questão é qual delas
deve ser preferida. Isso se resume às duas perguntas a seguir:

Qual deve ser a decisão padrão se nenhuma regra de controle de acesso corres-
ponder à solicitação?
Como as decisões conflitantes devem ser resolvidas?

A opção mais segura é negar solicitações por padrão, a menos que seja explicita-
mente permitido por alguma regra de acesso, e dar prioridade às decisões de ne-
gação sobre as decisões de permissão. Isso requer pelo menos uma regra para cor-
responder e decidir permitir a ação e nenhuma regra para decidir negar a ação
para que a solicitação seja permitida. Ao adicionar ABAC em cima de um sistema
de controle de acesso existente para impor restrições adicionais que não podem
ser expressas no sistema existente, pode ser mais simples optar por uma estraté-
gia de permissão padrãoonde as solicitações podem prosseguir se nenhuma regra
ABAC corresponder. Essa é a abordagem que você adotará com a API do Natter,
adicionando regras ABAC adicionais que negam algumas solicitações e permitem
a passagem de todas as outras. Nesse caso, as outras solicitações ainda podem ser
rejeitadas pelas permissões RBAC existentes impostas anteriormente no capítulo.

A lógica para implementar essa permissão padrão com estratégia de substituição


de negação é mostrada na classe Decisão na Listagem 8.10. A permit variável é
inicialmente definida como true , mas qualquer chamada ao deny() método irá
defini-la como falsa. Chamadas para o permit() métodosão ignorados porque
este é o padrão, a menos que outra regra tenha chamado deny () já, caso em que
a negação deve ter precedência. Abra ABACAccessController.java em seu editor e
adicione a Decision classecomo um interiorclasse.

Listagem 8.10 Implementando decisão combinando

public static class Decisão {


permissão booleana privada = true; ❶
public void deny() { ❷
permitir = false; ❷
}
permissão pública nula() { ❸
}

booleano éPermitido() {
permissão de retorno;
}
}

❶ Padrão para permitir

❷ Uma decisão de negação explícita substitui o padrão.

❸ Decisões de permissão explícitas são ignoradas.

8.3.2 Implementação das decisões ABAC

Emboravocê pode implementar as decisões de controle de acesso ABAC direta-


mente em Java ou outra linguagem de programação, geralmente é mais claro se a
política for expressa na forma de regras ou linguagem específica de domínio(DSL)
projetado explicitamente para expressar decisões de controle de acesso. Nesta se-
ção, você implementará um mecanismo de decisão ABAC simples usando o meca-
nismo de regras de negócios Drools ( https://drools.org ) da Red Hat. O Drools pode
ser usado para escrever todos os tipos de regras de negócios e fornece uma sin-
taxe conveniente para criar regras de controle de acesso.
DICA O Drools faz parte de um conjunto maior de ferramentas comercializadas
sob o banner “Knowledge is Everything”, então muitas classes e pacotes usados ​no
Drools incluem a kie abreviação em seus nomes.

Para adicionar o mecanismo de regras do Drools ao projeto Natter API, abra o ar-
quivo pom.xml em seu editor e adicione as seguintes dependências à
<dependencies> seção:

<dependência>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>7.26.0.Final</version>
</dependência>
<dependência>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.26.0.Final</version>
</dependência>
<dependência>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.26.0.Final</version>
</dependência>
Ao iniciar, o Drools procurará um arquivo chamado kmodule.xml no classpath
que define a configuração. Você pode usar a configuração padrão, então navegue
até a pasta src/main/resources e crie uma nova pasta chamada META-INF em re-
sources. Em seguida, crie um novo arquivo chamado kmodule.xml dentro da
pasta src/main/resource/META-INF com o seguinte conteúdo:

<?xml version="1.0" encoding="UTF-8" ?>


<kmodule xmlns="http://www.drools.org/xsd/kmodule">
</kmodule>

Agora você pode implementar uma versão da ABACAccessController classeque


avalia decisões usando Drools. A Listagem 8.11 mostra o código que implementa o
checkPermitted métodocarregando regras do caminho de classe
usando KieServices.get().getKieClasspathContainer () .

Para consultar as regras para uma decisão, você deve primeiro criar uma nova
sessão KIE e definir uma instância da classe Decision da seção anterior como uma
variável global que as regras podem acessar. Cada regra pode então chamar
o deny () ou permit() métodosneste objeto para indicar se a solicitação deve
ser permitida. Os atributos podem então ser adicionados à memória de trabalho
do Drools usando o insert() métodona sessão. Como o Drools prefere valores
fortemente tipados, você pode agrupar cada conjunto de atributos em uma classe
wrapper simples para distingui-los uns dos outros (descritos brevemente). Final-
mente, ligue session.fireAllRules () para avaliar as regras em relação aos
atributos e, em seguida, verificar o valor da variável de decisão para determinar a
decisão final. Crie um novo arquivo chamado DroolsAccessController.java dentro
da pasta do controlador e adicione o conteúdo da listagem 8.11.

Listagem 8.11 Avaliando decisões com Drools

pacote com.manning.apisecurityinaction.controller;
importar java.util.*;
importar org.kie.api.KieServices;
importar org.kie.api.runtime.KieContainer;

public class DroolsAccessController extends ABACAccessController {

final privado KieContainer kieContainer;

public DroolsAccessController() {
this.kieContainer = KieServices.get().getKieClasspathContainer(); ❶
}

@Sobrepor
boolean checkPermitted(Map<String, Object> subject,
Recurso Map<String, Object>,
Ação Map<String, Object>,
Map<String, Object> env) {

var sessão = kieContainer.newKieSession(); ❷


tentar {
var decisão = new Decisão(); ❸
session.setGlobal("decisão", decisão); ❸

sessão.insert(new Assunto(assunto)); ❹
sessão.insert(novo Recurso(recurso)); ❹
sessão.insert(nova Ação(ação)); ❹
session.insert(new Environment(env)); ❹

sessão.fireAllRules(); ❺
return decision.isPermitted(); ❺
} finalmente {
sessão.dispose(); ❻
}
}
}

❶ Carregue todas as regras encontradas no classpath.

❷ Inicie uma nova sessão do Drools.

❸ Crie um objeto Decisão e defina-o como uma variável global chamada “decisão”.

❹ Insira fatos para cada categoria de atributos.


❺ Execute o mecanismo de regras para ver quais regras correspondem à solicitação
e verifique a decisão.

❻ Descarte a sessão quando terminar.

Como mencionado, o Drools gosta de trabalhar com valores fortemente tipados,


então você pode agrupar cada coleção de atributos em uma classe distinta para
simplificar a escrita de regras que correspondam a cada um, conforme mostrado
na Listagem 8.12. Abra DroolsAccessController.java em seu editor novamente e
adicione as quatro classes wrapper da listagem a seguir como classes internas à
DroolsAccessController classe.

Listagem 8.12 Envolvendo atributos em tipos

public static class Subject extends HashMap<String, Object> { ❶


Subject(Map<String, Object> m) { super(m); } ❶
} ❶

public static class Resource extends HashMap<String, Object> { ❷


Resource(Map<String, Object> m) { super(m); } ❷
} ❷

classe estática pública Action extends HashMap<String, Object> {


Ação(Mapa<String, Objeto> m) { super(m); }
}
ambiente de classe estática pública estende HashMap<String, Object> {
Environment(Map<String, Object> m) { super(m); }
}

❶ Wrapper para atributos relacionados ao assunto

❷ Wrapper para atributos relacionados a recursos

Agora você pode começar a escrever regras de controle de acesso. Em vez de


reimplementar todas as verificações de controle de acesso RBAC existentes, você
apenas adicionará uma regra adicional que impede que os moderadores excluam
mensagens fora do horário comercial normal. Crie um novo arquivo
accessrules.drl na pasta src/main/resources para conter as regras. A Listagem 8.13
lista a regra de exemplo. Quanto ao Java, um arquivo de regras do Drools pode
conter um package e import instruções, portanto, use-as para importar as Deci-
sion e a classe wrapper que você acabou de criar. Em seguida, você precisa decla-
rar a decision variável global que será utilizada para comunicar a decisão pelas
regras. Finalmente, você pode implementar as próprias regras. Cada regra tem a
seguinte forma:

regra "descrição"
quando
condições
então
ações
terminam

A descrição pode ser qualquer string útil para descrever a regra. As condições da
regra correspondem às classes que foram inseridas na memória de trabalho e
consistem no nome da classe seguido por uma lista de restrições entre parênteses.
Nesse caso, como as classes são mapas, você pode usar a this["key"] sintaxe
para corresponder aos atributos dentro do mapa. Para esta regra, você deve veri-
ficar se o método HTTP é DELETE e se o campo hora do timeOfDay atributoestá
fora das horas de trabalho permitidas das 9 às 5. Se a regra corresponder, a ação
da regra chamará o deny() métododa decision variável global. Você pode en-
contrar informações mais detalhadas sobre como escrever as regras do Drools no
site https://drools.org ou no livro Mastering JBoss Drools 6, de Mauricio Salatino,
Mariano De Maio e Esteban Aliverti (Packt, 2016).

Listagem 8.13 Um exemplo de regra ABAC

pacote com.manning.apisecurityinaction.rules; ❶

import com.manning.apisecurityinaction.controller.
➥ DroolsAccessController.*; ❶
import com.manning.apisecurityinaction.controller.
➥ ABACAccessController.Decision; ❶
decisão de decisão global; ❷

regra "negar moderação fora do horário comercial" ❸


quando ❸
Action( this["method"] == "DELETE" ) ❹
Environment( this["timeOfDay"].hour < 9 ❹
|| this["timeOfDay"].hour > 17 ) ❹
então ❸
decision.deny(); ❺
fim

❶ Adicionar pacotes e instruções de importação como Java.

❷ Declare a variável global de decisão.

❸ Uma regra tem uma descrição, uma seção quando com padrões e uma seção então
com ações.

❹ Os padrões correspondem aos atributos.

❺ A ação pode chamar os métodos de permissão ou negação na decisão.

Agora que você escreveu uma regra ABAC, pode conectar o método principal para
aplicar suas regras como um Spark before () filtro que é executado antes das
outras regras de controle de acesso. O filtro chamará o enforcePolicy méto-
doherdado do ABACAccessController (listagem 8.9), que preenche os atributos
das requisições. A classe base então chama o checkDecision métododa listagem
8.11, que usará o Drools para avaliar as regras. Abra Main.java em seu editor e
adicione as seguintes linhas ao main() métodologo antes das definições de rota
nesse arquivo:

var droolsController = new DroolsAccessController();


before("/*", droolsController::enforcePolicy);

Reinicie o servidor API e faça algumas solicitações de amostra para ver se a polí-
tica está sendo aplicada e não está interferindo nas verificações de permissão
RBAC existentes. Para verificar se as solicitações DELETE estão sendo rejeitadas
fora do horário comercial, você pode ajustar o relógio do seu computador para
um horário diferente ou ajustar o atributo de ambiente de hora do dia para defi-
nir artificialmente a hora do dia como 23h Abra ABACAccessController .java e al-
terar a definição do timeOfDay atributoComosegue:

envAttrs.put("timeOfDay", LocalTime.now().withHour(23));

Se você tentar fazer qualquer solicitação DELETE para a API, ela será rejeitada:

$ curl -i -X ​
DELETE \
-u demo:senha https://localhost:4567/spaces/1/messages/1
HTTP/1.1 403 Proibido
...

DICA Não importa se você não implementou nenhum método DELETEna API do
Natter, porque as regras ABAC serão aplicadas antes que a solicitação corres-
ponda a qualquer ponto de extremidade (mesmo que não exista nenhum). A im-
plementação da API Natter no repositório GitHub que acompanha este livro tem
implementações de várias solicitações REST adicionais, incluindo suporte a DE-
LETE, se você quiser experimentá-lo.

8.3.3 Agentes de política e gateways de API

ABACa aplicação pode ser complexa à medida que as políticas aumentam em


complexidade. Embora os mecanismos de regras de uso geral, como o Drools, pos-
sam simplificar o processo de criação de regras ABAC, foram desenvolvidos com-
ponentes especializados que implementam a aplicação de políticas sofisticadas.
Esses componentes são normalmente implementados como um agente de política
que se conecta a um servidor de aplicativos, servidor web ou proxy reverso exis-
tente, ou então como gateways autônomos que interceptam solicitações na ca-
mada HTTP, conforme ilustrado na figura 8.4.
Figura 8.4 Um agente de política pode se conectar a um servidor de aplicativos ou
proxy reverso para impor políticas ABAC. Alguns gateways de API também podem
impor decisões de política como componentes autônomos.

Por exemplo, o Open Policy Agent(OPA, https://www.openpolicyagent.org ) imple-


menta um mecanismo de política usando uma DSL projetada para facilitar a ex-
pressão de decisões de controle de acesso. Ele pode ser integrado a uma infraes-
trutura existente usando sua API REST ou como uma biblioteca Go, e as integra-
ções foram escritas para vários proxies reversos e gateways para adicionar
políticasexecução.

8.3.4 Aplicação de política distribuída e XACML

Em vez de Em vez de combinar toda a lógica de imposição de políticas no próprio


agente, outra abordagem é centralizar a definição de políticas em um servidor se-
parado, que fornece uma API REST para que os agentes de política se conectem e
avaliem as decisões de política. Ao centralizar as decisões de política, uma equipe
de segurança pode revisar e ajustar mais facilmente as regras de política para to-
das as APIs em uma organização e garantir que regras consistentes sejam aplica-
das. Esta abordagem está mais intimamente associada com XACML, a eXtensible
Access-Control Markup Language (consulte http://mng.bz/Qx2w), que define uma
linguagem baseada em XML para políticas com um rico conjunto de funções para
combinar atributos e combinar decisões de políticas. Embora o formato XML para
definir políticas tenha caído um pouco em desuso nos últimos anos, o XACML
também definiu uma arquitetura de referência para sistemas ABAC que tem sido
muito influente e agora está incorporada nas recomendações do NIST para ABAC (
http://mng.bz/X0YG ).

DEFINIÇÃO XACML é a eXtensible Access-Control Markup Language, um pa-


drão produzido pelo organismo de padrões OASIS. O XACML define uma lingua-
gem de política rica baseada em XML e uma arquitetura de referência para apli-
cação de política distribuída.
Os principais componentes da arquitetura de referência XACML são mostrados na
figura 8.5 e consistem nos seguintes componentes funcionais:

Um ponto de aplicação da política(PEP) atua como um agente de política para


interceptar solicitações a uma API e rejeitar quaisquer solicitações negadas
pela política.
O PEP conversa com um Ponto de Decisão Política(PDP) para determinar se
uma solicitação deve ser permitida. O PDP contém um mecanismo de política
como os que você já viu neste capítulo.
Um ponto de informação da política(PIP) é responsável por recuperar e arma-
zenar em cache valores de atributos relevantes de diferentes fontes de dados.
Podem ser bancos de dados locais ou serviços remotos, como um terminal OIDC
UserInfo (consulte o capítulo 7).
Um ponto de administração de política(PAP) fornece uma interface para admi-
nistradores definirem e gerenciarem políticas.
Figura 8.5 XACML define quatro serviços que cooperam para implementar um sis-
tema ABAC. O Policy Enforcement Point (PEP) rejeita solicitações negadas pelo
Policy Decision Point (PDP). O Policy Information Point (PIP) recupera atributos
que são relevantes para as decisões políticas. Um ponto de administração de polí-
tica (PAP) pode ser usado para definir e gerenciar políticas.

Os quatro componentes podem ser colocados ou podem ser distribuídos em má-


quinas diferentes. Em particular, a arquitetura XACML permite que as definições
de política sejam centralizadas em uma organização, facilitando a administração
e a revisão. Vários PEPs para diferentes APIs podem se comunicar com o PDP por
meio de uma API (normalmente uma API REST), e o XACML oferece suporte ao
conceito de conjuntos de políticaspara permitir que políticas para diferentes PEPs
sejam agrupadas com diferentes regras de combinação. Muitos fornecedores ofe-
recem implementações da arquitetura de referência XACML de alguma forma,
embora muitas vezes sem a linguagem de política XML padrão, fornecendo agen-
tes de política ou gateways e serviços PDP que você pode instalar em seu ambi-
ente para adicionar decisões de controle de acesso ABAC a serviços existentes e
APIs.

8.3.5 Melhores práticas para ABAC

EmboraABAC fornece uma base extremamente flexível para controle de acesso,


sua flexibilidade também pode ser uma desvantagem. É fácil desenvolver regras
excessivamente complexas, tornando difícil determinar exatamente quem tem
acesso a quê. Já ouvi falar de implantações com milhares de regras de política. Pe-
quenas mudanças nas regras podem ter impactos dramáticos e pode ser difícil
prever como as regras serão combinadas. Por exemplo, certa vez trabalhei em um
sistema que implementava regras ABAC na forma de expressões XPath que eram
aplicadas a mensagens XML recebidas; se uma mensagem correspondesse a al-
guma regra, ela era rejeitada.

Descobriu-se que uma pequena alteração na estrutura do documento feita por ou-
tra equipe fez com que muitas das regras não correspondessem mais, o que per-
mitiu que solicitações inválidas fossem processadas por várias semanas antes que
alguém percebesse. Teria sido bom poder dizer automaticamente quando essas
expressões XPath não poderiam mais corresponder a nenhuma mensagem, mas
devido à flexibilidade do XPath, isso acaba sendo impossível de determinar auto-
maticamente em geral, e todos os nossos testes continuaram usando o formato an-
tigo. Essa anedota mostra a possível desvantagem dos mecanismos flexíveis de
avaliação de política, mas eles ainda são uma maneira muito poderosa de estrutu-
rar a lógica de controle de acesso.

Para maximizar os benefícios do ABAC e limitar o potencial de erros, considere a


adoção das seguintes práticas recomendadas:

Camada ABAC sobre uma tecnologia de controle de acesso mais simples, como
RBAC. Isso fornece uma estratégia de defesa em profundidade para que um
erro nas regras ABAC não resulte em perda total de segurança.
Implemente testes automatizados de seus endpoints de API para que você seja
alertado rapidamente se uma alteração de política resultar na concessão de
acesso a partes indesejadas.
Certifique-se de que as políticas de controle de acesso sejam mantidas em um
sistema de controle de versão para que possam ser facilmente revertidas, se ne-
cessário. Assegure a revisão adequada de todas as mudanças de política.
Considere quais aspectos da política devem ser centralizados e quais devem ser
deixados para APIs individuais ou agentes de políticas locais. Embora possa ser
tentador centralizar tudo, isso pode introduzir uma camada de burocracia que
pode dificultar as mudanças. No pior dos casos, isso pode violar o princípio do
menor privilégio porque políticas excessivamente amplas são mantidas devido
à sobrecarga de alterá-las.
Meça a sobrecarga de desempenho da avaliação de política ABAC com antece-
dência e frequência.

questionário

7. Quais são as quatro principais categorias de atributos usados ​nas decisões


ABAC?
1. Função
2. Ação
3. Sujeito
4. Recurso
5. Temporal
6. geográfico
7. Meio Ambiente
8. Qual dos componentes da arquitetura de referência XACML é usado para defi-
nir e gerenciar políticas?
1. Ponto de decisão da política
2. Ponto de Recuperação de Política
3. Ponto de Demolição da Política
4. Ponto de informação da política
5. Ponto de aplicação da política
6. Ponto de Administração de Política

As respostas estão no final do capítulo.

Respostas para perguntas do questionário

1. Verdadeiro. Muitos modelos de grupos permitem que grupos contenham outros


grupos, conforme discutido na seção 8.1.
2. a, c, d. Grupos estáticos e dinâmicos são padrão e grupos estáticos virtuais não
são padrão, mas amplamente implementados.
3. d. groupOfNames (ou groupOfUniqueNames ).
4. c, d, e. O RBAC apenas atribui permissões usando funções, nunca diretamente a
indivíduos. As funções oferecem suporte à separação de tarefas, pois normal-
mente pessoas diferentes definem permissões de função daquelas que atri-
buem funções aos usuários. As funções geralmente são definidas para cada
aplicativo ou API, enquanto os grupos geralmente são definidos globalmente
para toda a organização.
5. c. O modelo NIST permite que um usuário ative apenas algumas de suas fun-
ções ao criar uma sessão, o que permite o princípio do menor privilégio.
6. d. A @RolesAllowed anotação determina quais papéis podem ser atribuídos
ao método.
7. b, c, d e g. Assunto, Recurso, Ação e Ambiente.
8. f. O ponto de administração de política é usado para definir egerirpolíticas.

Resumo

Os usuários podem ser reunidos em grupos em um nível organizacional para


torná-los mais fáceis de administrar. O LDAP possui suporte integrado para ge-
renciamento de grupos de usuários.
O RBAC coleta conjuntos relacionados de permissões em objetos em funções
que podem ser atribuídas a usuários ou grupos e posteriormente revogadas. As
atribuições de funções podem ser estáticas ou dinâmicas.
As funções geralmente são específicas para uma API, enquanto os grupos são
mais frequentemente definidos estaticamente para uma organização inteira.
O ABAC avalia as decisões de controle de acesso dinamicamente com base nos
atributos do assunto, no recurso que está acessando, na ação que está tentando
executar e no ambiente ou contexto em que ocorre a solicitação (como hora ou
local).
As decisões de controle de acesso ABAC podem ser centralizadas usando um
mecanismo de política. O padrão XACML define um modelo comum para a ar-
quitetura ABAC, com componentes separados para decisões de políticas (PDP),
informações de políticas (PIP), administração de políticas (PAP) e aplicação de
políticas(PEP).

1.
Uma classe de objeto no LDAP define o esquema de uma entrada de diretório, des-
crevendo quais atributos ele contém.

Você também pode gostar