You are on page 1of 93

Tópicos Comece a aprender Pesquise mais de 50.

000 cursos, even… O que há de novo

7 OAuth2 e OpenID Connect


Este capítulocapas

Habilitando o acesso de terceiros à sua API com tokens com escopo


Integrando um servidor de autorização OAuth2 para autorização delegada
Validando tokens de acesso OAuth2 com introspecção de token
Implementando logon único com OAuth e OpenID Connect

Nos últimos capítulos, você implementou métodos de autenticação de usuário que


são adequados para a IU do Natter e seus próprios aplicativos móveis e de desk-
top. Cada vez mais, as APIs estão sendo abertas para aplicativos de terceiros e cli-
entes de outras empresas e organizações. Natter não é diferente, e seu CEO recém-
nomeado decidiu que você pode impulsionar o crescimento incentivando um
ecossistema de clientes e serviços da API Natter. Neste capítulo, você integrará
umOAuth2 Authorization Server (AS) para permitir que seus usuários deleguem
acesso a clientes de terceiros. Ao usar tokens com escopo, os usuários podem res-
tringir quais partes da API esses clientes podem acessar. Por fim, você verá como
o OAuth fornece uma maneira padrão de centralizar a autenticação baseada em
token em sua organização para obter logon único em diferentes APIs e serviços. O
padrão OpenID Connect se baseia no OAuth2 para fornecer uma estrutura de au-
tenticação mais completa quando você precisa de um controle mais preciso sobre
como um usuário é autenticado.
Neste capítulo, você aprenderá como obter um token de um AS para acessar uma
API e como validar esses tokens em sua API, usando a API Natter como exemplo.
Você não aprenderá como escrever seu próprio AS, porque isso está além do es-
copo deste livro. O uso do OAuth2 para autorizar chamadas serviço a serviço é
abordado no capítulo 11.

SAIBA MAIS SOBRE ISSO VejaOAuth2 in Action por Justin Richer e Antonio
Sanso (Manning, 2017; https://www.manning.com/books/oauth-2-in-action ) se
você quiser aprender como um AS funciona em detalhes.

Como todos os mecanismos descritos neste capítulo são padrões, os padrões funci-
onarão com qualquer AS compatível com os padrões com poucas alterações. Con-
sulte o apêndice A para obter detalhes sobre como instalar e configurar um AS
para uso neste capítulo.

7.1 Tokens com escopo

Dentronos velhos tempos, se você quisesse usar um aplicativo ou serviço de ter-


ceiros para acessar seu e-mail ou conta bancária, não tinha escolha a não ser for-
necer seu nome de usuário e senha e esperar que eles não os usassem indevida-
mente. Infelizmente, alguns serviços usaram mal essas credenciais. Mesmo aque-
les que eram confiáveis ​teriam que armazenar sua senha de forma recuperável
para poder usá-la, tornando o comprometimento potencial muito mais provável,
como você aprendeu no capítulo 3. A autenticação baseada em token fornece uma
solução para esse problema, permitindo que você para gerar um token de longa
duração que você pode fornecer ao serviço de terceiros em vez de sua senha. O
serviço pode usar o token para agir em seu nome. Ao parar de usar o serviço, você
pode revogar o token para impedir qualquer outro acesso.
Embora usar um token signifique que você não precisa fornecer sua senha a ter-
ceiros, os tokens que você usou até agora ainda concedem acesso total às APIs
como se você mesmo estivesse executando ações. O serviço de terceiros pode usar
o token para fazer tudo o que você pode fazer. Mas você pode não confiar em ter-
ceiros para ter acesso total e deseja apenas conceder a eles acesso parcial. Quando
administrei meu próprio negócio, usei brevemente um serviço de terceiros para
ler as transações da minha conta bancária comercial e importá-las para o soft-
ware de contabilidade que usei. Embora esse serviço precisasse apenas de acesso
de leitura a transações recentes, na prática ele tinha acesso total à minha conta e
poderia ter transferido fundos, cancelado pagamentos e executado muitas outras
ações. Parei de usar o serviço e voltei a inserir transações manualmente porque o
risco era muito grande.1

A solução para esses problemas é restringir as operações da API que podem ser
realizadas com um token, permitindo que ele seja utilizado apenas dentro de um
escopo bem definido. Por exemplo, você pode permitir que seu software de conta-
bilidade leia transações que ocorreram nos últimos 30 dias, mas não permitir que
visualize ou crie novos pagamentos na conta. O escopo do acesso que você conce-
deu ao software de contabilidade é, portanto, limitado ao acesso somente leitura a
transações recentes. Normalmente, o escopo de um token é representado como
um ou mais rótulos de string armazenados como um atributo do token. Por exem-
plo, você pode usar o rótulo de escopo transactions:read para permitir acesso
de leitura a transações e payment :create permitir a configuração de um novo
pagamento de uma conta. Como pode haver mais de um rótulo de escopo associ-
ado a um token, eles geralmente são chamados de escopos. Os escopos (rótulos) de
um token definem coletivamente o escopo de acesso que ele concede. A Figura 7.1
mostra alguns dos rótulos de escopo disponíveis ao criar um token de acesso pes-
soal no GitHub.
Figura 7.1 O GitHub permite que os usuários criem manualmente tokens com es-
copo, que eles chamam de tokens de acesso pessoal. Os tokens nunca expiram,
mas podem ser restritos para permitir acesso apenas a partes da API do GitHub
definindo o escopo do token.

DEFINIÇÃO Um token com escopo limita as operações que podem ser executa-
das com esse token. O conjunto de operações permitidas é conhecido como escopo
do token. O escopo de um token é especificado por um ou mais rótulos de escopo,
que geralmente são referidos coletivamente como escopos.

7.1.1 Adicionando tokens com escopo ao Natter

Adaptandoo endpoint de login existente para emitir tokens com escopo é muito
simples, conforme mostrado na Listagem 7.1. Quando uma solicitação de login é
recebida, se ela contiver um parâmetro de escopo, você poderá associar esse es-
copo ao token armazenando-o nos atributos do token. Você pode definir um con-
junto padrão de escopos a serem concedidos se o parâmetro de escopo não for es-
pecificado. Abra o arquivo TokenController.java em seu editor e atualize o mé-
todo de login para incluir suporte para tokens com escopo, como na listagem 7.1.
Na parte superior do arquivo, adicione uma nova constante listando todos os es-
copos. No Natter, você usará escopos correspondentes a cada operação da API:

string final estática privada DEFAULT_SCOPES =


"create_space post_message read_message list_messages" +
"delete_message add_member";

AVISO Há um possível problema de escalonamento de privilégios a ser obser-


vado neste código. Um cliente que recebe um token com escopo pode chamar esse
endpoint para trocá-lo por um com mais escopos. Você corrigirá isso em breve
adicionando uma nova regra de controle de acesso para o endpoint de login para
evitar isso.

Listagem 7.1 Emitindo tokens com escopo

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


String assunto = request.attribute("assunto");
var expiração = Instant.now().plus(10, ChronoUnit.MINUTES);
var token = new TokenStore.Token(expiry, subject);
var escopo = request.queryParamOrDefault("escopo", DEFAULT_SCOPES); ❶
token.attributes.put("escopo", escopo); ❶
var tokenId = tokenStore.create(pedido, token);

resposta.status(201);
retornar novo JSONObject()
.put("token", tokenId);
}

❶ Armazene o escopo nos atributos do token, padronizando todos os escopos se não


for especificado.

Para impor as restrições de escopo em um token, você pode adicionar um novo


filtro de controle de acesso que garante que o token usado para autorizar uma so-
licitação à API tenha o escopo necessário para a operação que está sendo execu-
tada. Este filtro se parece muito com o filtro de permissão existente que você adi-
cionou no capítulo 3 e é mostrado na Listagem 7.2. (Discutirei as diferenças entre
escopos e permissões na próxima seção.) Para verificar o escopo, você precisa rea-
lizar várias verificações:

Primeiro, verifique se o método HTTP da solicitação corresponde ao método


para o qual esta regra é, para que você não aplique um escopo para uma solici-
tação POST a uma solicitação DELETE ou vice-versa. Isso é necessário porque os
filtros do Spark são correspondidos apenas pelo caminho e não pelo método de
solicitação.
Você pode pesquisar o escopo associado ao token que autorizou a solicitação
atual no atributo de escopo da solicitação. Isso funciona porque o código de va-
lidação do token que você escreveu no capítulo 4 copia todos os atributos do to-
ken para a solicitação, portanto, o atributo do escopo também será copiado.
Se não houver nenhum atributo de escopo, o usuário autenticou diretamente a
solicitação com autenticação básica. Nesse caso, você pode pular a verificação
de escopo e deixar a solicitação prosseguir. Qualquer cliente com acesso à se-
nha do usuário poderia emitir um token para si mesmo com qualquer escopo.
Finalmente, você pode verificar se o escopo do token corresponde ao escopo
necessário para esta solicitação e, se não corresponder, você deve retornar um
erro 403 Forbidden. O esquema de autenticação do portador tem um código de
erro dedicado insufficient_ scope para indicar que o chamador precisa de
um token com um escopo diferente, portanto, você pode indicar isso no cabeça-
lho WWW-Authenticate.

Abra TokenController.java em seu editor novamente e adicione o requireSco-


pe métododa listagem.

Listagem 7.2 Verificando os escopos necessários

public Filter requireScope(String method, String requiredScope) {


return (solicitação, resposta) -> {
if (!method.equalsIgnoreCase(request.requestMethod())) ❶
return; ❶
var tokenScope = request.<String>attribute("scope"); ❷
if (tokenScope == null) return; ❷
if (!Set.of(tokenScope.split(" ")) ❸
.contains(requiredScope)) { ❸
response.header("WWW-Authenticate", ❸
"Bearer error=\"insufficient_scope\"," + ❸
" escopo=\""
parada(403); ❸
}
};
}

❶ Se o método HTTP não corresponder, ignore esta regra.

❷ Se o token não tiver escopo, permita todas as operações.

❸ Se o escopo do token não contiver o escopo necessário, retorne uma resposta 403
Forbidden.

Agora você pode usar esse método para impor qual escopo é necessário para exe-
cutar determinadas operações, conforme mostrado na Listagem 7.3. Decidir quais
escopos devem ser usados ​por sua API e exatamente qual escopo deve ser neces-
sário para quais operações é um tópico complexo, discutido com mais detalhes na
próxima seção. Para este exemplo, você pode usar escopos refinados correspon-
dentes a cada operação de API: create_space , post_message e assim por di-
ante. Para evitar a escalação de privilégios, você deve exigir um escopo específico
para chamar o endpoint de login, porque isso pode ser usado para obter um token
com qualquer escopo, ignorando efetivamente as verificações de escopo. 2 Por ou-
tro lado, revogar um token chamando o endpoint de logout não deve exigir ne-
nhum escopo. Abra o arquivo Main.java em seu editor e adicione verificações de
escopo usando o tokenController.requireScope métodocomo mostrado
emListagem 7.3.

Listagem 7.3 Impondo escopos para operações

before("/sessions", userController::requireAuthentication);
before("/sessions", ❶
tokenController.requireScope("POST", "full_access")); ❶
post("/sessions", tokenController::login);
delete("/sessions", tokenController::logout); ❷

before("/spaces", userController::requireAuthentication);
before("/spaces", ❸
tokenController.requireScope("POST", "create_space")); ❸
post("/spaces", spaceController::createSpace);

before("/spaces/*/messages", ❸
tokenController.requireScope("POST", "post_message")); ❸
before("/espaços/:espaçoId/mensagens",
userController.requirePermission("POST", "w"));
post("/spaces/:spaceId/messages", spaceController::postMessage);

before("/spaces/*/messages/*", ❸
tokenController.requireScope("GET", "read_message")); ❸
before("/espaços/:espaçoId/mensagens/*",
userController.requirePermission("GET", "r"));
get("/espaços/:espaçoId/messages/:msgId",
spaceController::readMessage);

before("/spaces/*/messages", ❸
tokenController.requireScope("GET", "list_messages")); ❸
before("/espaços/:espaçoId/mensagens",
userController.requirePermission("GET", "r"));
get("/spaces/:spaceId/messages", spaceController::findMessages);

before("/spaces/*/members", ❸
tokenController.requireScope("POST", "add_member")); ❸
before("/espaços/:espaçoId/membros",
userController.requirePermission("POST", "rwd"));
post("/spaces/:spaceId/members", spaceController::addMember);
before("/spaces/*/messages/*", ❸
tokenController.requireScope("DELETE", "delete_message")); ❸
before("/espaços/:espaçoId/mensagens/*",
userController.requirePermission("DELETE", "d"));
delete("/espaços/:spaceId/messages/:msgId",
moderadorController::deletePost);

❶ Certifique-se de que a própria obtenção de um token com escopo requer um es-


copo restrito.

❷ A revogação de um token não deve exigir nenhum escopo.

❸ Adicione requisitos de escopo a cada operação exposta pela API.

7.1.2 A diferença entre escopos e permissões

NoÀ primeira vista, pode parecer que os escopos e as permissões são muito seme-
lhantes, mas há uma distinção no que eles são usados, conforme mostrado na fi-
gura 7.2. Normalmente, uma API pertence e é operada por uma autoridade cen-
tral, como uma empresa ou organização. Quem pode acessar a API e o que eles
podem fazer é controlado inteiramente pela autoridade central. Este é um exem-
plo de controle de acesso obrigatório, porque os usuários não têm controle sobre
suas próprias permissões ou as de outros usuários. Por outro lado, quando um
usuário delega parte de seu acesso a um aplicativo ou serviço de terceiros, isso é
conhecido como controle de acesso discricionário, porque cabe ao usuário quanto
de seu acesso conceder ao terceiro. Os escopos OAuth tratam fundamentalmente
do controle de acesso discricionário, enquanto as permissões tradicionais (que
você implementou usando ACLs no capítulo 3) podem ser usadas para controle de
acesso obrigatório.
Figura 7.2 As permissões geralmente são concedidas por uma autoridade central
que possui a API que está sendo acessada. Um usuário não pode escolher ou alte-
rar suas próprias permissões. Os escopos permitem que um usuário delegue parte
de sua autoridade a um aplicativo de terceiros, restringindo quanto acesso eles
concedem usando escopos.

DEFINIÇÃO Com controle de acesso obrigatório (MAC), as permissões do usuá-


rio são definidas e aplicadas por uma autoridade central e não podem ser conce-
didas pelos próprios usuários. Com controle de acesso discricionário (DAC), os
usuários podem delegar algumas de suas permissões a outros usuários. OAuth2
permite controle de acesso discricionário, também conhecido como autorização
delegada.

Enquanto os escopos são usados ​para delegação, as permissões podem ser usadas
para acesso obrigatório ou discricionário. As permissões de arquivo no UNIX e na
maioria dos outros sistemas operacionais populares podem ser definidas pelo
proprietário do arquivo para conceder acesso a outros usuários e, assim, imple-
mentar o DAC. Por outro lado, alguns sistemas operacionais usados ​por militares e
governos têm controles de acesso obrigatórios que impedem que alguém com au-
torização apenas SECRET leia documentos ULTRA SECRETOS, por exemplo, inde-
pendentemente de o proprietário do arquivo querer conceder-lhes acesso. 3 Os
métodos para organizar e impor permissões para MAC são abordados no capítulo
8. Os escopos OAuth fornecem uma maneira de colocar DAC em camadas sobre
uma camada de segurança MAC existente.

Colocando a distinção teórica entre MAC e DAC de lado, a distinção mais prática
entre escopos e permissões está relacionada a como eles são projetados. O admi-
nistrador de uma API projeta permissões para refletir as metas de segurança do
sistema. Essas permissões refletem as políticas organizacionais. Por exemplo, um
funcionário que executa um trabalho pode ter acesso de leitura e gravação a to-
dos os documentos em uma unidade compartilhada. As permissões devem ser
projetadas com base nas decisões de controle de acesso que um administrador
pode querer tomar para usuários individuais, enquanto os escopos devem ser
projetados com base na previsão de como os usuários podem querer delegar seu
acesso a aplicativos e serviços de terceiros.

OBSERVAÇÃO A autorização delegada no OAuth é sobre usuários que delegam


sua autoridade a clientes, como aplicativos móveis. oAcesso gerenciado pelo
usuário(UMA) de OAuth2 permite que os usuários deleguem acesso a outros
usuários.

Um exemplo dessa distinção pode ser visto no design dos escopos OAuth usados ​
pelo Google para acessar seusServiços do Google Cloud Platform. Serviços que li-
dam com tarefas de administração do sistema, como oO serviço de gerenciamento
de chaves para lidar com chaves criptográficas possui apenas um único escopo
que concede acesso a toda essa API. O acesso a chaves individuais é gerenciado
por meio de permissões. Mas as APIs que fornecem acesso aos dados individuais
do usuário, como a API Fitness ( http://mng.bz/EEDJ ) são divididas em escopos
muito mais refinados, permitindo que os usuários escolham exatamente com
quais estatísticas de saúde desejam compartilhar terceiros, conforme figura 7.3.
Fornecer aos usuários um controle refinado ao compartilhar seus dados é uma
parte fundamental de uma estratégia moderna de privacidade e consentimento e
pode ser exigido em alguns casos por legislação como o Regulamento Geral de
Proteção de Dados da UE(RGPD).

Outra distinção entre escopos e permissões é que os escopos geralmente identifi-


cam apenas o conjunto de operações de API que podem ser executadas, enquanto
as permissões também identificam os objetos específicos que podem ser acessa-
dos. Por exemplo, um cliente pode receber um list_files escopoque permite
chamar uma operação de API para listar arquivos em uma unidade comparti-
lhada, mas o conjunto de arquivos retornado pode diferir dependendo das per-
missões do usuário que autorizou o token. Essa distinção não é fundamental, mas
reflete o fato de que os escopos geralmente são adicionados a uma API como uma
camada adicional sobre um sistema de permissão existente e são verificados com
base em informações básicas na solicitação HTTP sem conhecimento dos objetos
de dados individuais que serão operado.

Ao escolher quais escopos expor em sua API, você deve considerar qual nível de
controle seus usuários provavelmente precisarão ao delegar acesso. Não há uma
resposta simples para essa pergunta, e o design do escopo geralmente requer vá-
rias iterações de colaboração entre arquitetos de segurança, designers de experi-
ência do usuário edo utilizadorrepresentantes.
Figura 7.3 Os escopos OAuth do Google Cloud Platform são muito refinados para
APIs do sistema, como acesso ao banco de dados ou gerenciamento de chaves.
Para APIs que processam dados do usuário, como a API Fitness, muitos outros es-
copos são definidos, permitindo aos usuários maior controle sobre o que compar-
tilham com aplicativos e serviços de terceiros.

APRENDA SOBRE ISSO Algumas estratégias gerais para design de escopo e


documentação são fornecidas em The Design of Web APIs de Arnaud Lauret
(Manning, 2019; https://www.manning.com/books/the-design-of-web-apis ).

questionário

1. Quais das opções a seguir são diferenças típicas entre escopos e permissões?
1. Os escopos são mais refinados do que as permissões.
2. Os escopos são mais refinados do que as permissões.
3. Os escopos usam nomes mais longos do que permissões.
4. As permissões geralmente são definidas por uma autoridade central, en-
quanto os escopos são projetados para delegar o acesso.
5. Os escopos normalmente restringem apenas as operações de API que podem
ser chamadas. As permissões também restringem quais objetos podem ser
acessados.

A resposta está no final do capítulo.

7.2 Apresentando OAuth2

Emborapermitir que seus usuários criem manualmente tokens com escopo para
aplicativos de terceiros é uma melhoria em relação ao compartilhamento de to-
kens sem escopo ou credenciais de usuário, pode ser confuso e propenso a erros.
Um usuário pode não saber quais escopos são necessários para que o aplicativo
funcione e, portanto, pode criar um token com poucos escopos ou talvez delegar
todos os escopos apenas para que o aplicativo funcione.

Uma solução melhor é que o aplicativo solicite os escopos necessários e, em se-


guida, a API pode perguntar ao usuário se ele consente. Essa é a abordagem ado-
tada pelo protocolo de autorização delegada OAuth2, conforme mostrado na fi-
gura 7.4. Como uma organização pode ter muitas APIs, o OAuth introduz a noção
de um servidor de autorização (AS), que atua como um serviço central para ge-
renciar autenticação e consentimento do usuário e emissão de tokens. Como você
verá mais adiante neste capítulo, essa centralização oferece vantagens significati-
vas mesmo se sua API não tiver clientes de terceiros, o que é um dos motivos pe-
los quais o OAuth2 se tornou tão popular como padrão de segurança de API. Os to-
kens que um aplicativo usa para acessar uma API são conhecidos como tokens de
acesso no OAuth2, para diferenciá-los de outros tipos de tokens sobre os quais
você aprenderá mais adiante neste capítulo.

DEFINIÇÃO Um token de acesso é um token emitido por um servidor de autori-


zação OAuth2 para permitir que um cliente acesse uma API.

OAuth usa termos específicos para se referir às quatro entidades mostradas na fi-
gura 7.4, com base no papel que desempenham na interação:

O servidor de autorização (AS) autentica o usuário e emite tokens para os


clientes.
O usuário é conhecido como o proprietário do recurso(RO), porque normal-
mente são seus recursos (documentos, fotos e assim por diante) que o aplicativo
de terceiros está tentando acessar. Este termo nem sempre é preciso, mas pe-
gou agora.
O aplicativo ou serviço de terceiros é conhecido como cliente.
A API que hospeda os recursos do usuário é conhecida como servidor de
recursos(RS).

7.2.1 Tipos de clientes

Antes daum cliente pode solicitar um token de acesso, ele deve primeiro se regis-
trar no AS e obter um ID de cliente exclusivo. Isso pode ser feito manualmente
por um administrador do sistema ou existe um padrão para permitir que os clien-
tes se registrem dinamicamente em um AS ( https://tools.ietf .org/html/rfc7591 ).
SAIBA MAIS OAuth2 in Action de Justin Richer e Antonio Sanso (Manning, 2017;
https://www.manning.com/books/oauth-2-in-action ) abrange o registro dinâmico
de clientes com mais detalhes.

Existem dois tipos diferentes de clientes:

clientes públicossão aplicativos executados inteiramente no próprio dispositivo


do usuário, como um aplicativo móvel ou cliente JavaScript executado em um
navegador. O cliente está totalmente sob o controle do usuário.
clientes confidenciaisexecutado em um servidor da Web protegido ou outro lo-
cal seguro que não esteja sob o controle direto do usuário.

A principal diferença entre os dois é que um cliente confidencial pode ter suas
próprias credenciais de clienteque ele usa para autenticar no servidor de autori-
zação. Isso garante que um invasor não possa se passar por um cliente legítimo
para tentar obter um token de acesso de um usuário em um ataque de phishing.
Um aplicativo móvel ou baseado em navegador não pode manter as credenciais
em segredo porque qualquer usuário que fizer o download do aplicativo pode ex-
traí-las. 4 Para clientes públicos, são utilizadas medidas alternativas de proteção
contra esses ataques, como você verá em breve.
Figura 7.4 Para acessar uma API usando OAuth2, um aplicativo deve primeiro ob-
ter um token de acesso do Authorization Server (AS). O aplicativo informa ao AS
qual escopo de acesso ele requer. O AS verifica se o usuário consente com esse
acesso e emite um token de acesso ao aplicativo. O aplicativo pode usar o token de
acesso para acessar a API em nome do usuário.

DEFINIÇÃO Um cliente confidencial usa credenciais de clientepara autenticar


no AS. Normalmente, esta é uma senha aleatória longa conhecida como segredo
do cliente, mas formas mais seguras de autenticação podem ser usadas, incluindo
certificados de cliente JWTs e TLS.
Cada cliente normalmente pode ser configurado com o conjunto de escopos que
pode solicitar a um usuário. Isso permite que um administrador evite que aplica-
tivos não confiáveis ​solicitem alguns escopos se permitirem acesso privilegiado.
Por exemplo, um banco pode permitir que a maioria dos clientes tenha acesso so-
mente leitura às transações recentes de um usuário, mas exigir uma validação
mais ampla do desenvolvedor do aplicativo antes que o aplicativo possa ser
iniciadopagamentos.

7.2.2 Concessões de autorização

Paraobter um token de acesso, o cliente deve primeiro obter o consentimento do


usuário na forma de uma concessão de autorização com escopos apropriados. O
cliente então apresenta esta concessão ao token endpoint do AS para obter um to-
ken de acesso. OAuth2 oferece suporte a muitos tipos diferentes de concessão de
autorização para oferecer suporte a diferentes tipos de clientes:

oConcessão de Credenciais de Senha do Proprietário do Recurso (ROPC)é a mais


simples, na qual o usuário fornece seu nome de usuário e senha ao cliente, que
os envia diretamente ao AS para obter um token de acesso com a abrangência
que desejar. Isso é quase idêntico ao endpoint de login de token que você de-
senvolveu nos capítulos anteriores e não é recomendado para clientes de ter-
ceiros porque o usuário compartilha sua senha diretamente com o aplicativo -
exatamente o que você estava tentando evitar!

CUIDADO O ROPC pode ser útil para testes, mas deve ser evitado na maioria dos
casos. Pode ser obsoleto em versões futuras do padrão.
NoConcessão de Código de Autorização, o cliente primeiro usa um navegador
da Web para navegar até um terminal de autorização dedicadono AS, indi-
cando quais escopos ele requer. O AS então autentica o usuário diretamente no
navegador e pede consentimento para o acesso do cliente. Se o usuário concor-
dar, o AS gera um código de autorização e o entrega ao cliente para trocá-lo por
um token de acesso no endpoint do token. A concessão do código de autoriza-
ção é abordada com mais detalhes na próxima seção.
oConcessão de credenciais do clientepermite que o cliente obtenha um token de
acesso usando suas próprias credenciais, sem nenhum usuário envolvido. Essa
concessão pode ser útil em alguns padrões de comunicação de microsserviços
discutidos no capítulo 11.
Existem vários tipos de subsídios adicionais para situações mais específicas,
como oconcessão de autorização de dispositivo(também conhecido como fluxo
de dispositivo) para dispositivos sem qualquer meio direto de interação do
usuário. Não há registro de tipos de concessão definidos, mas sites como
https://oauth.net/2/grant-types/ listam os tipos mais usados. A concessão de au-
torização do dispositivo é abordada no capítulo 13. As concessões OAuth2 são
extensíveis, portanto, novos tipos de concessão podem ser adicionados quando
uma das concessões existentes não se encaixa.

E a concessão implícita?

A definição original de OAuth2 incluía uma variação na concessão do código de


autorização conhecida como concessão implícita. Nessa concessão, o AS retornava
um token de acesso diretamente do endpoint de autorização, de modo que o cli-
ente não precisava chamar o endpoint do token para trocar um código. Isso foi
permitido porque, quando o OAuth2 foi padronizado em 2012, o CORS ainda não
havia sido finalizado, portanto, um cliente baseado em navegador, como um apli-
cativo de página única, não poderia fazer uma chamada de origem cruzada para
o ponto de extremidade do token. Na concessão implícita, o AS redireciona de
volta do terminal de autorização para um URI controlado pelo cliente, com o to-
ken de acesso incluído no componente de fragmento do URI. Isso apresenta al-
guns pontos fracos de segurança em comparação com a concessão do código de
autorização, pois o token de acesso pode ser roubado por outros scripts em execu-
ção no navegador ou vazar pelo histórico do navegador e outros mecanismos.
Como o CORS agora é amplamente suportado pelos navegadores,O documento
OAuth Security Best Common Practice ( https://tools.ietf.org/html/draft-ietf-oauth-
security-topics ) agora desaconselha seu uso.
Um exemplo de como obter um token de acesso usando o tipo de concessão ROPC
é o seguinte, pois esse é o tipo de concessão mais simples. O cliente especifica o
tipo de concessão ( password neste caso), seu ID de cliente (para um cliente pú-
blico) e o escopo que está solicitando como parâmetros POST no
application/x-www-form-urlencoded formato usado por formulários HTML.
Ele também envia o nome de usuário e a senha do proprietário do recurso da
mesma maneira. O AS autenticará o RO usando as credenciais fornecidas e, se for
bem-sucedido, retornará um token de acesso em uma resposta JSON. A resposta
também contém metadados sobre o token, como por quanto tempo ele é
válido(em segundos).

$ curl -d 'grant_type=password&client_id=test ❶
➥ &scope=read_messages+post_message ❶
➥ &username=demo&password=changeit' ❷
➥ https://as.example.com:8443/oauth2/access_token
{
"access_token":"I4d9xuSQABWthy71it8UaRNM2JA", ❸
"scope":"post_message read_messages",
"token_type":"Portador",
"expires_in":3599}
❶ Especifique o tipo de concessão, ID do cliente e escopo solicitado como campos de
formulário POST.

❷ O nome de usuário e senha do RO também são enviados como campos de


formulário.

❸ O token de acesso é retornado em uma resposta JSON, junto com seus metadados.

7.2.3 Descobrindo endpoints OAuth2

oOs padrões OAuth2 não definem caminhos específicos para o token e os end-
points de autorização, portanto, eles podem variar de AS para AS. Como as exten-
sões foram adicionadas ao OAuth, vários outros endpoints foram adicionados,
juntamente com várias configurações para novos recursos. Para evitar que cada
cliente tenha que codificar os locais desses terminais, há uma maneira padrão de
descobrir essas configurações usando um documento de descoberta de serviço
publicado em um local conhecido. Originalmente desenvolvido para o perfil Ope-
nID Connect do OAuth (que será abordado posteriormente neste capítulo), ele foi
adotado pelo OAuth2 ( https://tools.ietf.org/html/rfc8414 ).

Um AS em conformidade é necessário para publicar um documento JSON no ca-


minho /.well-known/oauth-authorization-server na raiz de seu servidor web. 5
Este documento JSON contém as localizações do token e dos terminais de autori-
zação e outras configurações. Por exemplo, se seu AS estiver hospedado como
https://as.example.com:8443, uma solicitação GET para
https://as.example.com:8443/.well-known/oauth-authorization-server retornará
um documento JSON comoaSegue:

{
"authorization_endpoint":
"http://openam.example.com:8080/oauth2/authorize",
"token_endpoint":
"http://openam.example.com:8080/oauth2/access_token",
...
}

AVISO Como o cliente enviará credenciais e tokens de acesso para muitos desses
endpoints, é fundamental que eles sejam descobertos por uma fonte confiável. Re-
cupere o documento de descoberta apenas por HTTPS de um URL confiável.

questionário

2. Quais das duas concessões OAuth padrão agora são desencorajadas?


1. A concessão implícita
2. A concessão do código de autorização
3. A concessão de autorização do dispositivo
4. Hugh Grant
5. A concessão de Credenciais de Senha do Proprietário do Recurso (ROPC)
3. Que tipo de cliente deve ser usado para um aplicativo móvel?
1. Um cliente público
2. Um cliente confidencial

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

7.3 A concessão do Código de Autorização

No entantoOAuth2 suporta muitos tipos diferentes de concessão de autorização,


de longe a escolha mais útil e segura para a maioria dos clientes é a concessão de
código de autorização. Com a concessão implícita agora desencorajada, a conces-
são do código de autorização é a maneira preferencial para quase todos os tipos
de clientes obterem um token de acesso, incluindo o seguinte:
Clientes do lado do servidor, como aplicativos da Web tradicionais ou outras
APIs. Um aplicativo do lado do servidor deve ser um cliente confidencial com
credenciais para autenticação no AS.
Aplicativos JavaScript do lado do cliente executados no navegador, como apli-
cativos de página única. Um aplicativo do lado do cliente é sempre um cliente
público porque não possui um local seguro para armazenar um segredo do
cliente.
Aplicativos móveis, de desktop e de linha de comando. Quanto aos aplicativos
do lado do cliente, eles devem ser clientes públicos, pois qualquer segredo in-
corporado ao aplicativo pode ser extraído por um usuário.

Na concessão do código de autorização, o cliente primeiro redireciona o navega-


dor da web do usuário para o endpoint de autorização no AS, conforme mostrado
na figura 7.5. O cliente inclui seu ID de cliente e o escopo que está solicitando do
AS neste redirecionamento. Defina o response_type parâmetrona consulta para
code solicitar um código de autorização (outras configurações, como as toke-
n usadas para a concessão implícita). Por fim, o cliente deve gerar um valor alea-
tório exclusivo state para cada solicitação e armazená-lo localmente (como em
um cookie do navegador). Quando o AS redirecionar de volta para o cliente com o
código de autorização, ele incluirá o mesmo state parâmetro, devendo o cliente
verificar se corresponde ao original enviado na requisição. Isso garante que o có-
digo recebido pelo cliente seja o solicitado. Caso contrário, um invasor poderá
criar um link que chame o endpoint de redirecionamento do cliente diretamente
com um código de autorização obtido pelo invasor. Esse ataque é como os ataques
Login CSRF discutidos no capítulo 4, e o parâmetro de estado desempenha um pa-
pel semelhante a um token anti-CSRF nesse caso. Por fim, o cliente deve incluir o
URI para o qual deseja que o AS redirecione com o código de autorização. Normal-
mente, o AS exigirá que o URI de redirecionamento do cliente seja pré-registrado
para evitar ataques de redirecionamento aberto.
Figura 7.5 Na concessão do código de autorização, o cliente primeiro redireciona
o navegador da Web do usuário para o terminal de autorização do AS. O AS então
autentica o usuário e pede consentimento para conceder acesso ao aplicativo. Se
aprovado, o AS redireciona o navegador da Web para um URI controlado pelo cli-
ente, incluindo um código de autorização. O cliente pode então chamar o end-
point de token AS para trocar o código de autorização por um token de acesso a
ser usado para acessar a API em nome do usuário.

DEFINIÇÃO Um redirecionamento abertovulnerabilidade é quando um servi-


dor pode ser induzido a redirecionar um navegador da Web para um URI sob o
controle do invasor. Isso pode ser usado para phishing porque inicialmente pa-
rece que o usuário está indo para um site confiável, apenas para ser redirecio-
nado para o invasor. Você deve exigir que todos os URIs de redirecionamento se-
jam pré-registrados por clientes confiáveis, em vez de redirecionar para qualquer
URI fornecido em uma solicitação.

Para um aplicativo da web, isso é simplesmente um caso de retornar um código


de status de redirecionamento HTTP, como 303 See Other, 6 com o URI para o end-
point de autorização no cabeçalho Location, como no exemplo a seguir:

HTTP/1.1 303 Veja outro


Localização: https://as.example.com/authorize?client_id=test ❶
➥ &scope=read_messages+post_message ❷
➥ &state=t9kWoBWsYjbsNwY0ACJj0A ❸
➥ &response_type=code ❹
➥ &redirect_uri=https://client.example.net/callback ❺

❶ O parâmetro client_id indica o cliente.

❷ O parâmetro escopo indica o escopo solicitado.


❸ Inclua um parâmetro de estado aleatório para evitar ataques CSRF.

❹ Use o parâmetro response_type para obter um código de autorização.

❺ O endpoint de redirecionamento do cliente

Para aplicativos móveis e de desktop, o cliente deve iniciar o navegador da web


do sistema para realizar a autorização. O conselho de melhores práticas mais re-
cente para aplicativos nativos ( https://tools.ietf.org/html/rfc8252 ) recomenda que
o navegador do sistema seja usado para isso, em vez de incorporar uma visualiza-
ção HTML no aplicativo. Isso evita que os usuários tenham que digitar suas cre-
denciais em uma interface do usuário sob o controle de um aplicativo de terceiros
e permite que os usuários reutilizem quaisquer cookies ou outros tokens de ses-
são que já tenham no navegador do sistema para evitar a necessidade de fazer lo-
gin novamente. Tanto o Android quanto o iOS suportam o uso do navegador do
sistema sem sair do aplicativo atual, fornecendo uma experiência de usuário se-
melhante ao uso de uma visualização da Web incorporada.
Figura 7.6 Um exemplo de página de consentimento OAuth2 indicando o nome do
cliente solicitando acesso e o escopo que ele requer. O usuário pode optar por per-
mitir ou negar a solicitação.

Uma vez que o usuário tenha se autenticado em seu navegador, o AS normal-


mente exibirá uma página informando ao usuário qual cliente está solicitando
acesso e o escopo que ele requer, como mostrado na figura 7.6. O usuário tem en-
tão a oportunidade de aceitar ou recusar a solicitação, ou possivelmente ajustar o
escopo de acesso que está disposto a conceder. Se o usuário aprovar, o AS emitirá
um redirecionamento HTTP para um URI controlado pelo aplicativo cliente com o
código de autorização e o state valor original como parâmetro de consulta:

HTTP/1.1 303 Veja outro


Localização: https://client.example.net/callback? ❶
➥ code=kdYfMS7H3sOO5y_sKhpdV6NFfik ❶
➥ &state=t9kWoBWsYjbsNwY0ACJj0A ❷
❶ O AS redireciona para o cliente com o código de autorização.

❷ Inclui o parâmetro state da requisição original.

Como o código de autorização está incluído nos parâmetros de consulta do redire-


cionamento, ele fica vulnerável a ser roubado por scripts maliciosos em execução
no navegador ou vazar em logs de acesso ao servidor, histórico do navegador ou
por meio do cabeçalho Referer HTTP. Para se proteger contra isso, o código de
autorização geralmente é válido apenas por um curto período de tempo e o AS ga-
rantirá que seja usado apenas uma vez. Se um invasor tentar usar um código rou-
bado depois que o cliente legítimo o tiver usado, o AS rejeitará a solicitação e re-
vogará todos os tokens de acesso já emitidos com esse código.

O cliente pode então trocar o código de autorização por um token de acesso cha-
mando o endpoint do token no AS. Ele envia o código de autorização no corpo de
uma requisição POST, utilizando a application/x-www-form-urlencoded co-
dificação utilizada para formulários HTML, com os seguintes parâmetros:

Indique o tipo de concessão do código de autorização que está sendo usado, in-
cluindo grant_ type=authorization_code .
Inclua o ID do cliente no client_id parâmetroou forneça credenciais de cli-
ente para identificar o cliente.
Inclua o URI de redirecionamento usado na solicitação original no redirect
_uri parâmetro.
Por fim, inclua o código de autorização como valor do code parâmetro.

Esta é uma chamada HTTPS direta do cliente para o AS, em vez de um redirecio-
namento no navegador da Web e, portanto, o token de acesso retornado ao cliente
é protegido contra roubo ou adulteração. Um exemplo de solicitação para o end-
point do token se parece com o seguinte:
POST /token HTTP/1.1
Host: as.example.com
Tipo de conteúdo: application/x-www-form-urlencoded
Autorização: Basic dGVzdDpwYXNzd29yZA== ❶

grant_type=authorization_code& ❷
code=kdYfMS7H3sOO5y_sKhpdV6NFfik& ❷
redirect_uri=https://client.example.net/callback ❸

❶ Forneça credenciais de cliente para um cliente confidencial.

❷ Inclua o tipo de concessão e o código de autorização.

❸ Forneça o URI de redirecionamento usado na solicitação original.

Se o código de autorização for válido e não tiver expirado, o AS responderá com o


token de acesso em uma resposta JSON, juntamente com alguns detalhes (opcio-
nais) sobre o escopo e o tempo de expiração do token:

HTTP/1.1 200 OK
Tipo de conteúdo: aplicativo/json

{
"access_token":"QdT8POxT2SReqKNtcRDicEgIgkk", ❶
"scope":"post_message read_messages", ❷
"token_type":"Portador",
"expires_in":3599} ❸

❶ O token de acesso

❷ O escopo do token de acesso, que pode ser diferente do solicitado


❸ O número de segundos até o token de acesso expirar

Se o cliente for confidencial, ele deverá se autenticar no endpoint do token ao tro-


car o código de autorização. No caso mais comum, isso é feito incluindo o ID do
cliente e o segredo do cliente como nome de usuário e senha usando a autentica-
ção básica HTTP, mas métodos alternativos de autenticação são permitidos, como
usar um certificado de cliente JWT ou TLS. Autenticar-se no endpoint do token im-
pede que um cliente mal-intencionado use um código de autorização roubado
para obter um token de acesso.

Assim que o cliente obtiver um token de acesso, ele poderá usá-lo para acessar as
APIs no servidor de recursos, incluindo-o em um Authorization: Bearer ca-
beçalho, exatamente como você fez nos capítulos anteriores. Você verá como vali-
dar um token de acesso em sua API na seção 7.4.

7.3.1 Redirecionar URIs para diferentes tipos de clientes

oA escolha do URI de redirecionamento é uma importante consideração de segu-


rança para um cliente. Para clientes públicos que não se autenticam no AS, o URI
de redirecionamento é a única medida pela qual o AS pode ter certeza de que o
código de autorização foi enviado ao cliente correto. Se o URI de redireciona-
mento for vulnerável à interceptação, um invasor poderá roubar códigos de
autorização.

Para um aplicativo da Web tradicional, é simples criar um ponto de extremidade


dedicado a ser usado para o URI de redirecionamento para receber o código de
autorização. Para um aplicativo de página única, o URI de redirecionamento deve
ser o URI do aplicativo do qual o JavaScript do lado do cliente pode extrair o có-
digo de autorização e fazer uma solicitação CORS para o endpoint do token.

Para aplicativos móveis, há duas opções principais:


O aplicativo pode registrar um esquema de URI de uso privado com o sistema
operacional móvel, como myapp:/ /callback . Quando o AS redirecionar
para myapp:/ / callback?code=... o navegador da Web do sistema, o sis-
tema operacional iniciará o aplicativo nativo e passará o URI de retorno de cha-
mada. O aplicativo nativo pode então extrair o código de autorização desse URI
e chamar o endpoint do token.
Uma alternativa é registrar uma parte do caminho no domínio web do produ-
tor do aplicativo. Por exemplo, seu aplicativo pode se registrar no sistema ope-
racional para o qual tratará todas as solicitações para https:/
/example.com/app/callback . Quando o AS redirecionar para esse ponto de
extremidade HTTPS, o sistema operacional móvel iniciará o aplicativo nativo
da mesma forma que para um esquema de URI de uso privado. O Android
chama isso de App Link ( https://developer.android.com/training/app-links/ ),
enquanto no iOS eles são conhecidos como Links Universais(
https://developer.apple.com/ios/universal-links/ ).

Uma desvantagem dos esquemas de URI de uso privado é que qualquer aplicativo
pode se registrar para lidar com qualquer esquema de URI, portanto, um aplica-
tivo mal-intencionado pode registrar o mesmo esquema que seu cliente legítimo.
Se um usuário tiver o aplicativo mal-intencionado instalado, o redirecionamento
do AS com um código de autorização pode fazer com que o aplicativo mal-intenci-
onado seja ativado em vez de seu aplicativo legítimo. URIs de redirecionamento
HTTPS registrados no Android (App Links) e iOS (Universal Links) evitam esse
problema porque um aplicativo só pode reivindicar parte do espaço de endereço
de um site se o site em questão publicar um documento JSON explicitamente con-
cedendo permissão a esse aplicativo. Por exemplo, para permitir que seu aplica-
tivo iOS manipule solicitações para https://example.com/app/callback, você deve
publicar o seguinte arquivo JSON em https://example.com/.well-known/apple-app-
site -Associação:
{
"aplicativos": {
"aplicativos": [],
"detalhes": [
{ "appID": "9JA89QQLNQ.com.example.myapp", ❶
"paths": ["/app/callback"] }] ❷
}
}

❶ O ID do seu aplicativo na Apple App Store

❷ Os caminhos no servidor que o aplicativo pode interceptar

O processo é semelhante para aplicativos Android. Isso evita que um aplicativo


mal-intencionado reivindique o mesmo URI de redirecionamento, e é por isso que
os redirecionamentos HTTPS são recomendados pelo documento OAuth Native
Application Best Common Practice ( https://tools.ietf.org/ html/rfc8252#section-7.2
).

Para aplicativos de desktop e de linha de comando, tanto o Mac OS X quanto o


Windows oferecem suporte ao registro de esquemas de URI de uso privado, mas
não reivindicam URIs HTTPS no momento da redação. Para aplicativos e scripts
não nativos que não podem registrar um esquema de URI privado, a recomenda-
ção é que o aplicativo inicie um servidor web temporário escutando no disposi-
tivo de loopback local (isto é, http://127.0.0.1) em uma porta aleatória e usa isso
como seu URI de redirecionamento. Uma vez que o código de autorização é rece-
bido do AS, o cliente pode desligar o web temporárioservidor.
7.3.2 Troca de código de endurecimento com PKCE

Antes da Após a invenção dos URIs de redirecionamento HTTPS reivindicados, os


aplicativos móveis que usam esquemas de URI de uso privado ficaram vulnerá-
veis ​à interceptação de código por um aplicativo mal-intencionado registrando o
mesmo esquema de URI, conforme descrito na seção anterior. Para se proteger
contra esse ataque, o grupo de trabalho OAuth desenvolveu o padrão PKCE (Proof
Key for Code Exchange; https://tools.ietf.org/html/rfc7636), pronuncia-se “pixy”.
Desde então, a análise formal do protocolo OAuth identificou alguns ataques teó-
ricos contra o fluxo do código de autorização. Por exemplo, um invasor pode ob-
ter um código de autorização genuíno interagindo com um cliente legítimo e, em
seguida, usando um ataque XSS contra uma vítima para substituir seu código de
autorização pelo do invasor. Tal ataque seria muito difícil de realizar, mas é teori-
camente possível. Portanto, é recomendável que todos os tipos de clientes usem
PKCE para fortalecer o fluxo do código de autorização.

A maneira como o PKCE funciona para um cliente é bastante simples. Antes de o


cliente redirecionar o usuário para o terminal de autorização, ele gera outro valor
aleatório, conhecido como verificador de código PKCE. Esse valor deve ser gerado
com alta entropia, como um valor de 32 bytes de um SecureRandom objetoem
Java; o padrão PKCE requer que o valor codificado tenha pelo menos 43 caracte-
res e um máximo de 128 caracteres de um conjunto restrito de caracteres. O cli-
ente armazena o verificador de código localmente, juntamente com o parâmetro
de estado. Em vez de enviar esse valor diretamente para o AS, o cliente primeiro
faz um hash 7 usando a função hash criptográfica SHA-256 para criar um desafio
de código(lista 7.4). O cliente então adiciona o desafio de código como outro parâ-
metro de consulta ao redirecionar para o terminal de autorização.

Listagem 7.4 Calculando um desafio de código PKCE


String addPkceChallenge(spark.Request request,
String authorizeRequest) lança Exception {

var secureRandom = new java.security.SecureRandom();


var encoder = java.util.Base64.getUrlEncoder().withoutPadding();

var verificadorBytes = novo byte[32]; ❶


secureRandom.nextBytes(verifierBytes); ❶
var verificador = encoder.encodeToString(verifierBytes); ❶

request.session(true).attribute("verificador", verificador); ❷

var sha256 = java.security.MessageDigest.getInstance("SHA-256"); ❸


var challenge = encoder.encodeToString( ❸
sha256.digest(verifier.getBytes("UTF-8"))); ❸
return autorizaRequest +
"&code_challenge=" + desafio + ❹
"&code_challenge_method=S256"; ❹
}

❶ Crie uma string de verificação de código aleatório.

❷ Armazene o verificador em um cookie de sessão ou outro armazenamento local.

❸ Crie um desafio de código como o hash SHA-256 da string do verificador de código.

❹ Inclua o desafio de código no redirecionamento para o terminal de autorização do


AS.

Posteriormente, quando o cliente troca o código de autorização no endpoint do to-


ken, ele envia o verificador de código original (sem hash) na solicitação. O AS ve-
rificará se o hash SHA-256 do verificador de código corresponde ao desafio de có-
digo recebido na solicitação de autorização. Se forem diferentes, ele rejeita a soli-
citação. O PKCE é muito seguro, porque mesmo que um invasor intercepte o redi-
recionamento para o AS e o redirecionamento de volta com o código de autoriza-
ção, ele não poderá usar o código porque não pode calcular o verificador de có-
digo correto. Muitas bibliotecas de cliente OAuth2 calcularão automaticamente os
verificadores e desafios de código PKCE para você, e isso melhora significativa-
mente a segurança da concessão do código de autorização, portanto, você sempre
deve usá-lo quando possível. Servidores de autorização que não suportam PKCE
devem ignorar os parâmetros de consulta adicionais, padrão.

7.3.3 Atualizar tokens

Dentroalém de um token de acesso, o AS também pode emitir ao cliente um token


de atualização ao mesmo tempo. O token de atualização é retornado como outro
campo na resposta JSON do endpoint do token, como no exemplo a seguir:

$ curl -d 'grant_type=password
➥ &scope=read_messages+post_message
➥ &username=demo&password=changeit'
➥ -u test:password
➥ https://as.example.com:8443/oauth2/access_token
{
"access_token":"B9KbdZYwajmgVxr65SzL-z2Dt-4",
"refresh_token":"sBac5bgCLCjWmtjQ8Weji2mCrbI", ❶
"scope":"post_message read_messages",
"token_type":"Bearer","expires_in":3599}

❶ Um token de atualização
Quando o token de acesso expira, o cliente pode usar o token de atualização para
obter um novo token de acesso do AS sem que o proprietário do recurso precise
aprovar a solicitação novamente. Como o token de atualização é enviado apenas
por um canal seguro entre o cliente e o AS, ele é considerado mais seguro do que
um token de acesso que pode ser enviado para várias APIs diferentes.

DEFINIÇÃO Um cliente pode usar um token de atualização para obter um novo


token de acesso quando o original expirar. Isso permite que um AS emita tokens
de acesso de curta duração sem que os clientes precisem solicitar ao usuário um
novo token toda vez que ele expirar.

Ao emitir um token de atualização, o AS pode limitar o tempo de vida dos tokens


de acesso. Isso tem um benefício de segurança menor porque, se um token de
acesso for roubado, ele só poderá ser usado por um curto período de tempo. Mas,
na prática, muitos danos podem ser causados, mesmo em um curto espaço de
tempo, por um ataque automatizado, como o ataque do Facebook discutido no ca-
pítulo 6 ( https://newsroom.fb.com/news/2018/09/security -atualizar/). O principal
benefício dos tokens de atualização é permitir o uso de tokens de acesso sem es-
tado, como JWTs. Se o token de acesso for de curta duração, o cliente será forçado
a atualizar periodicamente o token no AS, oferecendo uma oportunidade para o
token ser revogado sem que o AS mantenha uma grande lista de bloqueio. A com-
plexidade da revogação é efetivamente enviada ao cliente, que agora deve lidar
com a atualização periódica de seus tokens de acesso.

Para atualizar um token de acesso, o cliente chama o endpoint do token AS pas-


sando o token de atualização, usando a concessão do token de atualização e envi-
ando o token de atualização e quaisquer credenciais do cliente, como no exemplo
a seguir:
$ curl -d 'grant_type=refresh_token ❶
➥ &refresh_token=sBac5bgCLCjWmtjQ8Weji2mCrbI' ❶
➥ -u test:password ❷
➥ https://as.example.com:8443/oauth2/access_token
{
"access_token":"snGxj86QSYB7Zojt3G1b2aXN5UM", ❸
"scope":"post_message read_messages",
"token_type":"Bearer","expires_in":3599}

❶ Use a concessão do token de atualização e forneça o token de atualização.

❷ Inclua as credenciais do cliente se estiver usando um cliente confidencial.

❸ O AS retorna um novo token de acesso.

Muitas vezes, o AS pode ser configurado para emitir um novo token de atualiza-
ção ao mesmo tempo (revogando o antigo), garantindo que cada token de atuali-
zação seja usado apenas uma vez. Isso pode ser usado para detectar roubo de to-
ken de atualização: quando o invasor usa o token de atualização, ele para de fun-
cionar para olegítimocliente.

questionário

4. Qual tipo de URI deve ser preferido como URI de redirecionamento para um cli-
ente móvel?
1. Um URI HTTPS reivindicado
2. Um esquema de URI de uso privado, como myapp:/ /cb
5. Verdadeiro ou falso: A concessão do código de autorização sempre deve ser
usada em combinação com PKCE.

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


7.4 Validando um token de acesso

Agoraque você aprendeu como obter um token de acesso para um cliente, precisa
aprender como validar o token em sua API. Nos capítulos anteriores, era simples
procurar um token no banco de dados de tokens local. Para OAuth2, isso não é
mais tão simples quando os tokens são emitidos pelo AS e não pela API. Embora
você possa compartilhar um banco de dados de token entre o AS e cada API, isso
não é desejável porque compartilhar o acesso ao banco de dados aumenta o risco
de comprometimento. Um invasor pode tentar acessar o banco de dados por meio
de qualquer um dos sistemas conectados, aumentando a superfície de ataque. Se
apenas uma API conectada ao banco de dados tiver uma vulnerabilidade de inje-
ção de SQL, isso comprometeria a segurança de todos.

Originalmente, o OAuth2 não fornecia uma solução para esse problema e deixava
para o AS e os servidores de recursos decidir como coordenar a validação dos to-
kens. Isso mudou com a publicação do padrão OAuth2 Token Introspection (
https://tools.ietf .org/html/rfc7662 ) em 2015, que descreve um endpoint HTTP pa-
drão no AS que o RS pode chamar para validar um token de acesso e recuperar
detalhes sobre seu escopo e proprietário do recurso. Outra solução popular é usar
JWTs como formato para tokens de acesso, permitindo que o RS valide localmente
o token e extraia os detalhes necessários das declarações JSON incorporadas. Você
aprenderá como usar ambos os mecanismos nesta seção.

7.4.1 Introspecção de token

Paravalidar um token de acesso usando introspecção de token, basta fazer uma


solicitação POST para o endpoint de introspecção do AS, passando o token de
acesso como um parâmetro. Você pode descobrir o endpoint de introspecção
usando o método na seção 7.2.3 se o AS suportar a descoberta. O AS geralmente
exigirá que sua API (atuando como o servidor de recursos) se registre como um
tipo especial de cliente e receba as credenciais do cliente para chamar o endpoint.
Os exemplos nesta seção assumirão que o AS requer autenticação HTTP básica
porque este é o requisito mais comum, mas você deve verificar a documentação
do seu AS para determinar como o RS deve autenticar.

DICA Para evitar problemas históricos com conjuntos de caracteres ambíguos, o


OAuth requer que as credenciais de autenticação HTTP Basic sejam primeiro codi-
ficadas em URL (como UTF-8) antes de serem codificadas em Base64.

A Listagem 7.5 mostra o construtor e as importações para um novo armazena-


mento de token que usará a introspecção de token OAuth2 para validar um token
de acesso. Você implementará os métodos restantes no restante desta seção.
o create e revoke Os métodos lançam uma exceção, desativando efetivamente
os endpoints de login e logout na API, forçando os clientes a obter tokens de
acesso do AS. A nova loja usa o URI do endpoint de introspecção de token, junta-
mente com as credenciais a serem usadas para autenticação. As credenciais são
codificadas em um cabeçalho de autenticação HTTP Basic pronto para ser usado.
Navegue até src/main/java/com/manning/apisecurityinaction/token e crie um
novo arquivo chamado OAuth2TokenStore.java. Digite o conteúdo da listagem 7.5
em seu editor e salve o novo arquivo.

Listagem 7.5 O armazenamento de token OAuth2

pacote com.manning.apisecurityinaction.token;
import org.json.JSONObject;
import spark.Request;
importar java.io.IOException;
importar java.net.*;
importar java.net.http.*;
importar java.net.http.HttpRequest.BodyPublishers;
importar java.net.http.HttpResponse.BodyHandlers;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
importar java.util.*;
importar estático java.nio.charset.StandardCharsets.UTF_8;
public class OAuth2TokenStore implementa SecureTokenStore {

URI final privado introspectionEndpoint; ❶


autorização de string final privada;

final privado HttpClient httpClient;

public OAuth2TokenStore(URI introspectionEndpoint, ❶


String clientId, String clientSecret) {
this.introspectionEndpoint = introspectionEndpoint; ❶

var credenciais = URLEncoder.encode(clientId, UTF_8) + ":" + ❷


URLEncoder.encode(clientSecret, UTF_8); ❷
this.authorization = "Basic " + Base64.getEncoder() ❷
.encodeToString(credentials.getBytes(UTF_8)); ❷

this.httpClient = HttpClient.newHttpClient();
}

@Sobrepor
public String create(Solicitação de solicitação, Token token) {
lançar novo UnsupportedOperationException(); ❸
}

@Sobrepor
public void revoke(Solicitação de solicitação, String tokenId) {
lançar novo UnsupportedOperationException(); ❸
}
}
❶ Injete o URI do endpoint de introspecção de token.

❷ Crie credenciais HTTP básicas a partir do ID e segredo do cliente.

❸ Lance uma exceção para desativar o login e logout diretos.

Para validar um token, você precisa fazer uma solicitação POST para o endpoint
de introspecção que passa o token. Você pode usar a biblioteca cliente HTTP em
java.net.http, que foi adicionada no Java 11 (para versões anteriores, você pode
usar Apache HttpComponents, https://hc.apache.org/httpcomponents-client-ga/).
Como o token não é confiável antes da chamada, primeiro você deve validá-lo
para garantir que esteja em conformidade com a sintaxe permitida para tokens
de acesso. Como você aprendeu no capítulo 2, é importante sempre validar todas
as entradas, e isso é especialmente importante quando a entrada for incluída em
uma chamada para outro sistema. O padrão não especifica um tamanho máximo
para tokens de acesso, mas você deve impor um limite de cerca de 1 KB ou menos,
o que deve ser suficiente para a maioria dos formatos de token (se o token de
acesso for um JWT, ele pode ficar muito grande e você pode necessidade de au-
mentar esse limite). O token deve ser codificado em URL para incluir no corpo do
POST como o token parâmetro. É importante codificar adequadamente os parâ-
metros ao chamar outro sistema para impedir que um invasor manipule o con-
teúdo da solicitação (consulte a seção 2.6 do capítulo 2). Você também pode incluir
um token_ type_hint parâmetropara indicar que é um token de acesso, mas
isso é opcional.

DICA Para evitar fazer uma chamada HTTP toda vez que um cliente usa um to-
ken de acesso com sua API, você pode armazenar em cache a resposta por um
curto período de tempo, indexada pelo token. Quanto mais você armazenar em
cache a resposta, mais tempo levará para sua API descobrir que um token foi re-
vogado, portanto, você deve equilibrar o desempenho com a segurança com base
em seu modelo de ameaça.

Se a chamada de introspecção for bem-sucedida, o AS retornará uma resposta


JSON indicando se o token é válido e os metadados sobre o token, como o proprie-
tário e o escopo do recurso. O único campo obrigatório nesta resposta é um ac-
tive campo booleano, que indica se o token deve ser considerado válido. Se for o
false caso, o token deve ser rejeitado, como na listagem 7.6. Você processará o
restante da resposta JSON em breve, mas, por enquanto, abra
OAuth2TokenStore.java em seu editor novamente e adicione a implementação do
método read da listagem.

Listagem 7.6 Introspecção de um token de acesso

@Sobrepor
public Opcional<Token> read(Request request, String tokenId) {
if (!tokenId.matches("[\\x20-\\x7E]{1,1024}")) { ❶
return Opcional.vazio();
}

var form = "token=" + URLEncoder.encode(tokenId, UTF_8) + ❷


"&token_type_hint=access_token"; ❷

var httpRequest = HttpRequest.newBuilder()


.uri(introspectionEndpoint)
.header("Tipo de conteúdo", "aplicativo/x-www-form-urlencoded")
.header("Autorização", autorização) ❸
.POST(BodyPublishers.ofString(formulário))
.construir();

tentar {
var httpResponse = httpClient.send(httpRequest,
BodyHandlers.ofString());

if (httpResponse.statusCode() == 200) {
var json = new JSONObject(httpResponse.body());

if (json.getBoolean("active")) { ❹
return processResponse(json); ❹
}
}
} catch (IOException e) {
lança nova RuntimeException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
lança nova RuntimeException(e);
}

return Opcional.vazio();
}

❶ Valide o token primeiro.

❷ Codifique o token no corpo do formulário POST.

❸ Chame o endpoint de introspecção usando suas credenciais de cliente.

❹ Verifique se o token ainda está ativo.

Vários campos opcionais são permitidos na resposta JSON, incluindo todas as de-
clarações JWT válidas (consulte o capítulo 6). Os campos mais importantes estão
listados na tabela 7.1. Como todos esses campos são opcionais, você deve estar
preparado para a falta deles. Este é um aspecto infeliz da especificação, porque
muitas vezes não há alternativa senão rejeitar um token se seu escopo ou proprie-
tário do recurso não puder ser estabelecido. Felizmente, a maioria dos softwares
AS gera valores sensatos para esses campos.

Tabela 7.1 Campos de resposta de introspecção de token

Campo Descrição

scope O escopo do token como uma string. Se vários escopos forem


especificados, eles serão separados por espaços, como
"read_messages post_message" .

sub Um identificador para o proprietário do recurso (assunto) do


token. Este é um identificador exclusivo, não necessariamente
legível por humanos.

username Um nome de usuário legível para o proprietário do recurso.

client_id O ID do cliente que solicitou o token.

exp O tempo de expiração do token, em segundos, a partir da


época do UNIX.

A Listagem 7.7 mostra como processar os campos JSON restantes extraindo o pro-
prietário do recurso do sub campo, o tempo de expiração do exp campo e o es-
copo do scope campo. Você também pode extrair outros campos de interesse,
como o client_id , que podem ser informações úteis para adicionar aos logs de
auditoria. Abra OAuth2TokenStore.java novamente e adicione o processRes-
ponse métododa listagem.

Listagem 7.7 Processando a resposta de introspecção


private Opcional<Token> processResponse(resposta JSONObject) {
var expiração = Instant.ofEpochSecond(response.getLong("exp")); ❶
var assunto = resposta.getString("sub"); ❶

var token = new Token(expiração, assunto);

token.attributes.put("scope", response.getString("scope")); ❶
token.attributes.put("client_id", ❶
response.optString("client_id")); ❶

return Opcional.of(token);
}

❶ Extraia atributos de token dos campos relevantes na resposta.

Embora você tenha usado o sub campo para extrair um ID para o usuário, isso
nem sempre pode ser apropriado. O assunto autenticado de um token precisa cor-
responder às entradas nas tabelas users e permissions no banco de dados que
definem as listas de controle de acesso para os espaços sociais do Natter. Se não
corresponderem, as solicitações de um cliente serão negadas, mesmo que tenham
um token de acesso válido. Você deve verificar a documentação do seu AS para
ver qual campo usar para corresponder aos seus IDs de usuário existentes.

Agora você pode alternar a API Natter para usar tokens de acesso OAuth2 alte-
rando o TokenStore em Main.java para usar o OAuth2TokenStore , passando o
URI do endpoint de introspecção de token do seu AS e o ID do cliente e o segredo
que você registrou para a API do Natter (consulte o apêndice A para obter
instruções):
var introspectionEndpoint =
URI.create("https://as.example.com:8443/oauth2/introspect");
SecureTokenStore tokenStore = new OAuth2TokenStore( ❶
introspectionEndpoint, clientId, clientSecret); ❶
var tokenController = new TokenController(tokenStore);

❶ Construa o armazenamento de token, apontando para o seu AS.

Você deve certificar-se de que o AS e a API tenham os mesmos usuários e que o AS


comunique o nome de usuário à API nos campos sub ou username da resposta
de introspecção. Caso contrário, a API pode não conseguir corresponder o nome
de usuário retornado da introspecção do token às entradas em suas listas de con-
trole de acesso (capítulo 3). Em muitos ambientes corporativos, os usuários não
serão armazenados em um banco de dados local, mas sim em um diretório LDAP
compartilhado mantido pelo departamento de TI da empresa ao qual tanto o AS
quanto a API têm acesso, conforme mostrado na figura 7.7.
Figura 7.7 Em muitos ambientes, o AS e a API terão acesso a um diretório LDAP
corporativo contendo detalhes de todos os usuários. Nesse caso, o AS precisa co-
municar o nome de usuário à API para que ela possa encontrar a entrada de usuá-
rio correspondente no LDAP e em suas próprias listas de controle de acesso.

Em outros casos, o AS e a API podem ter bancos de dados de usuários diferentes


que usam formatos de nome de usuário diferentes. Nesse caso, a API precisará de
alguma lógica para mapear o nome de usuário retornado pela introspecção de to-
ken em um nome de usuário que corresponda ao banco de dados local e às ACLs.
Por exemplo, se o AS retornar o endereço de e-mail do usuário, isso pode ser
usado para procurar um usuário correspondente no banco de dados de usuários
local. Em arquiteturas mais fracamente acopladas, a API pode depender inteira-
mente das informações retornadas do endpoint de introspecção de token e não
ter acesso a um banco de dados do usuário.

Assim que o AS e a API estiverem na mesma página sobre nomes de usuário, você
pode obter um token de acesso do AS e usá-lo para acessar a API do Natter, como
no exemplo a seguir usando a concessão ROPC:

$ curl -u test:password \ ❶
-d 'grant_type=password&scope=create_space+post_message ❶
➥ &username=demo&password=changeit' \ ❶
https://openam.example.com:8443/openam/oauth2/access_token
{"access_token":"_Avja0SO-6vAz-caub31eh5RLDU",
"scope":"post_message create_space",
"token_type":"Bearer","expires_in":3599}
$ curl -H 'Tipo de conteúdo: aplicativo/json' \
-H 'Autorização: Portador _Avja0SO-6vAz-caub31eh5RLDU' \ ❷
-d '{"name":"test","owner":"demo"}' https://localhost:4567/spaces
{"nome":"teste","uri":"/espaços/1"}

❶ Obtenha um token de acesso usando a concessão ROPC.

❷ Use o token de acesso para realizar ações com a API do Natter.

Tentar executar uma ação que não é permitida pelo escopo do token de acesso re-
sultará em um erro 403 Proibido devido aos filtros de controle de acesso que você
adicionou no início destecapítulo:

$ curl -i -H 'Autorização: Portador _Avja0SO-6vAz-caub31eh5RLDU' \


https://localhost:4567/spaces/1/messages
HTTP/1.1 403 Proibido ❶
Data: segunda-feira, 01 de julho de 2019 10:22:17 GMT
WWW-Autenticar: Portador
➥ error="insufficient_scope",scope="list_messages" ❷

❶ O pedido é proibido.

❷ A mensagem de erro informa ao cliente o escopo necessário.

7.4.2 Protegendo a configuração do cliente HTTPS

Porquea API depende inteiramente do AS para informar se um token de acesso é


válido e o escopo de acesso que deve conceder, é fundamental que a conexão en-
tre os dois seja segura. Embora essa conexão deva ser sempre por HTTPS, as confi-
gurações de conexão padrão usadas pelo Java não são tão seguras quanto pode-
riam ser:

As configurações padrão confiam em certificados de servidor assinados por


qualquer uma das principais autoridades públicas de certificação(CAs). Nor-
malmente, o AS será executado em sua própria rede interna e emitido com um
certificado por uma CA privada para sua organização, portanto, não é necessá-
rio confiar em todas essas CAs públicas.
As configurações padrão de TLS incluem uma ampla variedade de conjuntos de
cifras e versões de protocolo para compatibilidade máxima. As versões mais
antigas do TLS e alguns conjuntos de cifras têm falhas de segurança conhecidas
que devem ser evitadas sempre que possível. Você deve desativar essas opções
menos seguras e reativá-las apenas se precisar se comunicar com um servidor
antigo que não pode ser atualizado.

Conjuntos de cifras TLS


Um conjunto de cifras TLSé uma coleção de algoritmos criptográficos que traba-
lham juntos para criar o canal seguro entre um cliente e um servidor. Quando
uma conexão TLS é estabelecida pela primeira vez, o cliente e o servidor execu-
tam um handshake, em que o servidor é autenticado para o cliente, o cliente opci-
onalmente é autenticado para o servidor e eles concordam com uma chave de ses-
são a ser usada para mensagens subsequentes. O conjunto de cifras especifica os
algoritmos a serem usados ​para autenticação, troca de chaves e a cifra de bloco e
o modo de operação a ser usado para criptografar mensagens. O conjunto de ci-
fras a ser usado é negociado como a primeira parte do handshake.

Por exemplo, o conjunto de cifras TLS 1.2


TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 especifica que as duas partes usa-
rão oCurva Elíptica Diffie-Hellman(ECDH) algoritmo de acordo de chave (usando
chaves efêmeras, indicadas pelo E final), com assinaturas RSA para autenticação,
e a chave de sessão acordada será usada para criptografar mensagens usando AES
no modo Galois/Contador. (SHA-256 é usado como parte do contrato de chave.)

No TLS 1.3, os conjuntos de cifras especificam apenas a cifra de bloco e a função


hash usada, como TLS_AES_128_GCM_SHA256 . A troca de chaves e os algoritmos
de autenticação são negociados separadamente.
A versão mais recente e segura do TLS é a versão 1.3, lançada em agosto de 2018.
Ela substituiu o TLS 1.2, lançado exatamente uma década antes. Embora o TLS 1.3
seja uma melhoria significativa em relação às versões anteriores do protocolo,
ainda não é tão amplamente adotado que o suporte ao TLS 1.2 possa ser descar-
tado completamente. O TLS 1.2 ainda é um protocolo muito seguro, mas para se-
gurança máxima, você deve preferir conjuntos de cifras que ofereçam sigilo de
encaminhamentoe evite algoritmos mais antigos que usam AES no modo CBC,
pois são mais propensos a ataques. A Mozilla fornece recomendações para opções
seguras de configuração TLS ( https://wiki.mozilla.org/Security/Server_Side_TLS ),
junto com uma ferramenta para gerar automaticamente arquivos de configura-
ção para vários servidores web, balanceadores de carga e proxies reversos. A con-
figuração usada nesta seção é baseada nas configurações intermediárias do Mo-
zilla. Se você sabe que seu software AS é compatível com TLS 1.3, pode optar pelas
configurações modernas e remover o suporte a TLS 1.2.

DEFINIÇÃO Um conjunto de cifras oferece sigilo de encaminhamentose a confi-


dencialidade dos dados transmitidos usando esse conjunto de cifras for protegida,
mesmo que uma ou ambas as partes sejam comprometidas posteriormente. Todos
os conjuntos de cifras fornecem sigilo de encaminhamento no TLS 1.3. No TLS 1.2,
esses conjuntos de cifras começam com TLS_ECDHE_ ou TLS_DHE_ .

Para configurar a conexão para confiar apenas na CA que emitiu o certificado do


servidor usado pelo seu AS, você precisa criar
um javax.net.ssl.TrustManager que foi inicializado com um KeyStore que
contém apenas aquele certificado de CA. Por exemplo, se você estiver usando o
mkcert utilitáriodo capítulo 3 para gerar o certificado para seu AS, você pode
usar o seguinte comando para importar o certificado CA raiz para um keystore:

$ keytool -import -keystore as.example.com.ca.p12 \


-alias ca -file "$(mkcert -CAROOT)/rootCA.pem"

Isso perguntará se você deseja confiar no certificado CA raiz e, em seguida, solici-


tará uma senha para o novo armazenamento de chaves. Aceite o certificado e di-
gite uma senha adequada e, em seguida, copie o keystore gerado no diretório raiz
do projeto Natter.

cadeias de certificados
Ao configurar o armazenamento confiável para seu cliente HTTPS, você pode op-
tar por confiar diretamente no certificado do servidor para esse servidor. Embora
isso pareça mais seguro, significa que sempre que o servidor alterar seu certifi-
cado, o cliente precisará ser atualizado para confiar no novo. Muitos certificados
de servidor são válidos por apenas 90 dias. Se o servidor for comprometido, o cli-
ente continuará confiando no certificado comprometido até que seja atualizado
manualmente para removê-lo do armazenamento confiável.

Para evitar esses problemas, o certificado do servidor é assinado por uma CA, que
possui um certificado (autoassinado). Quando um cliente se conecta ao servidor,
ele recebe o certificado atual do servidor durante o handshake. Para verificar se
esse certificado é genuíno, ele procura o certificado de CA correspondente no ar-
mazenamento confiável do cliente e verifica se o certificado do servidor foi assi-
nado por essa CA e se não expirou ou foi revogado.

Na prática, o certificado do servidor geralmente não é assinado diretamente pela


CA. Em vez disso, a CA assina certificados para uma ou mais CAs intermediárias,
que assinam os certificados do servidor. O cliente pode, portanto, ter que verificar
uma cadeia de certificados até encontrar um certificado de uma CA raiz na qual
ele confia diretamente. Como os próprios certificados de CA podem ser revogados
ou expirar, em geral, o cliente pode ter que considerar várias cadeias de certifica-
dos possíveis antes de encontrar uma válida. A verificação de uma cadeia de certi-
ficados é complexa e propensa a erros com muitos detalhes sutis, portanto, você
sempre deve usar uma biblioteca madura para fazer isso.
Em Java, as configurações gerais de TLS podem ser definidas explicitamente
usando a javax.net.ssl.SSLParameters classe 8 (Listagem 7.8). Primeiro,
construa uma nova instância da classe e, em seguida, use os métodos setter, como
setCipherSuites(String[]) os que permitem versões TLS e conjuntos de ci-
fras. Os parâmetros configurados podem então ser passados ​ao construir o Http-
Client objeto. Abra OAuth2TokenStore.java em seu editor e atualize o construtor
para configurar o TLS segurodefinições.

Listagem 7.8 Protegendo a conexão HTTPS

importar javax.net.ssl.*;
importar java.security.*;
importar java.net.http.*;
var sslParams = new SSLParameters();
sslParams.setProtocols( ❶
new String[] { "TLSv1.3", "TLSv1.2" }); ❶
sslParams.setCipherSuites(new String[] {
"TLS_AES_128_GCM_SHA256", ❷
"TLS_AES_256_GCM_SHA384", ❷
"TLS_CHACHA20_POLY1305_SHA256", ❷

"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", ❸
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", ❸
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", ❸ "TLS_ECDHE_RSA_WITH_AES_2
", ❸ "
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", ❸
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" ❸
});
sslParams.setUseCipherSuitesOrder(true);
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
tentar {
var trustCerts = KeyStore.getInstance("PKCS12"); ❹
TrustedCerts.load( ❹
new FileInputStream("as.example.com.ca.p12"), ❹
"changeit".toCharArray()); ❹
var tmf = TrustManagerFactory.getInstance("PKIX"); ❹
tmf.init(trustedCerts); ❹
var sslContext = SSLContext.getInstance("TLS"); ❹
sslContext.init(null, tmf.getTrustManagers(), null); ❹
this.httpClient = HttpClient.newBuilder()
.sslParameters(sslParams) ❺
.sslContext(sslContext) ❺
.construir();
} catch (GeneralSecurityException | IOException e) {
lança nova RuntimeException(e);
}

❶ Permitir apenas TLS 1.2 ou TLS 1.3.

❷ Configurar conjuntos de cifras seguras para TLS 1.3. . .

❸ . . . e para TLS 1.2.

❹ O SSLContext deve ser configurado para confiar apenas na CA usada pelo seu AS.

❺ Inicialize o HttpClient com os parâmetros TLS escolhidos.

7.4.3 Revogação de token

Apenasquanto à introspecção de token, existe um padrão OAuth2 para revogar


um token de acesso ( https://tools.ietf.org/html/rfc7009 ). Embora isso possa ser
usado para implementar o revoke método no OAuth2TokenStore , o padrão per-
mite apenas que o cliente que recebeu um token o revogue, portanto, o RS (a API
do Natter, neste caso) não pode revogar um token em nome de um cliente. Os cli-
entes devem chamar diretamente o AS para revogar um token, assim como fazem
para obter um token de acesso em primeiro lugar.

A revogação de um token segue o mesmo padrão da introspecção de token: o cli-


ente faz uma solicitação POST para um endpoint de revogação no AS, passando o
token no corpo da solicitação, conforme mostrado na Listagem 7.9. O cliente deve
incluir suas credenciais de cliente para autenticar a solicitação. Apenas um código
de status HTTP é retornado, então não há necessidade de analisar a
respostacorpo.

Listagem 7.9 Revogando um token de acesso OAuth

pacote com.manning.apisecurityinaction;

importar java.net.*;
importar java.net.http.*;
importar java.net.http.HttpResponse.BodyHandlers;
importar java.util.Base64;

importar estático java.nio.charset.StandardCharsets.UTF_8;

public class RevokeAccessToken {

URI final estático privado revocationEndpoint =


URI.create("https://as.example.com:8443/oauth2/token/revoke");

public static void main(String...args) lança exceção {

if (args.length != 3) {
lançar novo IllegalArgumentException(
"Token RevokeAccessToken clientId clientSecret");
}

var clientId = args[0];


var clientSecret = args[1];
var símbolo = args[2];
var credenciais = URLEncoder.encode(clientId, UTF_8) + ❶
":" + URLEncoder.encode(clientSecret, UTF_8); ❶
var authorization = "Basic " + Base64.getEncoder() ❶
.encodeToString(credentials.getBytes(UTF_8)); ❶

var httpClient = HttpClient.newHttpClient();

var form = "token=" + URLEncoder.encode(token, UTF_8) + ❷


"&token_type_hint=access_token"; ❷

var httpRequest = HttpRequest.newBuilder()


.uri(revocationEndpoint)
.header("Tipo de conteúdo",
"aplicativo/x-www-form-urlencoded")
.header("Autorização", autorização) ❸
.POST(HttpRequest.BodyPublishers.ofString(form))
.construir();

httpClient.send(httpRequest, BodyHandlers.discarding());
}
}

❶ Codifique as credenciais do cliente para autenticação básica.

❷ Crie o corpo do POST usando codificação de URL para o token.

❸ Inclua as credenciais do cliente na solicitação de revogação.

questionário

6. Qual endpoint padrão é usado para determinar se um token de acesso é válido?


1. O ponto final do token de acesso
2. O endpoint de autorização
3. O endpoint de revogação de token
4. O endpoint de introspecção de token
7. Quais partes têm permissão para revogar um token de acesso usando o end-
point de revogação padrão?
1. Alguém
2. Apenas um servidor de recursos
3. Somente o cliente para o qual o token foi emitido
4. Um servidor de recursos ou o cliente para o qual o token foi emitido

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


7.4.4 Tokens de acesso JWT

No entantoA introspecção de token resolve o problema de como a API pode deter-


minar se um token de acesso é válido e o escopo associado a esse token, tem uma
desvantagem: a API deve fazer uma chamada para o AS toda vez que precisar va-
lidar um token. Uma alternativa é usar um formato de token independente, como
os JWTs abordados no capítulo 6. Isso permite que a API valide o token de acesso
localmente sem precisar fazer uma chamada HTTPS para o AS. Embora ainda não
exista um padrão para tokens de acesso OAuth2 baseados em JWT (embora um es-
teja sendo desenvolvido; consulte http://mng.bz/5pW4 ), é comum que um AS su-
porte isso como uma opção.

Para validar um token de acesso baseado em JWT, a API precisa primeiro autenti-
car o JWT usando uma chave criptográfica. No capítulo 6, você usou HMAC simé-
trico ou algoritmos de criptografia autenticados nos quais a mesma chave é usada
para criar e verificar mensagens. Isso significa que qualquer parte que pode veri-
ficar um JWT também pode criar um que será confiável para todas as outras par-
tes. Embora isso seja adequado quando a API e o AS existem dentro do mesmo li-
mite de confiança, torna-se um risco de segurança quando as APIs estão em dife-
rentes limites de confiança. Por exemplo, se o AS estiver em um datacenter dife-
rente da API, a chave agora deve ser compartilhada entre esses dois datacenters.
Se houver muitas APIs que precisam de acesso à chave compartilhada,

Para evitar esses problemas, o AS pode mudar para criptografia de chave pública
usando assinaturas digitais, conforme mostrado na figura 7.8. Em vez de ter uma
única chave compartilhada, o AS tem um par de chaves: uma chave privada e
uma chave pública. O AS pode assinar um JWT usando a chave privada e qual-
quer pessoa com a chave pública pode verificar se a assinatura é genuína. No en-
tanto, a chave pública não pode ser usada para criar uma nova assinatura e, por-
tanto, é seguro compartilhar a chave pública com qualquer API que precise vali-
dar tokens de acesso. Por esse motivo, a criptografia de chave pública também é
conhecida como criptografia assimétrica, pois o detentor de uma chave privada
pode realizar operações diferentes do detentor de uma chave pública. Dado que
apenas o AS precisa criar novos tokens de acesso, o uso de criptografia de chave
pública para JWTs impõe o princípio da menor autoridade (POLA; consulte o capí-
tulo 2), pois garante que as APIs possam apenas verificar tokens de acesso e não
criar novos.
Figura 7.8 Ao usar tokens de acesso baseados em JWT, o AS assina o JWT usando
uma chave privada conhecida apenas pelo AS. A API pode recuperar uma chave
pública correspondente do AS para verificar se o JWT é genuíno. A chave pública
não pode ser usada para criar um novo JWT, garantindo que os tokens de acesso
possam ser emitidos apenas pelo AS.

DICA Embora a criptografia de chave pública seja mais segura nesse sentido,
também é mais complicada com mais maneiras de falhar. As assinaturas digitais
também são muito mais lentas do que o HMAC e outros algoritmos simétricos - ge-
ralmente 10 a 100 vezes mais lentas para segurança equivalente.
RECUPERANDO A CHAVE PÚBLICA

oA API pode ser configurada diretamente com a chave pública do AS. Por exem-
plo, você pode criar um keystore que contenha a chave pública, que a API pode
ler quando for inicializada. Embora isso funcione, tem algumas desvantagens:

Um keystore Java pode conter apenas certificados, não chaves públicas brutas,
portanto, o AS precisaria criar um certificado autoassinado apenas para permi-
tir que a chave pública fosse importada para o keystore. Isso adiciona comple-
xidade que não seria necessária de outra forma.
Se o AS alterar sua chave pública, o que é recomendado, o keystore precisará
ser atualizado manualmente para listar a nova chave pública e remover a an-
tiga. Como alguns tokens de acesso que usam a chave antiga ainda podem estar
em uso, o keystore pode ter que listar ambas as chaves públicas até que esses
tokens antigos expirem. Isso significa que duas atualizações manuais precisam
ser realizadas: uma para adicionar a nova chave pública e uma segunda atuali-
zação para remover a chave pública antiga quando ela não for mais necessária.

Embora você possa usar cadeias de certificados X.509 para estabelecer confiança
em uma chave por meio de uma autoridade de certificação, assim como para
HTTPS na seção 7.4.2, isso exigiria que a cadeia de certificados fosse anexada a
cada token de acesso JWT (usando o cabeçalho x5c padrãodescrito no capítulo 6).
Isso aumentaria o tamanho do token de acesso além dos limites razoáveis ​- uma
cadeia de certificados pode ter vários kilobytes de tamanho. Em vez disso, uma so-
lução comum é o AS publicar sua chave pública em um documento JSON conhe-
cido como JWK Set ( https://tools.ietf.org/html/rfc7517 ). Um exemplo de JWK Set é
mostrado na listagem 7.10 e consiste em um objeto JSON com um único keys atri-
buto, cujo valor é uma matriz de JSON Web Keys (consulte o capítulo 6). A API
pode buscar periodicamente o Conjunto JWK de um URI HTTPS fornecido pelo AS.
A API pode confiar nas chaves públicas no Conjunto JWK porque elas foram recu-
peradas por HTTPS de um URI confiável e essa conexão HTTPS foi autenticada
usando o certificado do servidor apresentado durante o handshake TLS.

Listagem 7.10 Um exemplo de conjunto JWK

{"teclas": [ ❶
{
"kty": "CE", ❷
"criança": "I4x/IijvdDsUZMghwNq2gC/7pYQ=",
"usar": "assinar",
"x": "k5wSvW_6JhOuCj-9PdDWdEA4oH90RSmC2GTliiUHAhXj6rmTdE2S-
➥ _zGmMFxufuV",
"y": "XfbR-tRoVcZMCoUrkKtuZUIyfCgAy8b0FWnPZqevwpdoTzGQBOXSN
➥ i6uItN_o4tH",
"crv": "P-384",
"alg": "ES384"
},
{
"kty": "RSA", ❸
"criança": "wU3ifIIaLOUAReRB/FG6eM1P1QM=",
"usar": "assinar",
"n": "10iGQ5l5IdqBP1l5wb5BDBZpSyLs4y_Um-kGv_se0BkRkwMZavGD_Nqjq8x3-
➥ fKNI45nU7E7COAh8gjn6LCXfug57EQfi0gOgKhOhVcLmKqIEXPmqeagvMndsXWIy6k8WP
➥ PwBzSkN5PDLKBXKG_X1BwVvOE9276nrx6lJq3CgNbmiEihovNt_6g5pCxiSarIk2uaG3T
➥ 3Ve6hUJrM0W35QmqrNM9rL3laPgXtCuz4sJJN3rGnQq_25YbUawW9L1MTVbqKxWiyN5Wb
➥ XoWUg8to1DhoQnXzDymIMhFa45NTLhxtdH9CDprXWXWBaWzo8mIFes5yI4AJW4ZSg1PPO
➥ 2UJSQ",
"e": "AQAB",
"alg": "RS256"
}
]}
❶ O JWK Set tem um atributo “keys”, que é um array de JSON Web Keys.

❷ Uma chave pública de curva elíptica

❸ Uma chave pública RSA

Muitas bibliotecas JWT têm suporte integrado para recuperação de chaves de um


Conjunto JWK por HTTPS, incluindo atualização periódica. Por exemplo, a biblio-
teca Nimbus JWT que você usou no capítulo 6 suporta a recuperação de chaves de
um JWK Set URI usando a RemoteJWKSet classe:

var jwkSetUri = URI.create("https://as.example.com:8443/jwks_uri");


var jwkSet = new RemoteJWKSet(jwkSetUri);

A Listagem 7.11 mostra a configuração de um novo SignedJwtAccessTokenSto-


re que validará um token de acesso como um JWT assinado. O construtor pega
um URI para o endpoint no AS para recuperar o JWK Set e constrói um Remo-
teJWKSet com base nisso. Ele também considera os valores esperados do emissor
e do público do JWT e o algoritmo de assinatura do JWS que será usado. Como
você deve se lembrar do capítulo 6, há ataques na verificação do JWT se o algo-
ritmo errado for usado, então você deve sempre validar estritamente se o cabeça-
lho do algoritmo tem um valor esperado. Abra a pasta
src/main/java/com/manning/apisecurityinaction/token e crie um novo arquivo
SignedJwtAccessTokenStore.java com o conteúdo da listagem 7.11. Você preen-
cherá os detalhes do read métodoEm breve.

DICA Se o AS oferece suporte à descoberta (consulte a seção 7.2.3), ele pode


anunciar seu JWK Set URI como o jwks_uri campodo documento de descoberta.

Listagem 7.11 O SignedJwtAccessTokenStore


pacote com.manning.apisecurityinaction.token;
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.source.*;
import com.nimbusds.jose.proc.*;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import spark.Request;
importar java.net.*;
import java.text.ParseException;
import java.util.Opcional;

public class SignedJwtAccessTokenStore implementa SecureTokenStore {

final privado Seqüência de caracteres esperadoIssuer;


private final Seqüência de caracteres esperadoAudience;
private final JWSAlgorithm signatureAlgorithm;
private final JWKSource<SecurityContext> jwkSource;

public SignedJwtAccessTokenStore(String esperadoIssuer,


Sequência esperadaAudiência,
JWSAlgorithm signatureAlgorithm,
URI jwkSetUri)
lança MalformedURLException {
this.expectedIssuer = esperadoIssuer; ❶
this.expectedAudience = esperadoAudiência; ❶
this.signatureAlgorithm = signatureAlgorithm; ❶
this.jwkSource = new RemoteJWKSet<>(jwkSetUri.toURL()); ❷
}

@Sobrepor
public String create(Solicitação de solicitação, Token token) {
lançar novo UnsupportedOperationException();
}
@Sobrepor
public void revoke(Solicitação de solicitação, String tokenId) {
lançar novo UnsupportedOperationException();
}

@Sobrepor
public Opcional<Token> read(Request request, String tokenId) {
// Ver listagem 7.12
}
}

❶ Configure o emissor esperado, o público e o algoritmo JWS.

❷ Construa um RemoteJWKSet para recuperar as chaves do URI do Conjunto JWK.

Um token de acesso JWT pode ser validado configurando a classe do processador


para usar o RemoteJWKSet como fonte para chaves de verificação ( ES256 é um
exemplo de algoritmo de assinatura JWS):

var verificador = new DefaultJWTProcessor<>();


var keySelector = new JWSVerificationKeySelector<>(
JWSAlgorithm.ES256, jwkSet);
verificador.setJWSKeySelector(keySelector);
var reivindicações = verifier.process(tokenId, null);

Após verificar a assinatura e o tempo de expiração do JWT, o processador retorna


o JWT Claims Set. Você pode então verificar se as outras reivindicações estão cor-
retas. Você deve verificar se o JWT foi emitido pelo AS validando a iss declara-
ção, e que o token de acesso se destina a esta API garantindo que um identificador
para a API apareça no público ( aud ) afirmam (listagem 7.12).
No fluxo normal do OAuth2, o AS não é informado pelo cliente para quais APIs ele
pretende usar o token de acesso, 9 e assim a reclamação de audiência pode variar
de um AS para outro. Consulte a documentação do seu software AS para configu-
rar o público-alvo. Outra área de desacordo entre o software AS está em como o
escopo do token é comunicado. Algum software AS produz uma scope declara-
ção de string, enquanto outros produzem uma matriz JSON de strings. Alguns ou-
tros podem usar um campo totalmente diferente, como scp ou scopes . A Lista-
gem 7.12 mostra como lidar com uma declaração de escopo que pode ser uma
string ou um array de strings. Abra SignedJwtAccessTokenStore.java em seu edi-
tor novamente e atualize o read métodobaseado nolistagem.

Listagem 7.12 Validando tokens de acesso JWT assinados

@Sobrepor
public Opcional<Token> read(Request request, String tokenId) {
tentar {
var verificador = new DefaultJWTProcessor<>();
var keySelector = new JWSVerificationKeySelector<>(
signatureAlgorithm, jwkSource);
verificador.setJWSKeySelector(keySelector);

var reivindicações = verifier.process(tokenId, null); ❶

if (!issuer.equals(claims.getIssuer())) { ❷
return Optional.empty(); ❷
}
if (!claims.getAudience().contains(audience)) { ❷
return Optional.empty(); ❷
}

var expiração = reivindicações.getExpirationTime().toInstant(); ❸


var subject = Claims.getSubject(); ❸
var token = new Token(expiração, assunto); ❸

Escopo da string; ❹
try { ❹
scope = Claims.getStringClaim("scope"); ❹
} catch (ParseException e) { ❹
scope = String.join(" ", ❹
Claims.getStringListClaim("scope")); ❹
} ❹
token.attributes.put("escopo", escopo); ❹
return Opcional.of(token);

} catch (ParseException | BadJOSEException | JOSEException e) {


return Opcional.vazio();
}
}

❶ Verifique primeiro a assinatura.

❷ Certifique-se de que o emissor e o público tenham valores esperados.

❸ Extraia o assunto JWT e o tempo de expiração.

❹ O escopo pode ser uma string ou um array de strings.

ESCOLHENDO UM ALGORITMO DE ASSINATURA

oO padrão JWS que o JWT usa para assinaturas suporta muitos algoritmos dife-
rentes de assinatura de chave pública, resumidos na tabela 7.2. Como os algorit-
mos de assinatura de chave pública são caros e geralmente limitados na quanti-
dade de dados que podem ser assinados, o conteúdo do JWT é primeiro hash
usando uma função de hash criptográfica e, em seguida, o valor de hash é assi-
nado. O JWS fornece variantes para diferentes funções de hash ao usar o mesmo
algoritmo de assinatura subjacente. Todas as funções de hash permitidas forne-
cem segurança adequada, mas o SHA-512 é o mais seguro e pode ser um pouco
mais rápido do que as outras opções em sistemas de 64 bits. A exceção a esta re-
gra é ao usar assinaturas ECDSA, porque JWS especifica curvas elípticas para usar
junto com cada função de hash; a curva usada com SHA-512 tem uma penalidade
de desempenho significativa em comparação com a curva usada para SHA-256.
Tabela 7.2 Algoritmos de assinatura JWS

Algoritmo função hash Algoritmo de assinatura


JWS

RS256 SHA-256 RSA com preenchimento PKCS#1 v1.5

RS384 SHA-384

RS512 SHA-512

PS256 SHA-256 RSA com enchimento PSS

PS384 SHA-384

PS512 SHA-512

ES256 SHA-256 ECDSA com a curva NIST P-256

ES384 SHA-384 ECDSA com a curva NIST P-384

ES512 SHA-512 ECDSA com a curva NIST P-521

EdDSA SHA-512 / EdDSA com as curvas Ed25519 ou


SHAKE256 Ed448

Dessas opções, a melhor é EdDSA , com base no Algoritmo de Assinatura Digital


da Curva de Edwards( https://tools.ietf.org/html/rfc8037 ). As assinaturas EdDSA
são rápidas de produzir e verificar, produzem assinaturas compactas e são proje-
tadas para serem implementadas com segurança contra ataques de canal lateral.
Nem todas as bibliotecas JWT ou software AS suportam assinaturas EdDSAainda.
O antigo padrão ECDSA para assinaturas digitais de curva elíptica tem suporte
mais amplo e compartilha algumas das mesmas propriedades do EdDSA, mas é
um pouco mais lento e mais difícil de implementar com segurança.

Assinaturas ECDSA DE ADVERTÊNCIAexigem um nonce aleatório exclusivo


para cada assinatura. Se um nonce for repetido, ou mesmo apenas alguns bits não
forem completamente aleatórios, a chave privada poderá ser reconstruída a par-
tir dos valores de assinatura. Esse tipo de bug foi usado para hackear o Sony
PlayStation 3, roubar criptomoedas Bitcoin de carteiras em celulares Android, en-
tre muitos outros casos. As assinaturas ECDSA determinísticas (
https://tools.ietf.org/html/rfc6979 ) podem ser usadas para evitar isso, se sua bibli-
oteca as suportar. As assinaturas EdDSA também são imunes a esse problema.

As assinaturas RSA são caras de produzir, especialmente para tamanhos de chave


seguros (uma chave RSA de 3072 bits é aproximadamente equivalente a uma
chave de curva elíptica de 256 bits ou uma chave HMAC de 128 bits) e produz assi-
naturas muito maiores do que as outras opções, resultando em JWTs maiores. Por
outro lado, as assinaturas RSA podem ser validadas muito rapidamente. As vari-
antes de RSA usando preenchimento PSS devem ser preferidas àquelas que usam
o preenchimento PKCS#1 versão 1.5 mais antigo, mas podem não ser suportadas
portudobibliotecas.

7.4.5 Tokens de acesso JWT criptografados

DentroNo capítulo 6, você aprendeu que a criptografia autenticada pode ser


usada para fornecer os benefícios da criptografia para ocultar atributos confiden-
ciais e autenticação para garantir que um JWT seja genuíno e não tenha sido adul-
terado. JWTs criptografados também podem ser úteis para tokens de acesso, por-
que o AS pode querer incluir atributos no token de acesso que são úteis para a API
tomar decisões de controle de acesso, mas que devem ser mantidos em sigilo de
clientes terceiros ou do próprio usuário . Por exemplo, o AS pode incluir o ende-
reço de e-mail do proprietário do recurso no token para uso pela API, mas essas
informações não devem vazar para o cliente terceirizado. Nesse caso, o AS pode
criptografar o token de acesso JWT usando uma chave de criptografia que so-
mente a API pode descriptografar.

Infelizmente, nenhum dos algoritmos de criptografia de chave pública suportados


pelos padrões JWT fornecem criptografia autenticada, 10 porque isso é implemen-
tado com menos frequência para criptografia de chave pública. Os algoritmos su-
portados fornecem apenas confidencialidade e, portanto, devem ser combinados
com uma assinatura digital para garantir que o JWT não seja adulterado ou falsifi-
cado. Isso é feito primeiro assinando as declarações para produzir um JWT assi-
nado e, em seguida, criptografando esse JWT assinado para produzir uma estru-
tura JOSE aninhada (figura 7.9). A desvantagem é que o JWT resultante é muito
maior do que seria se tivesse acabado de ser assinado e requer duas operações de
chave pública caras para primeiro descriptografar o JWE criptografado externo e,
em seguida, verificar o JWT assinado interno. Você não deve usar a mesma chave
para criptografia e assinatura, mesmo que os algoritmos sejam compatíveis.
Figura 7.9 Ao usar criptografia de chave pública, um JWT precisa ser primeiro as-
sinado e depois criptografado para garantir confidencialidade e integridade, pois
nenhum algoritmo padrão fornece ambas as propriedades. Você deve usar chaves
separadas para assinatura e criptografia, mesmo que os algoritmos sejam
compatíveis.

As especificações do JWE incluem vários algoritmos de criptografia de chave pú-


blica, mostrados na tabela 7.3. Os detalhes dos algoritmos podem ser complicados
e diversas variações estão incluídas. Se o seu software for compatível, é melhor
evitar totalmente os algoritmos de criptografia RSA e optar pela ECDH-ES cripto-
grafia. O ECDH-ES é baseado no acordo de chave Diffie-Hellman da curva elíptica
e é uma escolha segura e de alto desempenho, especialmente quando usado com
as curvas elípticas X25519 ou X448 ( https://tools.ietf.org/html/rfc8037 ), mas essas
ainda não são amplamente suportados pelo JWTbibliotecas.
Tabela 7.3 Algoritmos de criptografia de chave pública JOSE

Algoritmo JWE Detalhes Comentários

RSA1_5 RSA com preenchi- Este modo é inseguro e não deve


mento PKCS#1 v1.5 ser usado.

RSA-OAEP RSA com preenchi- OAEP é seguro, mas a descripto-


mento OAEP usando grafia RSA é lenta e a criptografia
SHA-1 produz grandes JWTs.

RSA-OAEP- RSA com preenchi-


256 mento OAEP usando
SHA-256

ECDH-ES Esquema de criptogra- Um algoritmo de criptografia se-


fia integrada de curva guro, mas o epk cabeçalhoacres-
elíptica(ECIES) centa pode ser volumoso. Melhor
quando usado com as curvas
ECDH- ECDH-ES com uma X25519 ou X448.
ES+A128KW etapa extra de quebra
de chave AES
ECDH-
ES+A192KW

ECDH-
ES+A256KW
AVISO A maioria dos algoritmos JWE são seguros, exceto RSA1_5 os que usam o
antigo algoritmo de preenchimento PKCS#1 versão 1.5. Existem ataques conheci-
dos contra esse algoritmo, então você não deve usá-lo. Este modo de preenchi-
mento foi substituído pelo preenchimento de criptografia assimétrica ideal(OAEP)
que foi padronizado na versão 2 do PKCS#1. O OAEP usa uma função de hash in-
ternamente, então há duas variantes incluídas no JWE: uma usando SHA-1 e outra
usando SHA-256. Como o SHA-1 não é mais considerado seguro, você deve prefe-
rir a variante SHA-256, embora não haja ataques conhecidos contra ele quando
usado com OAEP. No entanto, mesmo o OAEP tem algumas desvantagens porque é
um algoritmo complicado e menos amplamente implementado. A criptografia
RSA também produz texto cifrado maior do que outros modos e a operação de
descriptografia é muito lenta, o que é um problema para um token de acesso que
pode precisar ser descriptografado várias vezes.

7.4.6 Deixando o AS descriptografar os tokens

Umalternativa ao uso de assinatura de chave pública e criptografia seria o AS


criptografar tokens de acesso com um algoritmo de criptografia autenticado simé-
trico, como os que você aprendeu no capítulo 6. Em vez de compartilhar essa
chave simétrica com cada API, eles chamam o token endpoint de introspecção
para validar o token em vez de verificá-lo localmente. Como o AS não precisa exe-
cutar uma pesquisa no banco de dados para validar o token, pode ser mais fácil
escalar horizontalmente o AS nesse caso adicionando mais servidores para lidar
com o aumento do tráfego.

Esse padrão permite que o formato dos tokens de acesso mude com o tempo por-
que apenas o AS valida os tokens. Em termos de engenharia de software, a esco-
lha do formato do token é encapsulada pelo AS e oculta dos servidores de recur-
sos, enquanto com JWTs assinados por chave pública, cada API sabe como validar
os tokens, tornando muito mais difícil alterar a representação posteriormente. Pa-
drões mais sofisticados para gerenciar tokens de acesso para ambientes de mi-
crosserviço são abordadosdentroparte 4.

questionário

8. Qual chave é usada para validar uma assinatura de chave pública?


1. A chave pública
2. A chave privada

A resposta está no final do capítulo.

7.5 Logon único

Um Uma das vantagens do OAuth2 é a capacidade de centralizar a autenticação


de usuários no AS, fornecendo uma experiência de logon único (SSO) (figura 7.10).
Quando o cliente do usuário precisa acessar uma API, ele redireciona o usuário
para o endpoint de autorização do AS para obter um token de acesso. Nesse ponto,
o AS autentica o usuário e solicita o consentimento para que o cliente tenha
acesso. Como isso acontece em um navegador da Web, o AS normalmente cria um
cookie de sessão para que o usuário não precise fazer login novamente.
Figura 7.10 OAuth2 habilita o logon único para os usuários. Como os clientes dele-
gam ao AS para obter tokens de acesso, o AS é responsável por autenticar todos os
usuários. Se o usuário tiver uma sessão existente com o AS, ele não precisará ser
autenticado novamente, proporcionando uma experiência de SSO perfeita.

Se o usuário começar a usar um cliente diferente, como um aplicativo da web di-


ferente, ele será redirecionado para o AS novamente. Mas desta vez o AS verá o
cookie de sessão existente e não solicitará que o usuário faça login. Isso funciona
até mesmo para aplicativos móveis de diferentes desenvolvedores se eles estive-
rem instalados no mesmo dispositivo e usarem o navegador do sistema para flu-
xos OAuth, conforme recomendado na seção 7.3. O AS também pode lembrar
quais escopos um usuário concedeu aos clientes, permitindo que a tela de consen-
timento seja ignorada quando um usuário retornar a esse cliente. Dessa forma, o
OAuth pode fornecer uma experiência de SSO perfeita para usuários que substi-
tuem as soluções tradicionais de SSO. Quando o usuário faz logout, o cliente pode
revogar seu token de acesso ou atualização usando o ponto de extremidade de re-
vogação de token OAuth, o que impedirá mais Acesso.

AVISO Embora possa ser tentador reutilizar um único token de acesso para for-
necer acesso a várias APIs diferentes dentro de uma organização, isso aumenta o
risco se um token for roubado. Prefira usar tokens de acesso separados para cada
API diferente.

7.6 Conexão OpenID

OAuthpode fornecer a funcionalidade básica de SSO, mas o foco principal é o


acesso delegado de terceiros às APIs, em vez da identidade do usuário ou do ge-
renciamento de sessão. O conjunto de padrões OpenID Connect (OIDC) (
https://openid.net/developers/specs/ ) estende o OAuth2 com vários recursos:

Uma maneira padrão de recuperar informações de identidade sobre um usuá-


rio, como nome, endereço de e-mail, endereço postal e número de telefone. O
cliente pode acessar um endpoint UserInfopara recuperar reivindicações de
identidade como JSON usando um token de acesso OAuth2 com escopos OIDC
padrão.
Uma maneira de o cliente solicitar que o usuário seja autenticado mesmo que
ele já tenha uma sessão existente e solicitar que ele seja autenticado de uma
maneira específica, como com autenticação de dois fatores. Embora a obtenção
de um token de acesso OAuth2 possa envolver a autenticação do usuário, não
há garantia de que o usuário estava presente quando o token foi emitido ou há
quanto tempo ele fez login. OAuth2 é principalmente um protocolo de acesso
delegado, enquanto o OIDC fornece um protocolo de autenticação completo. Se
o cliente precisar autenticar positivamente um usuário, o OIDC deverá ser
usado.
Extensões para gerenciamento de sessão e logout, permitindo que os clientes
sejam notificados quando um usuário sai de sua sessão no AS, permitindo que o
usuário saia de todos os clientes de uma só vez (conhecido como logout único).

Embora o OIDC seja uma extensão do OAuth, ele reorganiza um pouco as partes
porque a API que o cliente deseja acessar (o endpoint UserInfo) faz parte do pró-
prio AS (figura 7.11). Em um fluxo OAuth2 normal, o cliente primeiro falaria com
o AS para obter um token de acesso e depois falaria com a API em um servidor de
recursos separado.
Figura 7.11 No OpenID Connect, o cliente acessa as APIs no próprio AS, portanto,
há apenas duas entidades envolvidas em comparação com as três no OAuth nor-
mal. O cliente é conhecido como parte confiável (RP), enquanto o AS e a API com-
binados são conhecidos como OpenID Provider (OP).

DEFINIÇÃO No OIDC, o AS e o RS são combinados em uma única entidade co-


nhecida como OpenID Provider(OP). O cliente é conhecido como Parte
Confiável(PR).
O uso mais comum do OIDC é para um site ou aplicativo delegar a autenticação a
um provedor de identidade terceirizado. Se você já fez login em um site usando
sua conta do Google ou do Facebook, está usando o OIDC nos bastidores, e muitas
grandes empresas de mídia social agora oferecem suporte a isso.

7.6.1 fichas de identificação

SeSe você seguir as recomendações do OAuth2 neste capítulo, descobrir quem é


um usuário envolve três idas e vindas ao AS para o cliente:

1. Primeiro, o cliente precisa chamar o terminal de autorização para obter um có-


digo de autorização.
2. Em seguida, o cliente troca o código por um token de acesso.
3. Por fim, o cliente pode usar o token de acesso para chamar o terminal UserInfo
para recuperar as declarações de identidade do usuário.

Isso representa muita sobrecarga antes mesmo de você saber o nome do usuário,
portanto, o OIDC fornece uma maneira de retornar algumas das reivindicações de
identidade e autenticação sobre um usuário como um novo tipo de token conhe-
cido como token de ID, que é assinado e opcionalmente criptografado JWT. Esse
token pode ser retornado diretamente do endpoint do token na etapa 2 ou até
mesmo diretamente do endpoint de autorização na etapa 1, em uma variante do
fluxo implícito. Há também um fluxo híbrido no qual o endpoint de autorização
retorna um token de ID diretamente junto com um código de autorização que o
cliente pode então trocar por um token de acesso.

DEFINIÇÃO Um token de ID é um JWT assinado e opcionalmente criptografado


que contém declarações de identidade e autenticação sobre um usuário.

Para validar um token de ID, o cliente deve primeiro processar o token como um
JWT, descriptografando-o se necessário e verificando a assinatura. Quando um cli-
ente se registra em um provedor OIDC, ele especifica a assinatura do token de ID e
os algoritmos de criptografia que deseja usar e pode fornecer chaves públicas a
serem usadas para criptografia, portanto, o cliente deve garantir que o token de
ID recebido use esses algoritmos. O cliente deve, então, verificar as declarações
JWT padrão no token de ID, como os valores de expiração, emissor e público, con-
forme descrito no capítulo 6. O OIDC define várias declarações adicionais que
também devem ser verificadas, descritas na tabela 7.4.
Tabela 7.4 Reivindicações padrão de token de ID

Alegar Propósito Notas

azp Parte Um token de ID pode ser compartilhado com


Autorizada mais de uma parte e, portanto, ter vários valo-
res na declaração de público. A azp declara-
ção lista o cliente para o qual o token de ID foi
inicialmente emitido. Um cliente interagindo
diretamente com um provedor OIDC deve ve-
rificar se é a parte autorizada se mais de uma
parte estiver na audiência.

auth_time Tempo de A hora em que o usuário foi autenticado em


autentica- segundos a partir da época do UNIX.
ção do
usuário

nonce Anti-replay Um valor aleatório exclusivo que o cliente en-


nonce via na solicitação de autenticação. O cliente
deve verificar se o mesmo valor está incluído
no token de ID para evitar ataques de repeti-
ção - consulte a seção 7.6.2 para obter
detalhes.

acr Referência Indica a força geral da autenticação do usuá-


de classe rio executada. Esta é uma string e os valores
do contexto específicos são definidos pelo OP ou por ou-
de tros padrões.
autentica-
ção

amr Referências Uma matriz de strings indicando os métodos


de métodos específicos usados. Por exemplo, pode conter
de ["password", "otp"] para indicar que o
autentica- usuário forneceu uma senha e uma senha
ção única.

Ao solicitar autenticação, o cliente pode usar parâmetros extras para o endpoint


de autorização para indicar como o usuário deve ser autenticado. Por exemplo, o
max_time parâmetropode ser usado para indicar há quanto tempo o usuário
deve ter autenticado para poder reutilizar uma sessão de login existente no OP, e
o acr_values parâmetropode ser usado para indicar níveis de autenticação acei-
táveis ​de garantia. O prompt=login parâmetropode ser usado para forçar a re-
autenticação mesmo se o usuário tiver uma sessão existente que satisfaça quais-
quer outras restrições especificadas na solicitação de autenticação,
enquanto prompt=none pode ser usado para verificar se o usuário está conectado
no momento sem autenticá-lo, se estivernão.

AVISO Só porque um cliente solicitou que um usuário seja autenticado de uma


certa maneira, isso não significa que ele será. Como os parâmetros de solicitação
são expostos como parâmetros de consulta de URL em um redirecionamento, o
usuário pode alterá-los para remover algumas restrições. O OP pode não conse-
guir atender a todas as solicitações por outros motivos. O cliente sempre deve ve-
rificar as declarações em um token de ID para garantir que todas as restrições fo-
ram atendidas.
7.6.2 Endurecimento OIDC

Enquantoum token de ID é protegido contra adulteração pela assinatura cripto-


gráfica, ainda existem vários ataques possíveis quando um token de ID é passado
de volta para o cliente na URL do endpoint de autorização nos fluxos implícitos ou
híbridos:

O token de ID pode ser roubado por um script malicioso em execução no


mesmo navegador ou pode vazar nos logs de acesso do servidor ou no Refe-
rer cabeçalho HTTP. Embora um token de ID não conceda acesso a nenhuma
API, ele pode conter informações pessoais ou confidenciais sobre o usuário que
devem ser protegidas.
Um invasor pode capturar um token de ID de uma tentativa legítima de login e
reproduzi-lo posteriormente para tentar fazer login como um usuário dife-
rente. Uma assinatura criptográfica garante apenas que o token de ID foi emi-
tido pelo OP correto, mas não garante por si só que foi emitido em resposta a
essa solicitação específica.

A defesa mais simples contra esses ataques é usar o fluxo de código de autoriza-
ção com PKCE conforme recomendado para todos os fluxos OAuth2. Nesse caso, o
token de ID é emitido apenas pelo OP a partir do endpoint do token em resposta a
uma solicitação HTTPS direta do cliente. Se você decidir usar um fluxo híbrido
para receber um token de ID diretamente no redirecionamento do endpoint de
autorização, o OIDC inclui várias proteções que podem ser empregadas para pro-
teger o fluxo:
O cliente pode incluir um aleatório nonce parâmetro na solicitação e verifique
se o mesmo nonce está incluído no token de ID recebido em resposta. Isso evita
ataques de repetição, pois o nonce em um token de ID repetido não correspon-
derá ao novo valor enviado na nova solicitação. O nonce deve ser gerado alea-
toriamente e armazenado no cliente, assim como o state parâmetro OAuthe o
PKCE code_challenge . (Observe que o parâmetro nonce não está relacio-
nado a um nonce usado na criptografia conforme abordado no capítulo 6.)
O cliente pode solicitar que o token de ID seja criptografado usando uma chave
pública fornecida durante o registro ou usando criptografia AES com uma
chave derivada do segredo do cliente. Isso evita que informações pessoais con-
fidenciais sejam expostas se o token de ID for interceptado. A criptografia sozi-
nha não impede ataques de repetição, portanto, um nonce OIDC ainda deve ser
usado neste caso.
O token de ID pode incluir c_hash e at_hash declarações que contêm hashes
criptográficos do código de autorização e token de acesso associados a uma soli-
citação. O cliente pode compará-los com o código de autorização real e o token
de acesso que recebe para garantir que correspondam. Juntamente com o
nonce e a assinatura criptográfica, isso evita efetivamente que um invasor tro-
que o código de autorização ou o token de acesso na URL de redirecionamento
ao usar os fluxos híbridos ou implícitos.

DICA Você pode usar o mesmo valor aleatório para os parâmetros OAuth sta-
te e OIDC nonce para evitar ter que gerar e armazenar ambos no cliente.

As proteções adicionais fornecidas pelo OIDC podem atenuar muitos dos proble-
mas com a concessão implícita. Mas eles têm um custo de maior complexidade em
comparação com a concessão do código de autorização com PKCE, porque o cli-
ente deve executar várias operações criptográficas complexas e verificar muitos
detalhes do token de ID durante a validação. Com o fluxo de código de autentica-
ção e PKCE, as verificações são realizadas pelo OP quando o código é trocado por
acesso e IDtokens.

7.6.3 Passando um token de ID para uma API

Dadoque um token de ID é um JWT e se destina a autenticar um usuário, é tenta-


dor usá-los para autenticar usuários em sua API. Esse pode ser um padrão conve-
niente para clientes primários, porque o token de ID pode ser usado diretamente
como um token de sessão sem estado. Por exemplo, a interface do usuário da Web
do Natter pode usar o OIDC para autenticar um usuário e, em seguida, armazenar
o token de ID como um cookie ou no armazenamento local. A API do Natter seria
então configurada para aceitar o token de ID como um JWT, verificando-o com a
chave pública do OP. Um token de ID não é apropriado como substituto para to-
kens de acesso ao lidar com clientes de terceiros pelos seguintes motivos:

Os tokens de ID não têm escopo e o usuário é solicitado apenas a consentir para


que o cliente acesse suas informações de identidade. Se o token de ID puder ser
usado para acessar APIs, qualquer cliente com um token de ID poderá agir
como se fosse o usuário sem nenhuma restrição.
Um token de ID autentica um usuário para o cliente e não deve ser usado por
esse cliente para acessar uma API. Por exemplo, imagine se o Google permitisse
acesso às suas APIs com base em um token de ID. Nesse caso, qualquer site que
permitisse que seus usuários fizessem login com sua conta do Google (usando
OIDC) poderia reproduzir o token de ID de volta para as próprias APIs do Goo-
gle para acessar os dados do usuário sem seu consentimento.
Para evitar esses tipos de ataques, um token de ID possui uma declaração de
público que lista apenas o cliente. Uma API deve rejeitar qualquer JWT que não
liste essa API no público.
Se você estiver usando os fluxos implícitos ou híbridos, o token de ID será ex-
posto na URL durante o redirecionamento de volta do OP. Quando um token de
ID é usado para controle de acesso, há os mesmos riscos de incluir um token de
acesso na URL, pois o token pode vazar ou ser roubado.

Portanto, você não deve usar tokens de ID para conceder acesso a uma API.

NOTE Nunca use tokens de ID para controle de acesso para clientes de terceiros.
Use tokens de acesso para acesso e tokens de ID para identidade. Os tokens de ID
são como nomes de usuários; tokens de acesso são como senhas.

Embora você não deva usar um token de ID para permitir o acesso a uma API,
pode ser necessário procurar informações de identidade sobre um usuário du-
rante o processamento de uma solicitação de API ou precisar impor requisitos de
autenticação específicos. Por exemplo, uma API para iniciar transações financei-
ras pode exigir a garantia de que o usuário foi autenticado recentemente usando
um mecanismo de autenticação forte. Embora essas informações possam ser re-
tornadas de uma solicitação de introspecção de token, isso nem sempre é supor-
tado por todos os softwares de servidor de autorização. Os tokens de ID OIDC for-
necem um formato de token padrão para verificar esses requisitos. Nesse caso,
convém permitir que o cliente passe um token de ID assinado obtido de um OP
confiável. Quando isso é permitido,

Quando a API precisa acessar declarações no token de ID, ela deve primeiro veri-
ficar se é de um OP confiável, validando a assinatura e as declarações do emissor.
Ele também deve garantir que o assunto do token de ID corresponda exatamente
ao proprietário do recurso do token de acesso ou que haja algum outro relaciona-
mento de confiança entre eles. Idealmente, a API deve garantir que seu próprio
identificador esteja no público do token de ID e que o identificador do cliente seja
a parte autorizada ( azp reivindicação), mas nem todos os softwares OP supor-
tam a configuração desses valores corretamente neste caso. A Listagem 7.13 mos-
tra um exemplo de validação das declarações em um token de ID em comparação
com aquelas em um token de acesso que já foi usado para autenticar a solicitação.
Consulte o SignedJwtAccessToken store para obter detalhes sobre como confi-
gurar o verificador JWT.

Listagem 7.13 Validando um token de ID

var idToken = request.headers("X-ID-Token"); ❶


var Claims = verifier.process(idToken, null); ❶

if (!expectedIssuer.equals(claims.getIssuer())) { ❷
throw new IllegalArgumentException( ❷
"id inválido do emissor do token"); ❷
}
if (!claims.getAudience().contains(expectedAudience)) { ❷
throw new IllegalArgumentException( ❷
"público de token de id inválido"); ❷
}

var cliente = request.attribute("client_id"); ❸


var azp = Claims.getStringClaim("azp"); ❸
if (client != null && azp != null && !azp.equals(client)) { ❸
throw new IllegalArgumentException( ❸
"o cliente não é parte autorizada"); ❸
}

var assunto = request.attribute("assunto"); ❹


if (!subject.equals(claims.getSubject())) { ❹
throw new IllegalArgumentException( ❹
"assunto não corresponde ao token id"); ❹
}
request.attribute("id_token.claims", reivindicações); ❺

❶ Extraia o token de ID da solicitação e verifique a assinatura.

❷ Verifique se o token é de um emissor confiável e se essa API é o público-alvo.

❸ Se o token de ID tiver uma declaração azp, certifique-se de que seja para o mesmo
cliente que está chamando a API.

❹ Verifique se o assunto do token de ID corresponde ao proprietário do recurso do


token de acesso.

❺ Armazene as declarações de token de ID verificadas nos atributos de solicitação


para processamento posterior.

Respostas para perguntas do questionário

1. d e e. Se os escopos ou permissões são mais refinados varia de caso para caso.


2. a e e. A concessão implícita é desencorajada devido ao risco de roubo de tokens
de acesso. A concessão ROPC é desencorajada porque o cliente descobre a se-
nha do usuário.
3. uma. Os aplicativos móveis devem ser clientes públicos porque quaisquer cre-
denciais incorporadas no download do aplicativo podem ser facilmente extraí-
das pelos usuários.
4. uma. URIs HTTPS reivindicados são mais seguros.
5. Verdadeiro. O PKCE fornece benefícios de segurança em todos os casos e deve
ser sempre usado.
6. d.
7. c.
8. uma. A chave pública é usada para validarumaassinatura.

Resumo

Os tokens com escopo permitem que os clientes tenham acesso a algumas par-
tes de sua API, mas não a outras, permitindo que os usuários deleguem acesso
limitado a aplicativos e serviços de terceiros.
O padrão OAuth2 fornece uma estrutura para clientes de terceiros se registra-
rem em sua API e negociarem o acesso com o consentimento do usuário.
Todos os clientes de API voltados para o usuário devem usar a concessão de có-
digo de autorização com PKCE para obter tokens de acesso, sejam eles aplicati-
vos da web tradicionais, SPAs, aplicativos móveis ou aplicativos de desktop. A
concessão implícita não deve mais ser usada.
O endpoint de introspecção de token padrão pode ser usado para validar um to-
ken de acesso, ou tokens de acesso baseados em JWT podem ser usados ​para re-
duzir as viagens de ida e volta da rede. Os tokens de atualização podem ser usa-
dos ​para manter a vida útil dos tokens curta sem interromper a experiência do
usuário.
O padrão OpenID Connect se baseia no OAuth2, fornecendo uma estrutura
abrangente para transferir a autenticação do usuário para um serviço dedi-
cado. Os tokens de ID podem ser usados ​para identificação do usuário, mas de-
vem ser evitados para acessoao controle.

1.
Em alguns países, os bancos estão sendo obrigados a fornecer acesso API seguro
para transações e serviços de pagamento para aplicativos e serviços de terceiros.
A iniciativa Open Banking do Reino Unido e os regulamentos da Diretiva Europeia
de Serviços de Pagamento 2 (PSD2) são exemplos, sendo que ambos exigem o uso
de OAuth2.
2.
Uma maneira alternativa de eliminar esse risco é garantir que qualquer token re-
cém-emitido contenha apenas os escopos que estão no token usado para chamar o
endpoint de login. Vou deixar isso como um exercício.

3.
Projetos como SELinux ( https://selinuxproject.org/page/Main_Page ) e AppArmor (
https://apparmor .net/ ) trazem controles de acesso obrigatórios para o Linux.

4.
Uma solução possível para isso é registrar dinamicamente cada instância indivi-
dual do aplicativo como um novo cliente quando ele é inicializado, para que cada
um obtenha suas próprias credenciais exclusivas. Consulte o capítulo 12 de
OAuth2 em Action (Manning, 2017) para obter detalhes.

5.
O software AS que suporta o padrão OpenID Connect pode usar o caminho /.well-
known/openid-configuration. Recomenda-se verificar os dois locais.

6.
O antigo código de status 302 Found também é usado com frequência e há pouca
diferença entre eles.

7.
Existe um método alternativo em que o cliente envia o verificador original como
desafio, mas é menos seguro.

8.
Lembre-se do capítulo 3 que as versões anteriores do TLS eram chamadas de SSL,
e essa terminologia ainda é difundida.

9.
Como você já deve esperar, existe uma proposta para permitir que o cliente indi-
que os servidores de recursos que pretende acessar: http://mng.bz/6ANG
10.
Eu propus adicionar criptografia autenticada por chave pública ao JOSE e JWT,
mas a proposta ainda é um rascunho neste estágio. Veja http://mng.bz/oRGN .

You might also like