Você está na página 1de 85

Tópicos Comece a aprender Pesquise mais de 50.

000 cursos, even… O que há de novo

9 Segurança baseada em capacidade e


macaroons
Este capítulo cobre

Compartilhamento de recursos individuais por meio de URLs de recursos


Evitando ataques confusos de deputados contra controle de acesso baseado em
identidade
Integrando recursos com um design de API RESTful
Capacidades de endurecimento com macaroons e advertências contextuais

No capítulo 8, você implementou controles de acesso baseados em identidade que


representam a abordagem principal para controle de acesso no design de API mo-
derno. Às vezes, os controles de acesso baseados em identidade podem entrar em
conflito com outros princípios de design de API seguro. Por exemplo, se um usuá-
rio do Natter deseja compartilhar uma mensagem que escreveu com um público
mais amplo, ele gostaria apenas de copiar um link para ela. Mas isso não funcio-
nará a menos que os usuários com os quais eles estão compartilhando o link tam-
bém sejam membros do espaço social do Natter no qual ele foi postado, porque
eles não terão acesso. A única maneira de conceder a esses usuários acesso a essa
mensagem é torná-los membros do espaço, o que viola o princípio da menor auto-
ridade (porque agora eles têm acesso a todas as mensagens desse espaço) ou co-
piar e colar o mensagem inteira em um sistema diferente.

As pessoas compartilham naturalmente recursos e delegam acesso a outras pes-


soas para atingir seus objetivos, portanto, uma solução de segurança de API deve
tornar isso simples e seguro; caso contrário, seus usuários encontrarão maneiras
inseguras de fazer isso de qualquer maneira. Neste capítulo, você implementará
técnicas de controle de acesso baseadas em capacidade que permitem o comparti-
lhamento seguro adotando o princípio da menor autoridade (POLA) à sua conclu-
são lógica e permitindo um controle refinado sobre o acesso a recursos individu-
ais. Ao longo do caminho, você verá como os recursos impedem uma categoria ge-
ral de ataques contra APIs conhecidos como ataques de deputado confusos.

DEFINIÇÃO Um ataque de deputado confusoocorre quando um componente de


um sistema com privilégios elevados pode ser enganado por um invasor para rea-
lizar ações que o próprio invasor não teria permissão para executar. Os ataques
CSRF do capítulo 4 são exemplos clássicos de ataques de deputados confusos, nos
quais o navegador da Web é levado a realizar as solicitações do invasor usando o
cookie de sessão da vítima.
9.1 Segurança baseada em capacidade

uma capacidadeé uma referência que não pode ser falsificada a um objeto ou re-
curso junto com um conjunto de permissões para acessar esse recurso. Para ilus-
trar como a segurança baseada em capacidade difere da segurança baseada em
identidade, considere as duas maneiras a seguir para copiar um arquivo em siste-
mas UNIX 1 :

cp a.txt b.txt
cat <a.txt >b.txt

A primeira, usando o cp comando, recebe como entrada o nome do arquivo a ser


copiado e o nome do arquivo a ser copiado. A segunda, usando o cat comando,
em vez disso, toma como entrada dois descritores de arquivo: um aberto para lei-
tura e outro aberto para escrita. Em seguida, ele simplesmente lê os dados do pri-
meiro descritor de arquivo e os grava no segundo.

DEFINIÇÃO Um descritor de arquivoé um identificador abstrato que repre-


senta um arquivo aberto junto com um conjunto de permissões nesse arquivo. Os
descritores de arquivo são um tipo de capacidade.

Se você pensar nas permissões que cada um desses comandos precisa, o cp co-
mandoprecisa ser capaz de abrir qualquer arquivo que você possa nomear para
leitura e gravação. Para permitir isso, o UNIX executa o cp comando com as mes-
mas permissões de sua própria conta de usuário, para que possa fazer qualquer
coisa que você fizer, incluindo excluir todos os seus arquivos e enviar suas fotos
privadas por e-mail para um estranho. Isso viola o POLA porque o comando re-
cebe muito mais permissões do que precisa. o cat comando, por outro lado, só
precisa ler de sua entrada e gravar em sua saída. Ele não precisa de nenhuma
permissão (mas é claro que o UNIX dá todas as suas permissões de qualquer ma-
neira). Um descritor de arquivo é um exemplo de capacidade, porque combina
uma referência a algum recurso junto com um conjunto de permissões para atuar
nesse recurso.

Em comparação com as técnicas de controle de acesso baseadas em identidade


mais dominantes discutidas no capítulo 8, os recursos têm várias diferenças:

O acesso aos recursos é feito por meio de referências infalíveis aos objetos que
também concedem autoridade para acessar esse recurso. Em um sistema base-
ado em identidade, qualquer pessoa pode tentar acessar um recurso, mas pode
ter o acesso negado, dependendo de quem é. Em um sistema baseado em capa-
cidade, é impossível enviar uma solicitação a um recurso se você não tiver ca-
pacidade de acessá-lo. Por exemplo, é impossível gravar em um descritor de ar-
quivo que seu processo não possui. Você verá na seção 9.2 como isso é imple-
mentado para APIs REST.
Os recursos fornecem acesso refinado a recursos individuais e geralmente su-
portam o POLA de forma mais natural do que os sistemas baseados em identi-
dade. É muito mais fácil delegar uma pequena parte de sua autoridade a outra
pessoa, dando-lhe alguns recursos sem dar-lhe acesso a toda a sua conta.
A capacidade de compartilhar recursos facilmente pode dificultar a determina-
ção de quem tem acesso a quais recursos por meio de sua API. Na prática, isso
também é verdade para sistemas baseados em identidade, pois as pessoas com-
partilham o acesso de outras maneiras (como compartilhando senhas).
Alguns sistemas baseados em recursos não oferecem suporte à revogação de re-
cursos depois que eles foram concedidos. Quando a revogação é suportada, a
revogação de um recurso amplamente compartilhado pode negar o acesso a
mais pessoas do que o pretendido.

Uma das razões pelas quais a segurança baseada em capacidade é menos usada
do que a segurança baseada em identidade é devido à crença generalizada de que
as capacidades são difíceis de controlar devido ao fácil compartilhamento e à apa-
rente dificuldade de revogação. Na verdade, esses problemas são resolvidos por
sistemas de capacidade do mundo real, conforme discutido no artigo Capability
Myths Demolished, de Mark S. Miller, Ka-Ping Yee e Jonathan Shapiro (
http://srl.cs.jhu.edu/pubs/ SRL2003-02.pdf). Para dar um exemplo, geralmente é as-
sumido que os recursos podem ser usados ​apenas para controle de acesso discrici-
onário, porque o criador de um objeto (como um arquivo) pode compartilhar re-
cursos para acessar esse arquivo com qualquer pessoa. Mas em um sistema de ca-
pacidade pura, as comunicações entre as pessoas também são controladas por ca-
pacidades (como é a capacidade de criar arquivos em primeiro lugar); portanto,
se Alice criar um novo arquivo, ela poderá compartilhar a capacidade de acessar
esse arquivo com Bob somente se ela tem uma capacidade que lhe permite se co-
municar com Bob. Claro, nada impede que Bob peça a Alice pessoalmente para
executar ações no arquivo, mas esse é um problema que nenhum sistema de con-
trole de acesso pode evitar.

Uma breve história das capacidades

A segurança baseada em capacidade foi desenvolvida pela primeira vez no con-


texto de sistemas operacionais como o KeyKOS na década de 1970 e tem sido apli-
cada a linguagens de programação e protocolos de rede desde então. O IBM
System/38, que foi o antecessor do bem-sucedido AS/400 (agora IBM i), usava re-
cursos para gerenciar o acesso a objetos. Na década de 1990, a linguagem de pro-
gramação E ( http://erights.org ) combinou segurança baseada em capacidade com
segurança orientada a objetos(OO) programação para criar segurança baseada em
capacidade de objeto(ou ocaps), onde capacidades são apenas referências de obje-
tos normais em uma linguagem de programação OO segura para memória. A se-
gurança baseada em capacidade de objeto se encaixa bem com a sabedoria con-
vencional em relação a um bom design OO e padrões de design, porque ambos en-
fatizam a eliminação de variáveis ​globais e evitam métodos estáticos que execu-
tam efeitos colaterais.

E também incluiu um protocolo seguro para fazer chamadas de método em uma


rede usando recursos. Este protocolo foi adotado e atualizado peloEstrutura Cap'n
Proto ( https://capnproto.org/rpc.html#security ), que fornece um protocolo biná-
rio muito eficiente para implementar APIs com base em chamadas de procedi-
mento remoto. Os recursos também estão aparecendo em sites populares e APIs
REST.

9.2 Capacidades e REST

oos exemplos até agora foram baseados na segurança do sistema operacional,


mas a segurança baseada em capacidade também pode ser aplicada a APIs REST
disponíveis por HTTP. Por exemplo, suponha que você desenvolveu um aplicativo
Natter para iOS que permite ao usuário selecionar uma foto de perfil e deseja per-
mitir que os usuários carreguem uma foto de sua conta do Dropbox. O Dropbox
suporta OAuth2 para aplicativos de terceiros, mas o acesso permitido pelos esco-
pos OAuth2 é relativamente amplo; normalmente, um usuário pode conceder
acesso apenas a todos os seus arquivos ou criar uma pasta específica do aplicativo
separada do restante de seus arquivos. Isso pode funcionar bem quando o aplica-
tivo precisa de acesso regular a muitos de seus arquivos, mas, nesse caso, seu apli-
cativo precisa apenas de acesso temporário para baixar um único arquivo esco-
lhido pelo usuário. É uma violação da POLA conceder acesso somente leitura per-
manente a todo o seu Dropbox apenas para carregar uma foto. Embora os escopos
OAuth sejam ótimos para restringir as permissões concedidas a aplicativos de ter-
ceiros, eles tendem a ser estáticos e aplicáveis ​a todos os usuários. Mesmo se você
tivesse um escopo para cada arquivo individual, o aplicativo já teria que saber
qual arquivo precisava acessar no momento de fazer a solicitação de
autorização.2
Para dar suporte a esse caso de uso, o Dropbox desenvolveu o Choosere Economi-
zadorAPIs (consulte https://www.dropbox.com/developers/chooser e
https://www.dropbox.com/developers/ saver ), que permitem que um desenvolve-
dor de aplicativo solicite ao usuário acesso único a arquivos específicos em seu
Dropbox. Em vez de iniciar um fluxo OAuth, o desenvolvedor do aplicativo chama
uma função SDK que exibirá uma IU de seleção de arquivo fornecida pelo Drop-
box, conforme mostrado na Figura 9.1. Como essa interface do usuário é imple-
mentada como uma janela separada do navegador em execução no dropbox.com
e não como parte do aplicativo de terceiros, ela pode mostrar todos os arquivos do
usuário. Quando o usuário seleciona um arquivo, o Dropbox retorna um recurso
ao aplicativo que permite acessar apenas o arquivo que o usuário selecionou por
um curto período de tempo (4 horas atualmente para a API Chooser).
Figura 9.1 A IU do Dropbox Chooser permite que um usuário selecione arquivos
individuais para compartilhar com um aplicativo. O aplicativo recebe acesso so-
mente leitura por tempo limitado apenas aos arquivos selecionados pelo usuário.

As APIs Chooser e Saver fornecem várias vantagens sobre um fluxo OAuth2 nor-
mal para este caso de uso simples de compartilhamento de arquivo:
O autor do aplicativo não precisa decidir com antecedência qual recurso pre-
cisa acessar. Em vez disso, eles apenas informam ao Dropbox que precisam de
um arquivo para abrir ou salvar dados, e o Dropbox permite que o usuário de-
cida qual arquivo usar. O aplicativo nunca consegue ver uma lista dos outros
arquivos do usuário.
Como o aplicativo não está solicitando acesso de longo prazo à conta do usuá-
rio, não há necessidade de uma página de consentimento para garantir que o
usuário saiba o acesso concedido. A seleção de um arquivo na IU indica implici-
tamente o consentimento e, como o escopo é tão refinado, os riscos de abuso
são muito menores.
A interface do usuário é implementada pelo Dropbox e, portanto, é consistente
para todos os aplicativos e páginas da Web que usam a API. Pequenos detalhes
como o item de menu “Recente” funcionam de forma consistente em todos os
aplicativos.

Para esses casos de uso, os recursos fornecem uma experiência de usuário muito
intuitiva e natural que também é significativamente mais segura do que as alter-
nativas. Muitas vezes, assume-se que existe uma troca natural entre segurança e
usabilidade: quanto mais seguro for um sistema, mais difícil será usá-lo. Os recur-
sos parecem desafiar essa sabedoria convencional, porque mudar para um geren-
ciamento de permissões mais refinado permite padrões de interação mais conve-
nientes. O usuário escolhe os arquivos com os quais deseja trabalhar e o sistema
concede ao aplicativo acesso apenas a esses arquivos, sem a necessidade de um
complicado processo de consentimento.
Deputados confusos e autoridade ambiental

Muitas vulnerabilidades comuns em APIs e outros softwares são variações do que


é conhecido como ataque de deputado confuso, como os ataques CSRF discutidos
no capítulo 4, mas muitos tipos de ataque de injeção e XSS também são causados ​
pelo mesmo problema. O problema ocorre quando um processo é autorizado a
agir com sua autoridade (como seu “deputado”), mas um invasor pode enganar
esse processo para realizar ações maliciosas. O deputado confuso original (
http://cap-lore.com/CapTheory/ConfusedDeputy.html) era um compilador rodando
em um computador compartilhado. Os usuários podem enviar trabalhos para o
compilador e fornecer o nome de um arquivo de saída para armazenar o resul-
tado. O compilador também manteria um registro de cada trabalho para fins de
cobrança. Alguém percebeu que poderia fornecer o nome do arquivo de cobrança
como o arquivo de saída e o compilador o substituiria alegremente, perdendo to-
dos os registros de quem fez o quê. O compilador tinha permissões para gravar
em qualquer arquivo e isso poderia ser abusado para sobrescrever um arquivo
que o próprio usuário não poderia acessar.

No CSRF, o representante é o seu navegador que recebeu um cookie de sessão


após o login. Quando você faz solicitações à API a partir do JavaScript, o navega-
dor adiciona automaticamente o cookie para autenticar as solicitações. O pro-
blema é que, se um site malicioso fizer solicitações à sua API, o navegador tam-
bém anexará o cookie a essas solicitações, a menos que você tome medidas adicio-
nais para evitar isso (como as medidas anti-CSRF no capítulo 4). Cookies de sessão
são um exemplo de autoridade de ambiente: o cookie faz parte do ambiente no
qual uma página da web é executada e é adicionado de forma transparente às so-
licitações. A segurança baseada em capacidade visa remover todas as fontes de
autoridade do ambiente e, em vez disso, exige que cada solicitação seja especifica-
mente autorizada de acordo com o POLA.
DEFINIÇÃO Quando a permissão para realizar uma ação é concedida automati-
camente a todos os pedidos que se originam de um determinado ambiente, isso é
conhecido como autoridade de ambiente. Exemplos de autoridade de ambiente
incluem cookies de sessão e permissão de acesso com base no endereço IP de
onde vem uma solicitação. A autoridade ambiente aumenta os riscos de ataques
de deputados confusos e deve ser evitada sempre que possível.

9.2.1 Capacidades como URIs

Arquivoos descritores contam com regiões especiais de memória que podem ser
alteradas apenas por código privilegiado no kernel do sistema operacional para
garantir que os processos não possam adulterar ou criar descritores de arquivo
falsos. Linguagens de programação de capacidade segura também são capazes de
evitar adulterações, controlando o tempo de execução em que o código é execu-
tado. Para uma API REST, isso não é uma opção porque você não pode controlar a
execução de clientes remotos, portanto, outra técnica precisa ser usada para ga-
rantir que os recursos não possam ser falsificados ou adulterados. Você já viu vá-
rias técnicas para criar tokens que não podem ser falsificados nos capítulos 4, 5 e
6, usando strings aleatórias grandes que não podem ser adivinhadas ou usando
técnicas criptográficas para autenticar os tokens. Você pode reutilizar esses for-
matos de token para criar tokens de capacidade, mas há várias diferenças
importantes:

A autenticação baseada em token transmite a identidade de um usuário, a par-


tir da qual suas permissões podem ser consultadas. Em vez disso, um recurso
transmite diretamente algumas permissões e não identifica um usuário.
Os tokens de autenticação são projetados para serem usados ​para acessar mui-
tos recursos em uma API, portanto, não estão vinculados a nenhum recurso.
Em vez disso, os recursos são diretamente acoplados a um recurso e podem ser
usados ​para acessar apenas esse recurso. Você usa recursos diferentes para
acessar recursos diferentes.
Um token normalmente terá vida curta porque transmite acesso amplo à conta
de um usuário. Uma capacidade, por outro lado, pode durar mais porque tem
um escopo muito mais restrito para abuso.

O REST já possui um formato padrão para identificar recursos, o URI, portanto,


essa é a representação natural de um recurso para uma API REST. Uma capaci-
dade representada como um URI é conhecida como URI de capacidade. Os URIs de
capacidade são amplamente difundidos na Web, na forma de links enviados em e-
mails de redefinição de senha,GitHub Gists e compartilhamento de documentos
como no exemplo do Dropbox.
DEFINIÇÃO Um URI de capacidade (ou URL de capacidade) é um URI que iden-
tifica um recurso e transmite um conjunto de permissões para acessar esse re-
curso. Normalmente, um URI de capacidade codifica um token que não pode ser
adivinhado em alguma parte da estrutura do URI.

Para criar um URI de capacidade, você pode combinar um URI normal com um to-
ken de segurança. Existem várias maneiras de fazer isso, conforme mostrado na
figura 9.2.

Figura 9.2 Há muitas maneiras de codificar um token de segurança em um URI.


Você pode codificá-lo no caminho do recurso ou pode fornecê-lo usando um parâ-
metro de consulta. Representações mais sofisticadas codificam o token nos ele-
mentos fragmento ou userinfo do URI, mas requerem alguma análise do lado do
cliente.

Uma abordagem comumente usada é codificar um token aleatório no compo-


nente de caminho do URI, que é o que a API Dropbox Chooser faz, retornando
URIs como o seguinte:

https://dl.dropboxusercontent.com/1/view/8ygmwuqzf1l6x7c/
➥ livro/gráficos/CH08_FIG8.2_RBAC.png

No caso do Dropbox, o token aleatório é codificado em um prefixo do caminho


real do arquivo. Embora esta seja uma representação natural, significa que o
mesmo recurso pode ser representado por URIs com caminhos completamente di-
ferentes dependendo do token, portanto, um cliente que recebe acesso ao mesmo
recurso por meio de diferentes URIs de capacidade pode não ser capaz de dizer
que eles realmente referem-se ao mesmo recurso. Uma alternativa é passar o to-
ken como um parâmetro de consulta, caso em que o URI do Dropbox seria seme-
lhante ao seguinte:

https://dl.dropboxusercontent.com/1/view/
➥ livro/gráficos/CH08_FIG8.2_RBAC.png?token=8ygmwuqzf1l6x7c

Há um formulário padrão para esses URIs quando o token é um token OAuth2 de-
finido pela RFC 6750 ( https://tools.ietf.org/html/rfc6750#section-2.3 ) usando o
nome do parâmetro access_token . Geralmente, essa é a abordagem mais sim-
ples de implementar porque não requer alterações nos recursos existentes, mas
compartilha algumas deficiências de segurança com a abordagem baseada em
caminho:

Os caminhos de URI e os parâmetros de consulta são frequentemente registra-


dos por servidores da Web e proxies, o que pode disponibilizar o recurso para
qualquer pessoa que tenha acesso aos logs. O uso de TLS impedirá que os pro-
xies vejam o URI, mas uma solicitação ainda pode passar por vários servidores
não criptografados em uma implantação típica.
O URI completo pode ser visível para terceiros por meio do Referer cabeçalho
HTTPou a window.referrer variávelexposto ao conteúdo executado em um
iframe HTML. Você pode usar o Referrer-Policy cabeçalhoe
rel=”noreferrer” atributo em links em sua interface do usuário para evitar
esse vazamento. Consulte http://mng.bz/1g0g para obter detalhes.
Os URIs usados ​em navegadores da Web podem ser acessados ​por outros usuá-
rios, observando o histórico do navegador.

Para proteger os URIs de capacidade contra essas ameaças, você pode codificar o
token no componente de fragmento ou no URI ou até mesmo na parte userinfo
que foi originalmente projetada para armazenar credenciais HTTP Basic em um
URI. Nem o fragmento nem o componente userinfo de um URI são enviados para
um servidor da Web por padrão e ambos são removidos dos URIs comunicados
nos Referer cabeçalhos.
Credenciais em URIs: uma lição da história

O desejo de compartilhar o acesso a recursos privados simplesmente comparti-


lhando um URI não é novo. Por muito tempo, os navegadores suportaram a codifi-
cação de um nome de usuário e senha em um URL HTTP no formato
http://alice:secret@example.com/resource. Quando esse link era clicado, o navega-
dor enviava o nome de usuário e a senha usando a autenticação básica HTTP
(consulte o capítulo 3). Embora conveniente, isso é amplamente considerado um
desastre de segurança. Para começar, compartilhar um nome de usuário e senha
fornece acesso total à sua conta para qualquer pessoa que veja o URI. Em segundo
lugar, os invasores logo perceberam que isso poderia ser usado para criar links de
phishing convincentes, como
http://www.google.com:80@evil.example.com/login.html. Um usuário desavisado
veria o domínio google .com no início do link e presumiria que era genuíno,
quando na verdade este é apenas um nome de usuário e eles realmente serão en-
viados para uma página de login falsa no site do invasor. Para evitar esses ata-
ques, os fornecedores de navegadores pararam de oferecer suporte a essa sintaxe
de URI e agora removem agressivamente as informações de login ao exibir ou se-
guir esses links. Embora os URIs de capacidade sejam significativamente mais se-
guros do que compartilhar diretamente uma senha, você ainda deve estar ciente
de qualquer potencial de uso indevido se exibir URIs para os usuários.
URIS DE CAPACIDADE PARA APIS REST

As desvantagens dos URIs de capacidade que acabamos de mencionar se aplicam


quando são usados ​como um meio de navegar em um site. Quando os URIs de ca-
pacidade são usados ​em uma API REST, muitos desses problemas não se aplicam:

o Referer cabeçalhoe window.referrer variáveissão preenchidos pelos na-


vegadores quando um usuário navega diretamente de uma página da Web para
outra ou quando uma página é incorporada a outra em um iframe. Nenhuma
delas se aplica às respostas JSON típicas de uma API porque elas não são rende-
rizadas diretamente como páginas.
Da mesma forma, como os usuários normalmente não navegam diretamente
para os endpoints da API, esses URIs não vão parar no histórico do navegador.
Também é improvável que os URIs da API sejam marcados ou salvos por um
longo período de tempo. Normalmente, um cliente conhece alguns URIs perma-
nentes como pontos de entrada para uma API e, em seguida, navega para ou-
tros URIs conforme acessa os recursos. Esses URIs de recursos podem usar to-
kens de curta duração para atenuar o vazamento de tokens nos logs de acesso.
Essa ideia é explorada mais adiante na seção 9.2.3.

No restante do capítulo, você usará URIs de capacidade com o token codificado no


parâmetro de consulta porque isso é simples de implementar. Para atenuar qual-
quer ameaça de vazamento de tokens em arquivos de log, você usará tokens de
curta duração e aplicará mais proteções emseção 9.2.4.
questionário

1. Quais dos seguintes são bons lugares para codificar um token em um URI de
capacidade?
1. o fragmento
2. O nome do host
3. O nome do esquema
4. O número da porta
5. O componente do caminho
6. Os parâmetros de consulta
7. O componente de informações do usuário
2. Quais das opções a seguir são diferenças entre recursos e autenticação baseada
em token?
1. Os recursos são mais volumosos do que os tokens de autenticação.
2. Os recursos não podem ser revogados, mas os tokens de autenticação podem.
3. Os recursos estão vinculados a um único recurso, enquanto os tokens de au-
tenticação são aplicáveis ​a todos os recursos em uma API.
4. Os tokens de autenticação estão vinculados a uma identidade de usuário in-
dividual, enquanto os tokens de capacidade podem ser compartilhados entre
os usuários
5. Os tokens de autenticação são de curta duração, enquanto os recursos geral-
mente têm uma vida útil mais longa.

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


9.2.2 Usando URIs de capacidade na API Natter

Paraadicionar URIs de capacidade ao Natter, primeiro você precisa implementar


o código para criar um URI de capacidade. Para fazer isso, você pode reutilizar
um TokenStore implementação para criar o componente do token, codificando o
caminho do recurso e as permissões nos atributos do token, conforme mostrado
na Listagem 9.1. Como os recursos não estão vinculados a uma conta de usuário
individual, você deve deixar o campo de nome de usuário do token em branco. O
token pode então ser codificado no URI como um parâmetro de consulta, usando
o access _token campo padrãoda RFC 6750. Você pode usar a
java.net.URI classepara construir o URI de capacidade, passando o caminho e
os parâmetros de consulta. Alguns dos URIs de capacidade que você criará terão
vida longa, mas outros terão vida curta para mitigar o roubo de tokens. Para ofe-
recer suporte a isso, permita que o chamador especifique por quanto tempo o re-
curso deve durar adicionando um Duration argumento de expiraçãoque é usado
para definir o tempo de expiração do token.

Abra o projeto Natter API 3 e navegue até


src/main/java/com/manning/apisecurityinaction/controller e crie um novo ar-
quivo denominado CapabilityController.java com o conteúdo da listagem 9.1 e
salve o arquivo.

Listagem 9.1 Gerando URIs de capacidade


pacote com.manning.apisecurityinaction.controller;

importar com.manning.apisecurityinaction.token.SecureTokenStore;
importar com.manning.apisecurityinaction.token.TokenStore.Token;
importar faísca.*;
importar java.net.*;
importar java.time.*;
importar java.util.*;
importar java.time.Instant.now estático;
classe pública CapabilityController {
private final SecureTokenStore tokenStore; ❶

public CapabilityController(SecureTokenStore tokenStore) { ❶


this.tokenStore = tokenStore; ❶
}

public URI createUri(Request request, String path, String perms,


Expiração da duraçãoDuração) {

var token = new Token(now().plus(expiryDuration), null); ❷


token.attributes.put("caminho", caminho); ❸
token.attributes.put("perms", perms); ❸
var tokenId = tokenStore.create(pedido, token);
var uri = URI.create(request.uri());
return uri.resolve(caminho + "?access_token=" + tokenId); ❹
}
}

❶ Use um SecureTokenStore existente para gerar tokens.

❷ Deixe o nome de usuário nulo ao criar o token.

❸ Codifique o caminho do recurso e as permissões no token.

❹ Adicione o token ao URI como um parâmetro de consulta.

Agora você pode conectar o código para criar o CapabilityController dentro


de seu método principal, então abra Main.java em seu editor e crie uma nova ins-
tância do objeto junto com um armazenamento de token para ele usar. Você pode
usar qualquer implementação segura de armazenamento de token, mas para este
capítulo você usará o DatabaseTokenStore porque cria tokens curtos e, por-
tanto, URIs curtos.

OBSERVAÇÃO Se você trabalhou no capítulo 6 e optou por marcar o Databa-


seTokenStore como um ConfidentialTokenStore apenas, então você preci-
sará envolvê-lo em um HmacTokenStore no trecho a seguir. Consulte o capítulo 6
(seção 6.4) se ficar preso.

Você também deve passar o novo controlador como um argumento adicional para
o SpaceController construtor, porque em breve você o usará para criar URIs de
capacidade:
var database = Database.forDataSource(datasource);
var capController = new CapabilityController(
new DatabaseTokenStore(banco de dados));
var spaceController = new SpaceController(banco de dados, capController);
var userController = new UserController(banco de dados);

Antes de começar a gerar URIs de capacidade, porém, você precisa fazer um


ajuste no armazenamento de token do banco de dados. O armazenamento atual
exige que cada token tenha um usuário associado e gerará um erro se você tentar
salvar um token com um null nome de usuário. Como os recursos não são basea-
dos em identidade, você precisa remover essa restrição. Abra schema.sql em
seu editor e remova a restrição não nula da tokens tabelaapagando as palavras
NOT NULL do final da user_id colunadefinição. A nova definição da tabela deve
ter a seguinte aparência:

Marcadores CREATE TABLE(


token_id VARCHAR(30) CHAVE PRIMÁRIA,
user_id VARCHAR(30) REFERÊNCIAS users(user_id), ❶
expiração TIMESTAMP NÃO NULO,
atributos VARCHAR(4096) NÃO NULO
);

❶ Remova a restrição NOT NULL aqui.


RETORNANDO URIS DE CAPACIDADE

Vocêagora pode ajustar a API para retornar URIs de capacidade que podem ser
usados ​para acessar mensagens e espaços sociais. Onde a API atualmente retorna
um caminho simples para um espaço social ou mensagem como /spaces/1 ,
você retornará um URI de capacidade total que pode ser usado para acessá-lo.
Para fazer isso, você precisa adicionar o CapabilityController como um novo
argumento para o SpaceController construtor, conforme mostrado na Lista-
gem 9.2. Abra SpaceController.java em seu editor e inclua o novo campo e o argu-
mento do construtor.

Listagem 9.2 Adicionando o CapabilityController

public class SpaceController {


private static final Set<String> DEFINED_ROLES =
Set.of("dono", "moderador", "membro", "observador");

banco de dados de banco de dados final privado;


final privado CapabilityController CapacityController; ❶

public SpaceController(banco de dados de banco de dados,


CapacidadeControlador capacidadeControlador) { ❶
this.database = banco de dados;
this.capabilityController = capacidadeController; ❶
}
❶ Adicione o CapabilityController como um novo campo e argumento do construtor.

O próximo passo é ajustar o createSpace métodousar


o CapabilityController para criar um URI de capacidade para retornar, con-
forme mostrado na Listagem 9.3. As alterações de código são mínimas: basta cha-
mar o createUri métodopara criar o URI de capacidade. Como o usuário que
cria um espaço recebe permissões totais sobre ele, você pode passar todas as per-
missões ao criar o URI. Uma vez criado um espaço, a única maneira de acessá-lo
será por meio do URI de capacidade, portanto, certifique-se de que esse link não
expire passando um tempo de expiração longo. Então use o
uri.toASCIIString() métodopara converter o URI em uma string codificada
corretamente. Porque você vai usar recursos para acesso, você pode remover as
linhas que inserem na user_roles tabela; estes não são mais necessários. Abra
SpaceController.java em seu editor e ajuste a implementação do createSpa-
ce métodopara corresponder à listagem 9.3. Novo código é destacado emnegrito.

Listagem 9.3 Retornando um URI de capacidade

public JSONObject createSpace(Solicitação de solicitação, Resposta de respost


var json = new JSONObject(request.body());
var spaceName = json.getString("nome");
if (spaceName.length() > 255) {
throw new IllegalArgumentException("nome do espaço muito longo");
}
var proprietário = json.getString("proprietário");
if (!owner.matches("[a-zA-Z][a-zA-Z0-9]{1,29}")) {
throw new IllegalArgumentException("nome de usuário inválido");
}
var assunto = request.attribute("assunto");
if (!proprietário.igual(assunto)) {
lançar novo IllegalArgumentException(
"proprietário deve corresponder ao usuário autenticado");
}

return database.withTransaction(tx -> {


var spaceId = database.findUniqueLong(
"SELECIONE O PRÓXIMO VALOR PARA space_id_seq;");

database.updateUnique(
"INSERT INTO espaços(space_id, nome, proprietário)" +
"VALUES(?, ?, ?);", spaceId, spaceName, proprietário);

var expiração = Duration.ofDays(100000); ❶


var uri = capacidadeController.createUri(request, ❷
"/spaces/" + spaceId, "rwd", expiração); ❷

resposta.status(201);
response.header("Localização", uri.toASCIIString()); ❸

retornar novo JSONObject()


.put("nome", espaçoNome)
.put("uri", uri); ❷
});
}

❶ Certifique-se de que o link não expire.

❷ Crie um URI de capacidade com permissões totais.

❸ Retorne o URI como uma string no cabeçalho Location e na resposta JSON.

VALIDANDO CAPACIDADES

Emboravocê está retornando um URL de capacidade, a API Natter ainda está


usando RBAC para conceder acesso às operações. Para converter a API para usar
recursos, você pode substituir o UserController.lookupPermissions método
atual, que determina as permissões procurando as funções do usuário autenti-
cado, com uma alternativa que lê as permissões diretamente do token de capaci-
dade. A Listagem 9.4 mostra a implementação de um lookupPermissions fil-
tropara o CapabilityController .

O filtro primeiro verifica se há um token de capacidade no access_token parâ-


metro de consulta. Se nenhum token estiver presente, ele retornará sem definir
nenhuma permissão. Isso resultará na não concessão de acesso. Depois disso, você
precisa verificar se o recurso que está sendo acessado corresponde exatamente ao
recurso para o qual se destina o recurso. Nesse caso, você pode verificar se o ca-
minho acessado corresponde ao caminho armazenado nos atributos do token, ob-
servando o request.pathInfo() método. Se todas essas condições forem aten-
didas, você poderá definir as permissões na solicitação com base nas permissões
armazenadas no token de capacidade. Esse é o mesmo perms request que você de-
finiu no capítulo 8 ao implementar o RBAC, portanto, as verificações de permissão
existentes em chamadas de API individuais funcionarão como antes, obtendo as
permissões do URI de capacidade em vez de uma pesquisa de função. Abra
CapabilityController.java em seu editor e adicione o novo método da listagem 9.4.

Listagem 9.4 Validando um token de capacidade

public void lookupPermissions(Solicitação de solicitação, Resposta de resposta)


var tokenId = request.queryParams("access_token"); ❶
if (tokenId == null) { return; }

tokenStore.read(request, tokenId).ifPresent(token -> { ❷


var tokenPath = token.attributes.get("path"); ❷
if (Objects.equals(tokenPath, request.pathInfo())) { ❷
request. attribute("perms", ❸
token.attributes.get("perms")); ❸
}
});
}

❶ Procure o token nos parâmetros de consulta.


❷ Verifique se o token é válido e corresponde ao caminho do recurso.

❸ Copie as permissões do token para a solicitação.

Para concluir a troca de recursos, você precisa alterar os filtros usados ​para pes-
quisar as permissões do usuário atual para usar o novo filtro de recursos. Abra
Main.java em seu editor e localize os três before () filtros que chamam atual-
mente userController::lookupPermissions e alterá-los para chamar o filtro
do controlador de capacidade. Eu destaquei a mudança de controlador em
negrito:

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

Agora você pode reiniciar o servidor API, criar um usuário e criar um novo es-
paço social. Isso funciona exatamente como antes, mas agora você recebe de volta
um URI de capacidade em resposta à criaçãoaespaço:

$ curl -X POST -H 'Tipo de conteúdo: aplicativo/json' \


-d '{"nome":"teste","proprietário":"demonstração"}' \
-u demo:senha https://localhost:4567/spaces
{"nome":"teste",
➥ "uri":"https://localhost:4567/spaces/1?access_token=
➥ jKbRWGFDuaY5yKFyiiF3Lhfbz-U"}

DICA Você pode estar se perguntando por que teve que criar um usuário e au-
tenticar antes de poder criar um espaço no último exemplo. Afinal, não acabamos
de abandonar a segurança baseada em identidade? A resposta é que a identidade
não está sendo usada para autorizar a ação neste caso, porque nenhuma permis-
são é necessária para criar um novo espaço social. Em vez disso, a autenticação é
necessária apenas para responsabilidade, para que haja um registro no log de au-
ditoria de quem criou o espaço.

9.2.3 ÓDIOAS

Você agora tem um URI de capacidade retornado da criação de um espaço social,


mas você não pode fazer muito com ele. O problema é que esse URI permite
acesso apenas ao recurso que representa o próprio espaço, mas para ler ou postar
mensagens no espaço, o cliente precisa acessar o sub-recurso
/spaces/1/messages . Anteriormente, isso não seria um problema, pois o cli-
ente poderia apenas construir o caminho para chegar às mensagens e usar o
mesmo token para acessar também esse recurso. Mas um token de capacidade dá
acesso a apenas um único recurso específico, seguindo o POLA. Para acessar as
mensagens, você precisará de um recurso diferente, mas os recursos não podem
ser falsificados, então você não pode simplesmente criar um! Parece que esse mo-
delo de segurança baseado em capacidade é realmente difícil de usar.

Se você é um aficionado por design RESTful, deve saber que fazer com que o cli-
ente apenas saiba que precisa adicionar /messages ao final de um URI para
acessar as mensagens é uma violação de um princípio REST central, que é que as
interações do cliente devem ser conduzidas por hipertexto (links). Em vez de um
cliente precisar ter conhecimento específico sobre como acessar recursos em sua
API, o servidor deve informar ao cliente onde estão os recursos e como acessá-los.
Esse princípio recebe o nome rápido de Hypertext as the Engine of Application
State, ou HATEOAS para abreviar. Roy Fielding, criador dos princípios de design
REST, declarou que esse é um aspecto crucial do design da API REST (
http://mng.bz/Jx6v ).

PRINCÍPIO HATEOAS, ou hipertexto como o mecanismo do estado do aplicativo,


é um princípio central do design da API REST que afirma que um cliente não pre-
cisa ter conhecimento específico de como construir URIs para acessar sua API. Em
vez disso, o servidor deve fornecer essas informações na forma de hiperlinks e
modelos de formulário.

O objetivo do HATEOAS é reduzir o acoplamento entre o cliente e o servidor que,


de outra forma, impediria que o servidor evoluísse sua API ao longo do tempo
porque poderia quebrar as suposições feitas pelos clientes. Mas o HATEOAS tam-
bém é um ajuste perfeito para URIs de capacidade porque podemos retornar no-
vos URIs de capacidade como links em resposta ao uso de outro URI de capaci-
dade, permitindo que um cliente navegue com segurança de um recurso para ou-
tro sem precisar fabricar nenhum URI por conta própria. 4

Você pode permitir que um cliente acesse e publique novas mensagens no espaço
social retornando um segundo URI do createSpace operação que permite o
acesso ao recurso de mensagens para este espaço, conforme a Listagem 9.5. Você
simplesmente cria um segundo URI de capacidade para esse caminho e o retorna
como outro link na resposta JSON. Abra SpaceController.java em seu editor nova-
mente e atualize o final do método createSpace para criar o segundo link. As no-
vas linhas de código são destacadas em negrito.

Listagem 9.5 Adicionando um link de mensagens

var uri = capacidadeController.createUri(pedido,


"/spaces/" + spaceId, "rwd", expiração);
var messageUri = capacidadeController.createUri(request, ❶
"/spaces/" + spaceId + "/messages", "rwd", expiração); ❶

resposta.status(201);
response.header("Localização", uri.toASCIIString());

retornar novo JSONObject()


.put("nome", espaçoNome)
.put("uri", uri)
.put("mensagens", mensagensUri); ❷

❶ Crie um novo URI de capacidade para as mensagens.

❷ Retorne o URI das mensagens como um novo campo na resposta.

Se você reiniciar o servidor de API novamente e criar um novo espaço, verá que
os dois URIs agora são retornados. Uma solicitação GET para o messages URI re-
tornará uma lista de mensagens no espaço, que agora pode ser acessada por qual-
quer pessoa com esse URI de capacidade. Por exemplo, você pode abrir esse link
diretamente em um navegador da web. Você também pode POSTAR uma nova
mensagem no mesmo URI. Novamente, esta operação requer autenticação além
do URI de capacidade porque a mensagem alega explicitamente ser de um usuá-
rio específico e, portanto, a API deve autenticar essa declaração. A permissão para
postar a mensagem vem da capacidade, enquanto a prova de identidade vem da
autenticação:

$ curl -X POST -H 'Tipo de conteúdo: aplicativo/json' \


-u demo:senha \ ❶
-d '{"autor":"demo","mensagem":"Olá!"}' \
'https://localhost:4567/spaces/1/messages?access_token=
➥ u9wu69dl5L8AT9FNe03TM-s4H8M' ❷

❶ A prova de identidade é fornecida por autenticação.


❷ A permissão para postar é concedida apenas pelo URI de capacidade.

SUPORTANDO DIFERENTES NÍVEIS DE ACESSO

Os URIs de capacidade retornados até agora fornecem acesso total aos recursos
que eles identificam, conforme indicado pelas rwd permissões (ler-escrever-ex-
cluir, se você se lembra do capítulo 3). Isso significa que é impossível dar a outra
pessoa acesso ao espaço sem dar a ela acesso total para excluir as mensagens de
outro usuário. Tanto para POLA!

Uma solução para isso é retornar vários URIs de capacidade com diferentes níveis
de acesso, conforme mostrado na Listagem 9.6. O proprietário do espaço pode for-
necer os URIs mais restritos, mantendo o URI que concede privilégios totais ape-
nas para moderadores confiáveis. Abra SpaceController.java novamente e inclua
os recursos adicionais da listagem. Reinicie a API e tente executar ações diferen-
tes com recursos diferentes.

Listagem 9.6 Recursos restritos

var uri = capacidadeController.createUri(pedido,


"/spaces/" + spaceId, "rwd", expiração);
var mensagensUri = capacidadeController.createUri(solicitação,
"/spaces/" + spaceId + "/messages", "rwd", expiração);
var messageReadWriteUri = capacidadeController.createUri( ❶
request, "/spaces/" + spaceId + "/messages", "rw", ❶
expiração); ❶
var messageReadOnlyUri = CapacityController.createUri( ❶
request, "/spaces/" + spaceId + "/messages", "r", ❶
expiração); ❶

resposta.status(201);
response.header("Localização", uri.toASCIIString());

retornar novo JSONObject()


.put("nome", espaçoNome)
.put("uri", uri)
.put("messages-rwd", messagesUri) ❷
.put("messages-rw", messageReadWriteUri) ❷
.put("messages-r", messageReadOnlyUri); ❷

❶ Crie URIs de recursos adicionais com permissões restritas.

❷ Retorne os recursos adicionais.

Para concluir a conversão da API em segurança baseada em capacidade, você pre-


cisa passar pelas outras ações da API e converter cada uma para retornar os URIs
de capacidade apropriados. Esta é uma tarefa bastante simples, por isso não a
abordaremos aqui. Um aspecto a ser observado é que você deve garantir que os
recursos retornados não concedam mais permissões do que o recurso usado para
acessar um recurso. Por exemplo, se o recurso usado para listar mensagens em
um espaço concedeu apenas permissões de leitura, os links para mensagens indi-
viduais dentro de um espaço também devem ser somente leitura. Você pode im-
por isso sempre baseando as permissões para um novo link nas permissões defi-
nidas para a solicitação atual, conforme mostrado na Listagem 9.7 para o find-
Messages método. Em vez de fornecer permissões de leitura e exclusão para to-
das as mensagens, você usa as permissões da solicitação existente. Isso garante
que os usuários que possuem um recurso de moderador verão links que permi-
tem ler e excluir mensagens, enquanto o acesso comum por meio de um recurso
de leitura/gravação ou somente leitura verá apenas links de mensagens somente
leitura.

Listagem 9.7 Impondo permissões consistentes

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


.replace("w", ""); ❷
resposta.status(200);
retornar novo JSONArray(messages.stream()
.map(msgId -> "/spaces/" + spaceId + "/messages/" + msgId)
.map(caminho ->
capacidadeController.createUri(request, path, perms)) ❸
.collect(Collectors.toList()));

❶ Pesquise as permissões da solicitação atual.

❷ Remova quaisquer permissões que não sejam aplicáveis.

❸ Crie novos recursos usando as permissões revisadas.


Atualize os métodos restantes no arquivo SpaceController.java para retornar os
URIs de capacidade apropriados, lembrando-se de seguir o POLA. O repositório
GitHub que acompanha o livro (
https://github.com/NeilMadden/apisecurityinaction ) completou o código-fonte se
você ficar preso, mas eu recomendo tentar você mesmo primeiro.

DICA Você pode usar a capacidade de especificar tempos de expiração diferentes


para links para implementar funcionalidades úteis. Por exemplo, quando um
usuário publica uma nova mensagem, você pode retornar um link que permite
editá-la por apenas alguns minutos. Um link separado pode fornecer acesso so-
mente leitura permanente. Isso permite que os usuários corrijam erros, mas não
alterem mensagens históricas.

questionário

3. Os URIs de capacidade para cada espaço usam tokens de banco de dados que
nunca expiram. Com o tempo, isso preencherá o banco de dados com tokens.
Quais das opções a seguir são maneiras de evitar isso?
1. Tokens de hash no banco de dados
2. Usando um formato de token independente, como JWTs
3. Usando um banco de dados nativo da nuvem que pode ser dimensionado
para conter todos os tokens
4. Usando o HmacTokenStore além do DatabaseTokenStore
5. Reutilizar um token existente quando o mesmo recurso já foi emitido
4. Qual é a principal razão pela qual o HATEOAS é um importante princípio de de-
sign ao usar URIs de capacidade? Escolha uma resposta.
1. HATEOAS é uma parte central do REST.
2. URIs de capacidade são difíceis de lembrar.
3. Os clientes não são confiáveis ​para criar seus próprios URIs.
4. Roy Fielding, o inventor do REST, diz que é importante.
5. Um cliente não pode criar seus próprios URIs de capacidade e, portanto, só
pode acessar outros recursos por meio de links.

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


9.2.4 URIs de capacidade para clientes baseados em navegador

DentroNa seção 9.2.1, mencionei que colocar o token no caminho do URI ou nos
parâmetros de consulta é menos do que ideal porque eles podem vazar nos logs
de auditoria, Referer cabeçalhos e no histórico do navegador. Esses riscos são li-
mitados quando os URIs de capacidade são usados ​em uma API, mas podem ser
um problema real quando esses URIs são expostos diretamente aos usuários em
um cliente de navegador da web. Se você usar URIs de capacidade em sua API, os
clientes baseados em navegador precisarão traduzir de alguma forma os URIs
usados ​na API em URIs usados ​para navegar na IU. Uma abordagem natural seria
usar URIs de capacidade para isso também, reutilizando os tokens dos URIs da
API. Nesta seção, você verá como fazer isso com segurança.

Uma abordagem para esse problema é colocar o token em uma parte do URI que
normalmente não é enviada ao servidor ou incluída nos Referer cabeçalhos. A
solução original foi desenvolvida para o servidor Waterken que usava URIs de ca-
pacidade extensivamente, sob o nome web-keys(
http://waterken.sourceforge.net/web-key/ ). Em uma chave da web, o token que
não pode ser adivinhado é armazenado no componente de fragmento do URI; ou
seja, o bit após um caractere # no final do URI. O fragmento é normalmente usado
para saltar para um local específico dentro de um documento maior e tem a van-
tagem de nunca ser enviado ao servidor por clientes e nunca incluído em um Re-
ferer cabeçalho ou window.referrer campoem JavaScript e, portanto, é menos
suscetível a vazamentos. A desvantagem é que, como o servidor não vê o token, o
cliente deve extraí-lo do URI e enviá-lo ao servidor por outros meios.

No Waterken, que foi projetado para aplicativos da web, quando um usuário cli-
cava em um link de chave da web no navegador, ele carregava um modelo sim-
ples de página JavaScript. O JavaScript então extraiu o token do fragmento de con-
sulta (usando a window.location.hash variável) e fez uma segunda chamada
para o servidor web, passando o token em um parâmetro de consulta. O fluxo é
mostrado na figura 9.3.
Figura 9.3 No design de chave da Web Waterken para URIs de capacidade, o token
é armazenado no fragmento do URI, que nunca é enviado ao servidor. Quando
um navegador carrega esse URI, ele carrega inicialmente uma página JavaScript
estática que extrai o token do fragmento e o usa para fazer solicitações Ajax à API.
O modelo JavaScript pode ser armazenado em cache pelo navegador, evitando a
ida e volta extra para solicitações subsequentes.

Como o próprio modelo JavaScript não contém dados confidenciais e é o mesmo


para todos os URIs, ele pode ser servido com cabeçalhos de controle de cache de
longa duração e, portanto, depois que o navegador o carregou uma vez, ele pode
ser reutilizado para todos os URIs de capacidade subsequentes sem um chamada
extra para o servidor, conforme mostrado na metade inferior da figura 9.3. Essa
abordagem funciona bem com aplicativos de página única(SPAs) porque eles ge-
ralmente já usam o fragmento dessa maneira para permitir a navegação no apli-
cativo sem causar o recarregamento da página enquanto ainda preenchem o his-
tórico do navegador.

AVISO Embora o componente de fragmento não seja enviado ao servidor, ele


será incluído se ocorrer um redirecionamento. Se seu aplicativo precisar redireci-
onar para outro site, você sempre deve incluir explicitamente um componente de
fragmento no URI de redirecionamento para evitar o vazamento acidental de to-
kens dessa maneira.
A Listagem 9.8 mostra como analisar e carregar um URI de capacidade neste for-
mato de um cliente API JavaScript. Ele primeiro analisa o URI usando a URL clas-
see extrai o token do hash campo, que contém o componente do fragmento. Este
campo inclui o caractere literal “#” no início, então
use hash.substring(1 ) para remover isso. Em seguida, você deve remover
esse componente do URI para enviar à API e, em vez disso, adicionar o token de
volta como um parâmetro de consulta. Isso garante que
o CapabilityController verá o token no local esperado. Navegue até
src/main/resources/public e crie um novo arquivo chamado resource.js com o
conteúdo dolistagem.

OBSERVAÇÃO Este código pressupõe que as páginas da interface do usuário


correspondem diretamente aos URIs em sua API. Para um SPA, isso não será ver-
dade e há (por definição) uma única página de interface do usuário que lida com
todas as solicitações. Nesse caso, você precisará codificar o caminho da API e o to-
ken no fragmento juntos em um formato como #/spaces/1/
messages&tok=abc123 . Estruturas modernas, como Vue ou React, podem usar a
API de histórico do HTML 5 para fazer com que os URIs do SPA pareçam URIs nor-
mais (sem o fragmento). Ao usar essas estruturas, você deve garantir que o token
esteja no componente de fragmento real; caso contrário, os benefícios de segu-
rança serão perdidos.

Listagem 9.8 Carregando um URI de capacidade do JavaScript


function getCap(url, callback) {
let capUrl = new URL(url); ❶
let token = capUrl.hash.substring(1); ❶
capUrl.hash = ''; ❷
capUrl.search = '?access_token=' + token; ❸

return busca(capUrl.href) ❹
.then(resposta => resposta.json())
.then(retorno de chamada)
.catch(err => console.error('Erro: ', err));
}

❶ Analise a URL e extraia o token do componente fragmento (hash).

❷ Apague o fragmento.

❸ Adicione o token aos parâmetros de consulta URI.

❹ Agora busque o URI para chamar a API com o token.

9.2.5 Combinando recursos com identidade

Tudoas chamadas para a API Natter agora são autorizadas apenas usando tokens
de capacidade, que têm como escopo um recurso individual e não estão vincula-
dos a nenhum usuário. Como você viu com o exemplo do navegador de mensa-
gens simples na última seção, você pode até mesmo codificar URIs de capacidade
somente leitura em uma página da Web para permitir a navegação completa-
mente anônima de mensagens. Algumas chamadas de API ainda exigem autenti-
cação do usuário, como criar um novo espaço ou postar uma mensagem. O mo-
tivo é que essas ações da API envolvem declarações sobre quem é o usuário, por-
tanto, você ainda precisa autenticar essas declarações para garantir que sejam ge-
nuínas, por motivos de responsabilidade e não por autorização. Caso contrário,
qualquer pessoa com um URI de capacidade para postar mensagens em um es-
paço poderia usá-lo para representar qualquer outro usuário.

questionário

5. Qual das opções a seguir é o principal risco de segurança ao incluir um token


de capacidade no componente de fragmento de um URI?
1. Fragmentos de URI não são RESTful.
2. O token aleatório faz com que o URI pareça feio.
3. O fragmento pode ter vazado nos logs do servidor e no Referer cabeçalho
HTTP.
4. Se o servidor executar um redirecionamento, o fragmento será copiado para
o novo URI.
5. O fragmento pode já estar sendo usado para outros dados, fazendo com que
seja sobrescrito.

A resposta está no final do capítulo.


Você também pode querer identificar positivamente os usuários por outros moti-
vos, como para garantir que você tenha um registro de auditoria preciso de quem
fez o quê. Como um URI de capacidade pode ser compartilhado por vários usuá-
rios, é útil identificar esses usuários independentemente de como suas solicita-
ções são autorizadas. Finalmente, você pode querer aplicar alguns controles de
acesso baseados em identidade sobre o acesso baseado em capacidade. Por exem-
plo, no Google Docs ( https://docs.google.com ), você pode compartilhar documen-
tos usando URIs de recursos, mas também pode restringir esse compartilhamento
apenas a usuários que tenham uma conta no domínio de sua empresa. Para aces-
sar o documento, o usuário precisa ter o link e estar conectado a uma conta do
Google vinculada à mesma empresa.

Existem algumas maneiras de comunicar a identidade em um sistema baseado


em capacidade:
Você pode associar um nome de usuário e outras declarações de identidade a
cada token de recurso. As permissões no token ainda são o que concede acesso,
mas o token também autentica declarações de identidade sobre o usuário que
podem ser usadas para registro de auditoria ou verificações de acesso adicio-
nais. A principal desvantagem dessa abordagem é que compartilhar um URI de
recurso permite que o destinatário represente você sempre que fizer chamadas
para a API usando esse recurso. No entanto, essa abordagem pode ser útil ao
gerar recursos de curta duração destinados apenas a um único usuário. O link
enviado em um e-mail de redefinição de senha pode ser visto como esse tipo de
URI de recurso porque fornece um recurso por tempo limitado para redefinir a
senha vinculada à conta de um usuário.
Você pode usar um mecanismo de autenticação tradicional, como um cookie de
sessão, para identificar o usuário, além de exigir um token de capacidade, con-
forme mostrado na Figura 9.4. O cookie não seria mais usado para autorizar
chamadas de API, mas sim para identificar o usuário para log de auditoria ou
para verificações adicionais. Como o cookie não é mais usado para controle de
acesso, ele é menos sensível e, portanto, pode ser um cookie persistente de
longa duração, reduzindo a necessidade de login frequente do usuário.
Figura 9.4 Ao combinar URIs de capacidade com um mecanismo de autenticação
tradicional, como cookies, a API pode impor acesso usando recursos enquanto au-
tentica reivindicações de identidade usando o cookie. O mesmo URI de capaci-
dade pode ser compartilhado entre os usuários, mas a API ainda é capaz de iden-
tificar positivamente cada um deles.

Ao desenvolver uma API REST, a segunda opção geralmente é atraente porque


você pode reutilizar tecnologias tradicionais de autenticação baseadas em coo-
kies, como um provedor de identidade OpenID Connect centralizado (capítulo 7).
Essa é a abordagem adotada na API do Natter, em que as permissões para uma
chamada de API vêm de um URI de capacidade, mas algumas chamadas de API
precisam de autenticação de usuário adicional usando um mecanismo tradicio-
nal, como autenticação básica HTTP ou um token ou cookie de autenticação.
Para voltar a usar cookies para autenticação, abra o arquivo Main.java em seu
editor e localize as linhas que criam o TokenController objeto. Mude a to-
kenStore variávelusar o CookieTokenStore que você desenvolveu lá atrásCapí-
tulo 4:

SecureTokenStore tokenStore = new CookieTokenStore();


var tokenController = new TokenController(tokenStore);

9.2.6 URIs de capacidade de proteção

Vocêpode se perguntar se você pode acabar com o token anti-CSRF agora que está
usando recursos para controle de acesso, que são imunes ao CSRF. Isso seria um
erro, porque um invasor com capacidade genuína de acessar a API ainda pode
usar um ataque CSRF para fazer com que suas solicitações pareçam vir de um
usuário diferente. A autoridade para acessar a API vem do URI de capacidade do
invasor, mas a identidade do usuário vem do cookie. Se você mantiver o token
anti-CSRF existente, os clientes deverão enviar três credenciais a cada solicitação:

O cookie que identifica o usuário


O token anti-CSRF
O token de capacidade que autoriza a solicitação específica

Isso é um pouco excessivo. Ao mesmo tempo, os tokens de capacidade são vulne-


ráveis ​a serem roubados. Por exemplo, se um URI de capacidade destinado a um
moderador for roubado, ele poderá ser usado por qualquer pessoa para excluir
mensagens. Você pode resolver ambos os problemas vinculando os tokens de ca-
pacidade a um usuário autenticado e impedindo que sejam usados ​por qualquer
outra pessoa. Isso remove um dos benefícios dos URIs de capacidade - que são fá-
ceis de compartilhar - mas melhora a segurança geral:

Se um token de capacidade for roubado, ele não poderá ser usado sem um coo-
kie de login válido para o usuário. Se o cookie for definido com os sinalizadores
HttpOnly e Secure, será muito mais difícil roubá-lo.
Agora você pode remover o token anti-CSRF separado porque cada URI de capa-
cidade atua efetivamente como um token anti-CSRF. O cookie não pode ser
usado sem o recurso e o recurso não pode ser usado sem o cookie.

A Listagem 9.9 mostra como associar um token de capacidade a um usuário au-


tenticado preenchendo o username atributodo token que você deixou em branco
anteriormente. Abra o arquivo CapabilityController.java em seu editor e adicione
as linhas de código realçadas.

Listagem 9.9 Vinculando um recurso a um usuário

public URI createUri(Request request, String path, String perms,


Expiração da duraçãoDuração) {
var assunto = (String) request.attribute("assunto"); ❶
var token = new Token(now().plus(expiryDuration), subject); ❷
token.attributes.put("caminho", caminho);
token.attributes.put("perms", perms);
var tokenId = tokenStore.create(pedido, token);

var uri = URI.create(request.uri());


return uri.resolve(caminho + "?access_token=" + tokenId);
}

❶ Procure o usuário autenticado.

❷ Associe o recurso ao usuário.

Você pode então ajustar o lookupPermissions métodono mesmo arquivo para


não retornar permissões se o nome de usuário associado ao token de capacidade
não corresponder ao usuário autenticado, conforme mostrado na Listagem 9.10.
Isso garante que o recurso não possa ser usado sem uma sessão associada para o
usuário e que o cookie de sessão só possa ser usado quando corresponder ao to-
ken de recurso, evitando também ataques CSRF de maneira eficaz.

Listagem 9.10 Verificando o usuário

public void lookupPermissions(Solicitação de solicitação, Resposta de resposta)


var tokenId = request.queryParams("access_token");
if (tokenId == null) { return; }

tokenStore.read(request, tokenId).ifPresent(token -> {


if (!Objects.equals(token.username, ❶
request.attribute("assunto"))) { ❶
return; ❶
} ❶

var tokenPath = token.attributes.get("caminho");


if (Objects.equals(tokenPath, request.pathInfo())) {
request.attribute("perms",
token.attributes.get("perms"));
}
});
}

❶ Se o usuário autenticado não corresponder à capacidade, ele não retornará ne-


nhuma permissão.

Agora você pode excluir o código que verifica o token anti-CSRF no CookieTo-
kenStore se desejar e contar com o código de capacidade para proteção contra
CSRF. Consulte o capítulo 4 para ver como era a versão original antes da proteção
CSRF ser adicionada. Você também precisará ajustar o
TokenController.validateToken métodopara não rejeitar uma solicitação
que não tenha um token anti-CSRF. Se você ficar preso, confira o capítulo 09 no fi-
nal do repositório GitHub que acompanha o livro, que possui todas as alterações
necessárias.
COMPARTILHAMENTO DE ACESSO

PorqueURIs de capacidade agora estão vinculados a usuários individuais, você


precisa de um novo mecanismo para compartilhar o acesso a espaços sociais e
mensagens individuais. A Listagem 9.11 mostra uma nova operação para permitir
que um usuário troque um de seus próprios URIs de capacidade por um para um
usuário diferente, com uma opção para especificar um conjunto reduzido de per-
missões. O método lê um URI de capacidade da entrada e procura o token associ-
ado. Se o URI corresponder ao token e as permissões solicitadas forem um sub-
conjunto das permissões concedidas pelo URI de recurso original, o método criará
um novo token de recurso com as novas permissões e usuário e retornará o URI
solicitado. Esse novo URI pode ser compartilhado com segurança com o usuário
pretendido. Abra o arquivo CapabilityController.java e inclua o novo método.

Listagem 9.11 URIs de capacidade de compartilhamento

public JSONObject share(solicitação de solicitação, resposta de resposta) {


var json = new JSONObject(request.body());

var capUri = URI.create(json.getString("uri")); ❶


var caminho = capUri.getPath(); ❶
var query = capUri.getQuery(); ❶
var tokenId = query.substring(query.indexOf('=') + 1); ❶

var token = tokenStore.read(request, tokenId).orElseThrow(); ❷


if (!Objects.equals(token.attributes.get("caminho"), caminho)) { ❷
throw new IllegalArgumentException("caminho incorreto"); ❷
} ❷

var tokenPerms = token.attributes.get("perms"); ❸


var perms = json.optString("perms", tokenPerms); ❸
if (!tokenPerms.contains(perms)) { ❸
Spark.halt(403); ❸
} ❸
var usuário = json.getString("usuário");
var newToken = new Token(token.expiry, user); ❹
newToken.attributes.put("caminho", caminho); ❹
newToken.attributes.put("perms", perms); ❹
var newTokenId = tokenStore.create(request, newToken); ❹

var uri = URI.create(request.uri()); ❺


var newCapUri = uri.resolve(path + "?access_token=" ❺
+ newTokenId); ❺
return new JSONObject() ❺
.put("uri", newCapUri); ❺
}

❶ Analise o URI de capacidade original e extraia o token.

❷ Procure o token e verifique se ele corresponde ao URI.


❸ Verifique se as permissões solicitadas são um subconjunto das permissões de
token.

❹ Crie e armazene o novo token de capacidade.

❺ Retorne o URI do recurso solicitado.

Agora você pode adicionar uma nova rota à Main classepara expor esta nova ope-
ração. Abra o arquivo Main.java e adicione a seguinte linha ao main método:

post("/capabilities", capController::share);

Agora você pode chamar esse ponto de extremidade para trocar um URI de capa-
cidade privilegiada, como o URI de mensagens-rwd retornado da criação de um
espaço, como no exemplo a seguir:

curl -H 'Tipo de conteúdo: aplicativo/json' \


-d '{"uri":"/spaces/1/messages?access_token=
➥ 0ed8-IohfPQUX486d0kr03W8Ec8", "user":"demo2", "perms":"r"}' \
https://localhost:4567/share
{"uri":"/spaces/1/messages?access_token=
➥ 1YQqZdNAIce5AB_Z8J7ClMrnx68"}

O novo URI de capacidade na resposta só pode ser usado pelo usuário demo2 e
fornece apenas permissão de leitura no espaço. Você pode usar esse recurso para
criar compartilhamento de recursos para suas APIs. Por exemplo, se um usuário
compartilhar diretamente um URI de capacidade próprio com outro usuário, em
vez de negar o acesso completamente, você pode permitir que ele solicite acesso.
Isso é o que acontece no Google Docs se você seguir um link para um documento
ao qual não tem acesso. O proprietário do documento pode então aprovar o
acesso. No Google Docs, isso é feito adicionando uma entrada a uma lista de con-
trole de acesso (capítulo 3) associada a cada documento, mas com recursos, o pro-
prietário pode gerar um URI de recurso que é enviado por e-mailparaadestinatá-
rio.

9.3 Macaroons: Tokens com ressalvas

Capacidadespermitem que os usuários compartilhem facilmente o acesso refi-


nado aos seus recursos com outros usuários. Se um usuário do Natter quiser com-
partilhar uma de suas mensagens com alguém que não tenha uma conta do Nat-
ter, ele poderá fazer isso facilmente criando um URI de capacidade somente lei-
tura para essa mensagem específica. O outro usuário poderá ler apenas aquela
mensagem e não terá acesso a nenhuma outra mensagem ou a capacidade de pos-
tar mensagens.

Às vezes, a granularidade dos URIs de capacidade não corresponde à forma como


os usuários desejam compartilhar recursos. Por exemplo, suponha que você de-
seja compartilhar acesso somente leitura a um instantâneo das conversas desde
ontem em um espaço social. É improvável que a API sempre forneça um URI de
capacidade que corresponda exatamente aos desejos do usuário; a createSpa-
ce action já retorna quatro URIs, e nenhum deles se encaixa perfeitamente no
projeto.

Os macaroons fornecem uma solução para esse problema, permitindo que qual-
quer pessoa acrescente advertências a um recurso que restringe como ele pode
ser usado. Os macarons foram inventados por uma equipe de pesquisadores aca-
dêmicos e do Google em um artigo publicado em 2014 ( https://ai.google/
research/pubs/pub41892 ).

DEFINIÇÃO Um biscoito é um tipo de token criptográfico que pode ser usado


para representar recursos e outras concessões de autorização. Qualquer pessoa
pode acrescentar novas advertências a um biscoito que restringe como ele pode
ser usado.

Para abordar nosso exemplo, o usuário poderia anexar as seguintes ressalvas à


sua capacidade de criar uma nova capacidade que permita apenas o acesso de lei-
tura às mensagens desde a hora do almoço de ontem:

método = GET
desde >= 2019-10-12T12:00:00Z

Ao contrário do método de compartilhamento que você adicionou na seção 9.2.6,


as ressalvas do macaroon podem expressar condições gerais como essas. O outro
benefício dos macaroons é que qualquer pessoa pode anexar uma advertência a
um macaroon usando uma biblioteca de macaroon, sem precisar chamar um ter-
minal de API ou ter acesso a nenhuma chave secreta. Depois que a advertência foi
adicionada, ela não pode ser removida.

Macaroons usam tags HMAC-SHA256 para proteger a integridade do token e


quaisquer ressalvas, assim como o HmacTokenStore você desenvolveu no capí-
tulo 5. Para permitir que qualquer pessoa acrescente ressalvas a um macaroon,
mesmo que não tenha a chave, os macaroons usam uma propriedade interessante
do HMAC: a etiqueta de autenticação de saída do HMAC pode ser usada como uma
chave para assinar um nova mensagem com HMAC. Para anexar um aviso a um
biscoito, você usa a marca de autenticação antiga como a chave para calcular uma
nova marca HMAC-SHA256 sobre o aviso, conforme mostrado na figura 9.5. Em
seguida, jogue fora a etiqueta de autenticação antiga e acrescente a ressalva e a
nova etiqueta ao biscoito. Como é inviável reverter o HMAC para recuperar a tag
antiga, ninguém pode remover as advertências que foram adicionadas, a menos
que tenha a chave original.
Figura 9.5 Para anexar uma nova advertência a um biscoito, você usa a tag HMAC
antiga como a chave para autenticar a nova advertência. Em seguida, você des-
carta a tag antiga e acrescenta a nova advertência e tag. Como ninguém pode re-
verter o HMAC para calcular a tag antiga, eles não podem remover a ressalva.

AVISO Como qualquer um pode adicionar uma advertência a um biscoito, é im-


portante que eles sejam usados ​apenas para restringir como um token é usado.
Você nunca deve confiar em nenhuma reivindicação em uma advertência ou con-
ceder acesso adicional com base em seu conteúdo.
Quando o macaroon é apresentado de volta à API, ele pode usar a chave HMAC
original para reconstruir a tag original e todas as tags de advertência e verificar
se aparece com o mesmo valor de assinatura no final da cadeia de advertências. A
Listagem 9.12 mostra um exemplo de como verificar uma cadeia HMAC como
aquela usada pelos macaroons.

Primeiro inicialize um javax.crypto.Mac objetocom a chave de autenticação


da API (consulte o capítulo 5 para saber como gerar isso) e, em seguida, calcule
uma tag inicial sobre o identificador exclusivo do macaroon. Em seguida, você
percorre cada advertência na cadeia e calcula uma nova tag HMAC sobre a adver-
tência, usando a tag antiga como chave. 5 Por fim, você compara a tag calculada
com a tag fornecida com o biscoito usando uma função de igualdade de tempo
constante. A Listagem 9.14 é apenas para demonstrar como funciona; você usará
uma biblioteca real de macarons na API do Natter para não precisar implementar
esse método.

Listagem 9.12 Verificando a cadeia HMAC

private boolean Verify(String id, List<String> advertências, tag byte[])


lança exceção {
var hmac = Mac.getInstance("HmacSHA256"); ❶
hmac.init(macKey); ❶
var computado = hmac.doFinal(id.getBytes(UTF_8)); ❷
for (var caveat : caveats) { ❸
hmac.init(new SecretKeySpec(computed, "HmacSHA256")); ❸
computado = hmac.doFinal(caveat.getBytes(UTF_8)); ❸
}
return MessageDigest.isEqual(tag, computado); ❹
}

❶ Inicialize o HMAC-SHA256 com a chave de autenticação.

❷ Calcule uma tag inicial sobre o identificador do macaroon.

❸ Calcule uma nova tag para cada advertência usando a tag antiga como chave.

❹ Compare os tags com uma função de igualdade de tempo constante.

Após a verificação da tag HMAC, a API precisa verificar se as ressalvas foram


atendidas. Não há um conjunto padrão de advertências que as APIs suportem,
portanto, como os escopos OAuth2, cabe ao designer da API decidir o que oferecer
suporte. Existem duas grandes categorias de ressalvas suportadas pelas bibliote-
cas de macarons:

Advertências própriassão restrições que podem ser facilmente verificadas pela


API no ponto de uso, como restringir os horários do dia em que o token pode
ser utilizado. As advertências de terceiros são discutidas com mais detalhes na
seção 9.3.3.
Advertências de terceiros são restrições que exigem que o cliente obtenha uma
prova de um serviço de terceiros, como prova de que o usuário é funcionário
de uma determinada empresa ou que tem mais de 18 anos. As advertências de
terceiros são discutidas na seção 9.3 .4.

9.3.1 Advertências contextuais

UMAA vantagem significativa dos macaroons em relação a outras formas de to-


ken é que eles permitem que o cliente anexe advertências contextuais logo antes
de o macaroon ser usado. Por exemplo, um cliente que está prestes a enviar um
biscoito para uma API por meio de um canal de comunicação não confiável pode
anexar uma advertência primária limitando-a a ser válida apenas para solicita-
ções HTTP PUT para esse URI específico pelos próximos 5 segundos. Dessa forma,
se o biscoito for roubado, o dano será limitado porque o invasor só pode usar o to-
ken em circunstâncias muito restritas. Como o cliente pode manter uma cópia do
biscoito original irrestrito, sua própria capacidade de usar o token não é limitada
da mesma maneira.

DEFINIÇÃO Uma advertência contextual é uma advertência adicionada por um


cliente antes do uso. As advertências contextuais permitem que a autoridade de
um token seja restrita antes de enviá-lo por um canal inseguro ou para uma API
não confiável, limitando o dano que pode ocorrer se o token for roubado.

A capacidade de adicionar ressalvas contextuais torna os macaroons um dos de-


senvolvimentos recentes mais importantes na segurança da API. Macaroons po-
dem ser usados ​com qualquer autenticação baseada em token e até tokens de
acesso OAuth2 se o seu servidor de autorização os suportar. 6 Por outro lado, não
há especificação formal de macaroons e a conscientização e adoção do formato
ainda é bastante limitada, portanto, eles não são tão amplamente apoiados
quanto os JWTs(Capítulo 6).

9.3.2 Uma loja de fichas de macaroon

Parausar macaroons na API do Natter, você pode usar a biblioteca jmacaroons de


código aberto ( https://github.com/nitram509/jmacaroons ). Abra o arquivo
pom.xml em seu editor e adicione as seguintes linhas à seção de dependências:

<dependência>
<groupId>com.github.nitram509</groupId>
<artifactId>jmacaroons</artifactId>
<version>0.4.1</version>
</dependência>

Agora você pode criar uma nova implementação de armazenamento de token


usando macaroons, conforme mostrado na Listagem 9.13. Para criar um maca-
roon, primeiro você usará outro TokenStore implementação para gerar o identi-
ficador do macaroon. Você pode usar qualquer uma das lojas existentes, mas para
manter os tokens compactos, você usará o DatabaseTokenStore nestes exem-
plos. Você também pode usar o JsonTokenStore , caso em que a etiqueta HMAC
do macaroon também o protege contra adulteração.
Você então cria o macaroon usando o MacaroonsBuilder.create() método,
passando o identificador e a chave HMAC. Uma peculiaridade estranha da API
macaroon significa que você tem que passar os bytes brutos da chave
usando macKey.getEncoded () . Você também pode dar uma dica opcional de
onde o macaroon deve ser usado. Como você os usará com URIs de capacidade
que já incluem o local completo, deixe esse campo em branco para economizar es-
paço. Você pode então usar o macaroon.serialize() métodopara converter o
biscoito em um formato de string base64 seguro para URL. No mesmo projeto de
API do Natter que você está usando até agora, navegue até
src/main/java/com/manning/apisecurityinaction/token e crie um novo arquivo
chamado MacaroonTokenStore.java. Copie o conteúdo da Listagem 9.13 para o ar-
quivo e salve-o.

AVISO A dica de localização não está incluída na marca de autenticação e des-


tina-se apenas a ser uma dica para o cliente. Seu valor não deve ser confiável por-
que pode ser adulterado.

Listagem 9.13 A MacaroonTokenStore

pacote com.manning.apisecurityinaction.token;

importar java.security.Key;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Opcional;
import com.github.nitram509.jmacaroons.*;
import com.github.nitram509.jmacaroons.verifier.*;
import spark.Request;

public class MacaroonTokenStore implementa SecureTokenStore {


delegado TokenStore final privado;
chave final privada macKey;
private MacaroonTokenStore(TokenStore delegado, Key macKey) {
this.delegate = delegado;
this.macKey = macKey;
}

@Sobrepor
public String create(Solicitação de solicitação, Token token) {
var identificador = delegado.create(solicitação, token); ❶
var macaroon = MacaroonsBuilder.create("", ❷
macKey.getEncoded(), identificador); ❷
return macaroon.serialize(); ❸
}
}

❶ Use outro armazenamento de token para criar um identificador exclusivo para


este biscoito.

❷ Crie o macaroon com uma dica de localização, o identificador e a chave de


autenticação.
❸ Retorne a forma de string segura de URL serializada do biscoito.

Como o HmacTokenStore do capítulo 4, o armazenamento de tokens de maca-


roon fornece apenas autenticação de tokens e não confidencialidade, a menos que
o armazenamento subjacente já forneça isso. Assim como você fez no capítulo 5,
você pode criar dois métodos de fábrica estáticos que retornam um armazena-
mento digitado corretamente, dependendo do armazenamento de token
subjacente:

Se o armazenamento de token subjacente for um ConfidentialTokenStore ,


então ele retorna um SecureTokenStore porque o armazenamento resultante
fornece confidencialidade e autenticidade de tokens.
Caso contrário, ele retorna um AuthenticatedTokenStore para deixar claro
que a confidencialidade não é garantida.

Esses métodos de fábrica são mostrados na listagem 9.14 e são muito semelhantes
aos que você criou no capítulo 5, então abra o arquivo MacaroonTokenStore.java
novamente e adicione esses novos métodos.

Listagem 9.14 Métodos de fábrica

public static SecureTokenStore wrap( ❶


ConfidentialTokenStore tokenStore, Key macKey) { ❶
return new MacaroonTokenStore(tokenStore, macKey); ❶
} ❶
public static AuthenticatedTokenStore wrap( ❷
TokenStore tokenStore, Key macKey) { ❷
return new MacaroonTokenStore(tokenStore, macKey); ❷
} ❷

❶ Se o armazenamento subjacente fornecer confidencialidade dos dados do token,


retorne um SecureTokenStore.

❷ Caso contrário, retorne um AuthenticatedTokenStore.

Para verificar um biscoito, desserialize e valide o biscoito usando um Macaro-


onsVerifier , que verificará a tag HMAC e todas as ressalvas. Se o biscoito for
válido, você poderá procurar o identificador no armazenamento de token dele-
gado. Para revogar um biscoito, basta desserializar e revogar o identificador. Na
maioria dos casos, você não deve verificar as advertências no token quando ele
está sendo revogado, porque se alguém tiver obtido acesso ao seu token, a coisa
menos maliciosa que poderá fazer com ele é revogá-lo! No entanto, em alguns ca-
sos, a revogação maliciosa pode ser uma ameaça real; nesse caso, você pode veri-
ficar as advertências para reduzir o risco de isso ocorrer. A Listagem 9.15 mostra
as operações para ler e revogar um token de biscoito. Abra o arquivo Macaroon-
TokenStore .java novamente e inclua os novos métodos.

Listagem 9.15 Lendo um token de biscoito


@Sobrepor
public Opcional<Token> read(Request request, String tokenId) {
var macaroon = MacaroonsBuilder.deserialize(tokenId); ❶
var verifier = new MacaroonsVerifier(macaroon); ❶
if (verifier.isValid(macKey.getEncoded())) { ❶
return delegate.read(request, macaroon.identifier); ❷
}
return Opcional.vazio();
}

@Sobrepor
public void revoke(Solicitação de solicitação, String tokenId) {
var macaroon = MacaroonsBuilder.deserialize(tokenId);
delegado.revoke(pedido, macaroon.identificador); ❸
}

❶ Desserialize e valide a assinatura e as ressalvas do macaroon.

❷ Se o biscoito for válido, procure o identificador no repositório de tokens


delegados.

❸ Para revogar um biscoito, revogue o identificador no armazenamento delegado.


LIGANDO

Agora você pode ligar o CapabilityController para usar o novo armazena-


mento de token para tokens de capacidade. Abra o arquivo Main.java em seu edi-
tor e localize as linhas que constroem o arquivo CapabilityController . Atua-
lize o arquivo para usar o MacaroonTokenStore em vez de. Você pode precisar
primeiro mover o código que lê o macKey do armazenamento de chaves (consulte
o capítulo 6) mais adiante no arquivo. O código deve ter a seguinte aparência,
com a nova parte destacada em negrito:

var keyPassword = System.getProperty("keystore.password",


"alterar").toCharArray();
var keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("keystore.p12"),
chaveSenha);
var macKey = keyStore.getKey("hmac-key", keyPassword);
var encKey = keyStore.getKey("aes-key", keyPassword);

var capController = new CapabilityController(


MacaroonTokenStore.wrap(
new DatabaseTokenStore(banco de dados), macKey));

Se agora você usar a API para criar um novo espaço, verá os tokens de biscoito
sendo usados ​nos URIs de capacidade retornados da chamada de API. Você pode
copiar e colar esses tokens no depurador em http://macaroons.io para ver as par-
tes componentes.

CUIDADO Você não deve colar tokens de um sistema de produção em nenhum


site. No momento da redação deste artigo, o macaroons.io nem sequer oferece su-
porte a SSL.

Conforme escrito atualmente, o armazenamento de tokens de macaroon funciona


de maneira muito semelhante ao armazenamento de tokens HMAC existente. Nas
próximas seções, você implementará suporte para ressalvas para aproveitar ao
máximo o novo tokenformato.

9.3.3 Advertências de primeira parte

oas advertências mais simples são advertências próprias, que podem ser verifica-
das pela API puramente com base na solicitação da API e no ambiente atual. Essas
advertências são representadas como strings e não há um formato padrão. A
única advertência de primeira parte comumente implementada é definir um
tempo de expiração para o biscoito usando a sintaxe:

hora < 2019-10-12T12:00:00Z

Você pode pensar nessa ressalva como sendo a reivindicação de expiração (exp)
em um JWT (capítulo 6). Os tokens emitidos pela API Natter já possuem um tempo
de expiração, mas um cliente pode querer criar uma cópia de seu token com um
tempo de expiração mais restrito, conforme discutido na seção 9.3.1 sobre adver-
tências contextuais.

Para verificar quaisquer advertências de tempo de expiração, você pode usar


um TimestampCaveatVerifier que vem com a biblioteca jmacaroons conforme
mostrado na Listagem 9.16. A biblioteca de macarons tentará corresponder cada
advertência a um verificador capaz de satisfazê-la. Nesse caso, o verificador veri-
fica se a hora atual é anterior à hora de expiração especificada na advertência. Se
a verificação falhar ou se a biblioteca não conseguir encontrar um verificador
que corresponda a uma advertência, o biscoito será rejeitado. Isso significa que a
API deve registrar explicitamente os verificadores para todos os tipos de adver-
tências que ela suporta. Tentar adicionar uma ressalva que a API não suporta im-
pedirá que o biscoito seja usado. Abra o arquivo MacaroonTokenStore.java em
seu editor novamente e atualize o read métodopara verificar as advertências de
expiração, conforme mostrado na listagem.

Listagem 9.16 Verificando o timestamp de expiração

@Sobrepor
public Opcional<Token> read(Request request, String tokenId) {
var macaroon = MacaroonsBuilder.deserialize(tokenId);
var verificador = new MacaroonsVerifier(macaroon);
verifier.satisfyGeneral(new TimestampCaveatVerifier()); ❶
if (verifier.isValid(macKey.getEncoded())) {
return delegado.read(pedido, macaroon.identificador);
}
return Opcional.vazio();
}

❶ Adicione um TimestampCaveatVerifier para satisfazer a advertência de expiração.

Você também pode adicionar seus próprios verificadores de advertência usando


dois métodos. O mais simples é o satisfyExact método, que satisfará as adver-
tências que correspondem exatamente à cadeia de caracteres fornecida. Por
exemplo, você pode permitir que um cliente restrinja um macaroon a um único
tipo de método HTTP adicionando a linha:

verifier.satisfyExact("método = " + request.requestMethod());

ao read método. Isso garante que um macaroon com a ressalva method =


GET só possa ser usado em solicitações HTTP GET, tornando-o efetivamente so-
mente leitura. Adicione essa linha ao read métodoagora.

Uma abordagem mais geral é implementar a GeneralCaveatVerifier inter-


face, que permite implementar condições arbitrárias para atender a uma adver-
tência. A Listagem 9.17 mostra um exemplo de verificador para verificar se o
since parâmetro de consulta para o findMessages métodoé depois de um certo
tempo, permitindo que você restrinja um cliente a visualizar apenas as mensa-
gens desde ontem. A classe analisa a ressalva e o parâmetro como Instant obje-
tos e, em seguida, verifica se a solicitação não está tentando ler mensagens mais
antigas que a ressalva usando o isAfter método. Abra o arquivo
MacaroonTokenStore.java novamente e inclua o conteúdo da listagem 9.17 como
uma classe interna.

Listagem 9.17 Um verificador de advertência personalizado

classe estática privada DesdeVerifier implementa GeneralCaveatVerifier {


solicitação de solicitação final privada;

private SinceVerifier(solicitação de solicitação) {


this.pedido = pedido;
}

@Sobrepor
public boolean VerifyCaveat(String advertência) {
if (caveat.startsWith("since > ")) { ❶
var minSince = Instant.parse(caveat.substring(8)); ❶

var reqSince = Instant.now().minus(1, ChronoUnit.DAYS); ❷


if (request.queryParams("desde") != null) { ❷
reqSince = Instant.parse(request.queryParams("desde")); ❷
}
return reqSince.isAfter(minSince); ❸
}
retorna falso; ❹
}
}

❶ Verifique as correspondências de advertência e analise a restrição.

❷ Determine o valor do parâmetro “since” na solicitação.

❸ Satisfaça a ressalva se a solicitação for posterior à restrição de mensagem mais


antiga.

❹ Rejeite todas as outras ressalvas.

Você pode então adicionar o novo verificador ao read métodoadicionando a se-


guinte linha

verifier.satisfyGeneral(new SinceVerifier(request));

ao lado das linhas que adicionam os outros verificadores de advertência. O código


finalizado para construir o verificador deve ter a seguinte aparência:

var verificador = new MacaroonsVerifier(macaroon);


verifier.satisfyGeneral(new TimestampCaveatVerifier());
verifier.satisfyExact("método = " + request.requestMethod());
verifier.satisfyGeneral(new SinceVerifier(request));
ADICIONANDO ADVERTÊNCIAS

Para adicionar uma advertência a um biscoito, você pode analisá-lo usando a Ma-
caroonsBuilder classee depois use o add_first_party_caveat métodoacres-
centar ressalvas, conforme a listagem 9.18. A listagem é um programa de linha de
comando autônomo para adicionar ressalvas a um biscoito. Ele primeiro analisa o
macaroon, que é passado como o primeiro argumento para o programa e, em se-
guida, percorre todos os argumentos restantes, tratando-os como advertências.
Por fim, ele imprime o macaroon resultante como uma string novamente. Nave-
gue até a pasta src/main/java/com/manning/apisecurityinaction e crie um novo
arquivo chamado CaveatAppender.java e digite o conteúdo da listagem.

Listagem 9.18 Anexando advertências

pacote com.manning.apisecurityinaction;

import com.github.nitram509.jmacaroons.MacaroonsBuilder;
import static com.github.nitram509.jmacaroons.MacaroonsBuilder.deserialize;

public class CaveatAppender {


public static void main(String... args) {
var builder = new MacaroonsBuilder(deserialize(args[0])); ❶
for (int i = 1; i < args.length; ++i) { ❷
var caveat = args[i]; ❷
builder.add_first_party_caveat(caveat); ❷
} ❷
System.out.println(builder.getMacaroon().serialize()); ❸
}
}

❶ Analise o macaroon e crie um MacaroonsBuilder.

❷ Adicione cada ressalva ao biscoito.

❸ Serialize o biscoito de volta em uma string.

IMPORTANTE Em comparação com o servidor, o cliente precisa apenas de al-


gumas linhas de código para anexar ressalvas e não precisa armazenar nenhuma
chave secreta.

Para testar o programa, use a API Natter para criar um novo espaço social e rece-
ber um URI de capacidade com um token de biscoito. Neste exemplo, eu usei
o jq e cut utilitários para extrair o token do macaroon, mas você pode copiar e
colar manualmente se preferir:

MAC=$(curl -u demo:changeit -H 'Tipo de conteúdo: aplicativo/json' \


-d '{"proprietário":"demonstração","nome":"teste"}' \
https://localhost:4567/spaces | jq -r '.["messages-rw"]' \
| cortar -d= -f2)
Você pode anexar uma advertência, por exemplo, definindo o tempo de expiração
em um minuto ou mais no futuro:

NEWMAC=$(mvn -q exec:java \
-Dexec.mainClass= com.manning.apisecurityinaction.CaveatAppender \
-Dexec.args="$MAC 'time < 2020-08-03T12:05:00Z'")

Você pode usar este novo macaroon para ler todas as mensagens no espaço até
que expire:

curl -u demo:changeit -i \
"https://localhost:4567/spaces/1/messages?access_token=$NEWMAC"

Depois que o novo limite de tempo expirar, a solicitação retornará um erro 403
Forbidden, mas o token original ainda funcionará (basta alterar $NEWMAC para
$MAC na consulta para testar isso). Isso demonstra a principal vantagem dos ma-
caroons: depois de configurar o servidor, é muito fácil (e rápido) para um cliente
acrescentar advertências contextuais que restringem o uso de um token, prote-
gendo esses tokens em caso de comprometimento. Um cliente JavaScript execu-
tado em um navegador da Web pode usar uma biblioteca de macaroon JavaScript
para anexar advertências sempre que usar um token com apenas algumas linhas
decódigo.
9.3.4 Advertências de terceiros

Primeira festaadvertências fornecem flexibilidade considerável e melhorias de se-


gurança em relação aos tokens tradicionais por conta própria, mas os macaroons
também permitemadvertências de terceiros que são verificadas por um serviço
externo. Em vez de a API verificar diretamente uma advertência de terceiros, o
cliente deve entrar em contato com o próprio serviço de terceiros e obter um bis-
coito de descargaque prova que a condição é satisfeita. Os dois macaroons são
vinculados criptograficamente para que a API possa verificar se a condição foi
atendida sem falar diretamente com o serviço terceirizado.

DEFINIÇÃO Um macaroon de descargaé obtido por um cliente de um serviço


de terceiros para provar que uma advertência de terceiros foi satisfeita. Um ser-
viço de terceiros é qualquer serviço que não seja o cliente ou o servidor que está
tentando acessar. O macaroon de descarga é vinculado criptograficamente ao ma-
caroon original de forma que a API possa garantir que a condição foi satisfeita
sem falar diretamente com o serviço terceirizado.

Advertências de terceiros fornecem a base para autorização descentralizada fra-


camente acoplada e fornecem algumas propriedades interessantes:

A API não precisa se comunicar diretamente com o serviço de terceiros.


Nenhum detalhe sobre a consulta respondida pelo serviço terceirizado é divul-
gado ao cliente. Isso pode ser importante se a consulta contiver informações
pessoais sobre um usuário.
O macaroon de descarga prova que a ressalva foi atendida sem revelar ne-
nhum detalhe ao cliente ou à API.
Como o macaroon de descarga é em si um macaroon, o serviço de terceiros
pode anexar ressalvas adicionais a ele que o cliente deve atender antes de rece-
ber acesso, incluindo outras ressalvas de terceiros.

Por exemplo, um cliente pode receber um token de macaroon de longo prazo para
realizar atividades bancárias em nome de um usuário, como iniciar pagamentos
de sua conta. Além das advertências de terceiros que restringem quanto o cliente
pode transferir em uma única transação, o banco pode anexar uma advertência
de terceiros que exige que o cliente obtenha autorização para cada pagamento de
um serviço de autorização de transação. O serviço de autorização de transação ve-
rifica os detalhes da transação e potencialmente confirma a transação direta-
mente com o usuário antes de emitir um macaroon de descarga vinculado a essa
transação. Esse padrão de ter um único token de longa duração fornecendo acesso
geral, mas exigindo macaroons de descarga de curta duração para autorizar tran-
sações específicas é um caso de uso perfeito para ressalvas de terceiros.

CRIANDO ADVERTÊNCIAS DE TERCEIROS

Diferenteuma advertência de primeira parte, que é uma string simples, uma ad-
vertência de terceiros tem três componentes:

Uma dica de localização informando ao cliente onde localizar o serviço de


terceiros.
Uma string secreta única e impossível de adivinhar, que será usada para deri-
var uma nova chave HMAC que o serviço de terceiros usará para assinar o ma-
caroon de descarga.
Um identificador para a advertência que o terceiro pode usar para identificar a
consulta. Esse identificador é público e, portanto, não deve revelar o segredo.

Para adicionar uma advertência de terceiros a um biscoito, use o


add_third_party_caveat métodono MacaroonsBuilder objeto:

macaroon = MacaroonsBuilder.modify(macaroon) ❶
.add_third_party_caveat("https://auth.example.com", ❷
segredo, caveatId) ❷
.getMacaroon();

❶ Modifique um biscoito existente para adicionar uma ressalva.

❷ Adicione a advertência de terceiros.

O segredo indecifrável deve ser gerado com alta entropia, como um valor de 256
bits de um SecureRandom :

var chave = novo byte[32];


novo SecureRandom().nextBytes(chave);
var segredo = Base64.getEncoder().encodeToString(chave);
Quando você adiciona uma advertência de terceiros a um biscoito, esse segredo é
criptografado para que apenas a API que verifica o biscoito possa descriptografá-
lo. A parte que anexa a ressalva também precisa comunicar o segredo e a consulta
a ser verificada ao serviço terceirizado. Existem duas maneiras de fazer isso, com
diferentes compensações:

O anexador de advertência pode codificar a consulta e o segredo em uma men-


sagem e criptografá-la usando uma chave pública do serviço de terceiros. O va-
lor criptografado é usado como o identificador para a advertência de terceiros.
O terceiro pode então descriptografar o identificador para descobrir a consulta
e o segredo. A vantagem dessa abordagem é que a API não precisa se comuni-
car diretamente com o serviço de terceiros, mas o identificador criptografado
pode ser muito grande.
Como alternativa, o anexador de advertência pode entrar em contato direta-
mente com o serviço terceirizado (por meio de uma API REST, por exemplo)
para registrar a advertência e o segredo. O serviço de terceiros os armazenaria
e retornaria um valor aleatório (conhecido como ticket) que pode ser usado
como identificador de advertência. Quando o cliente apresenta o identificador
ao terceiro, ele pode pesquisar a consulta e o segredo em seu armazenamento
local com base no ticket. Essa solução provavelmente produzirá identificadores
menores, mas ao custo de solicitações de rede adicionais e armazenamento no
serviço de terceiros.
Atualmente, não há um padrão para nenhuma dessas duas opções descrevendo
como seria a API para registrar uma ressalva para a segunda opção ou qual algo-
ritmo de criptografia de chave pública e formato de mensagem seriam usados ​
para a primeira. Também não há um padrão que descreva como um cliente apre-
senta o identificador de advertência ao serviço terceirizado. Na prática, isso limita
o uso de ressalvas de terceiros, pois os desenvolvedores de clientes precisam sa-
ber como se integrar a cada serviço individualmente, portanto, normalmente são
usados ​apenas em um ambiente fechado.ecossistema.

questionário

6. Qual das opções a seguir se aplica a uma advertência de primeira parte? Seleci-
one tudo que se aplica.
1. É uma corda simples.
2. É satisfeito com um macaroon de descarga.
3. Requer que o cliente entre em contato com outro serviço.
4. Pode ser verificado no ponto de uso pela API.
5. Ele tem um identificador, uma string secreta e uma dica de localização.
7. Qual das opções a seguir se aplica a uma advertência de terceiros? Selecione
tudo que se aplica.
1. É uma corda simples.
2. É satisfeito com um macaroon de descarga.
3. Requer que o cliente entre em contato com outro serviço.
4. Pode ser verificado no ponto de uso pela API.
5. Ele tem um identificador, uma string secreta e uma dica de localização.

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

Respostas para perguntas do questionário

1. uma,e, f ou g são lugares aceitáveis ​para codificar o token. Os outros podem in-
terferir no funcionamento do URI.
2. c, d e e.
3. b e e impediriam que os tokens preenchessem o banco de dados. Usar um
banco de dados mais escalável provavelmente apenas atrasará isso (e aumen-
tará seus custos).
4. e. Sem retornar links, um cliente não tem como criar URIs para outros recursos.
5. d. Se o servidor redirecionar, o navegador copiará o fragmento para o novo
URL, a menos que um novo seja especificado. Isso pode vazar o token para ou-
tros servidores. Por exemplo, se você redirecionar o usuário para um serviço de
login externo, o componente de fragmento não é enviado ao servidor e não é
incluído nos cabeçalhos do Referer.
6. a e d.
7. b,c,ee.
Resumo

Os URIs de capacidade podem ser usados ​para fornecer acesso refinado a re-
cursos individuais por meio de sua API. Um URI de capacidade combina um
identificador para um recurso junto com um conjunto de permissões para aces-
sar esse recurso.
Como alternativa ao controle de acesso baseado em identidade, os recursos evi-
tam a autoridade ambiente que pode levar a ataques confusos de representan-
tes e adotar o POLA.
Há muitas maneiras de formar URIs de capacidade com diferentes compensa-
ções. As formas mais simples codificam um token aleatório no caminho do URI
ou nos parâmetros de consulta. Variantes mais seguras codificam o token no
fragmento ou nos componentes de informações do usuário, mas têm um custo
de maior complexidade para os clientes.
Vincular um URI de capacidade a uma sessão de usuário aumenta a segurança
de ambos, porque reduz o risco de roubo de tokens de capacidade e pode ser
usado para evitar ataques CSRF. Isso torna mais difícil compartilhar URIs de
capacidade.
Os macaroons permitem que qualquer pessoa restrinja uma capacidade ane-
xando advertências que podem ser verificadas criptograficamente e aplicadas
por uma API. Advertências contextuais podem ser anexadas logo antes de um
biscoito ser usado para proteger um token contra uso indevido.
As advertências primárias codificam condições simples que podem ser verifica-
das localmente por uma API, como restringir a hora do dia em que um token
pode ser usado. As advertências de terceiros exigem que o cliente obtenha um
macaroon de descarga de um serviço externo que comprove que ele atende a
uma condição, de modo que o usuário seja funcionário de uma determinada
empresa ou tenha terminado18 anos de idade.

1.
Este exemplo foi retirado de “Paradigm Regained: Abstraction Mechanisms for Ac-
cess Control”. Veja http://mng.bz/Mog7 .

2.
Existem propostas para fazer o OAuth funcionar melhor para esses tipos de opera-
ções transacionais pontuais, como https://oauth.xyz, mas elas ainda exigem que o
aplicativo saiba qual recurso deseja acessar antes de iniciar o fluxo.

3.
Você pode obter o projeto em https://github.com/NeilMadden/apisecurityinaction
se não tiver trabalhado no capítulo 8. Confira o capítulo 09 da ramificação.

4.
Neste capítulo, você retornará links como URIs dentro de campos JSON normais.
Existem formas padrão de representar links em JSON, como JSON-LD (
https://json-ld.org ), mas não vou abordá-las neste livro.
5.
Se você é um entusiasta da programação funcional, isso pode ser escrito elegante-
mente como uma operação de dobra à esquerda ou redução.

6.
Meu empregador, ForgeRock, adicionou suporte experimental para macaroons ao
seu software de servidor de autorização.

Você também pode gostar