Table of Contents
O principal objetivo de implementar OAuth 2.0 é proteger as APIs da Web por tokens de acesso. Portanto, todos vocês finalmente precisarão ler este documento.
O que as pessoas querem fazer é proteger suas APIs web por tokens de acesso, mas eles logo perceberão que têm que implementar RFC 6749 (OAuth 2.0) como pré-requisito. O Authlete existe para mitigar a carga para implementar a especificação e permitir que você se concentre na implementação de suas APIs web.
As APIs web protegidas por tokens de acesso OAuth 2.0 são chamadas pontos finais de recursos protegidos. Esses pontos finais fazem o seguinte antes de devolver os dados solicitados aos aplicativos do cliente.
As seções a seguir descrevem como fazer isso.
Como um ponto final de recurso protegido deve aceitar um token de acesso de um aplicativo cliente? É possível criar quantas novas maneiras de fazer isso você quiser. No entanto, não temos que reinventar a roda. RFC 6750 (Uso de token do portador) já definiu três maneiras de aceitar um token de acesso conforme listado abaixo.
Authorization
cabeçalho. (2.1. Campo de cabeçalho de solicitação de solicitação de autorização)access_token
. (2.2. Parâmetro do corpo codificado por formulário)access_token
. (2.3. Parâmetro de consulta uri)A maneira mais desejável é a primeira e RFC 6750 Diz “Os servidores de recursos devem suportar esse método”. Simplesmente dizendo, quando um aplicativo cliente usa a primeira maneira, um ponto final de recurso protegido pode encontrar um cabeçalho HTTP como abaixo na solicitação.
Authorization: Bearer access-token
Abaixo estão exemplos para extrair um token de acesso.
padrão final estático privado CHALLENGE\_PATTERN
\= Pattern.compile("^Portador \*(\[^]+) \*$", Pattern.CASE\_INSENSITIVE);
/\*\*
* Ponto de entrada para o método GET.
\*/
@GET
resposta pública obter(
@HeaderParam (HttpHeaders.AUTHORIZATION) Autorização de string,
@QueryParam ("access\_token") Acesso à seqüênciatoken)
{
Extrair um token de acesso do cabeçalho ou uso da Autorização
o valor do parâmetro de consulta 'access\_token'.
accessToken = extratoAken (autorização, acessoToken);
}
/\*\*
* Ponto de entrada para o método POST.
\*/
@POST
@Consumes(MediaType.APPLICATION\_FORM\_URLENCODED)
post de resposta pública(
@HeaderParam (HttpHeaders.AUTHORIZATION) Autorização de string,
@FormParam ("access\_token") Acesso à cordaToken)
{
Extrair um token de acesso do cabeçalho ou uso da Autorização
o valor do parâmetro de forma 'access\_token'.
accessToken = extratoAken (autorização, acessoToken);
}
/\*\*
* Extrair um token de acesso do cabeçalho autorização ou retornar
* o valor do parâmetro de solicitação 'access\_token'.
\*/
extrato de string estática privadaAccessToken(
Autorização de string, String accessToken)
{
Se o cabeçalho autorização não for encontrado na solicitação.
se (autorização == nulo)
{
Devolva o valor do parâmetro de solicitação 'access\_token'.
acesso de retornoToken;
}
Correspondência de padrões no valor do cabeçalho autorização.
Matcher matcher = CHALLENGE\_PATTERN.matcher (autorização);
Se o valor do cabeçalho autorização estiver no formato de
'Bearer access-token'.
se (matcher.match())
{
Devolva o valor extraído do cabeçalho autorização.
retorno matcher.group(1);
}
mais
{
Devolva o valor do parâmetro de solicitação 'access\_token'.
acesso de retornoToken;
}
}
# Método para extrair um token de acesso de uma solicitação.
def extract\_access\_token(solicitação)
# O valor do cabeçalho autorização.
cabeçalho = request.env\["HTTP\_AUTHORIZATION"]
# Se o valor estiver no formato de 'Bearer access-token'.
se /^Bearer\[]+(.+)/i =~ cabeçalho
\# Devolva o valor extraído do cabeçalho autorização.
devolver $1
fim
# Devolva o valor do parâmetro de solicitação 'access\_token'.
solicitação de retorno \["access\_token"]
fim
# Ponto de entrada para o método GET.
obter '/Olá' do
# Extrair um token de acesso da solicitação.
access\_token = extract\_access\_token(solicitação)
fim
# Ponto de entrada para o método POST.
postar '/Olá' do
# Extrair um token de acesso da solicitação.
access\_token = extract\_access\_token(solicitação)
fim
/\*\*
* Função para extrair um token de acesso de uma solicitação.
\*/
extract\_access\_token de função()
{
O valor do cabeçalho autorização.
$header = $\_SERVER\['HTTP\_AUTHORIZATION'];
Se o valor estiver no formato de 'Bearer access-token'.
se ($header != nulo && preg\_match('/^Portador\[]+(.+)/i', $header, $captured))
{
Devolva o valor extraído do cabeçalho autorização.
$captured de retorno;
}
se ($\_SERVER\['REQUEST\_METHOD'] == 'GET')
{
Devolva o valor do parâmetro de consulta 'access\_token'.
devolver $\_GET \['access\_token'];
}
mais
{
Retorne o valor do parâmetro de formulário 'access\_token'.
devolver $\_POST \['access\_token'];
}
}
O processo para obter informações detalhadas sobre um token de acesso é chamado introspecção. Como introspecr um token de acesso depende de cada implementação dos servidores de autorização OAuth 2.0.
No entanto, em outubro de 2015, RFC 7662 (OAuth 2.0 Token Introspection) foi lançado como uma especificação padrão para introspecção. Se um servidor de autorização que você estiver usando suporta rfc 7662, seus pontos finais de recursos protegidos podem introspectar tokens de acesso sem depender de uma implementação específica do servidor de autorização. Mas, ainda assim, como proteger o ponto final de introspecção em si depende de cada implementação como mencionado em “2.1. Solicitação de introspecção” de “RFC 7662”.
Da RFC 7662, 2.1. Solicitação de Introspecção;
Para evitar ataques de digitalização de tokens, o ponto final também deve exigir alguma forma de autorização para acessar este ponto final, como a autenticação do cliente, conforme descrito no OAuth 2.0 [RFC 6749] ou um token de acesso OAuth 2.0 separado, como o token portador descrito no OAuth 2.0 Bearer Token Usage [RFC 6750]. Os métodos de gerenciamento e validação dessas credenciais de autenticação estão fora de escopo desta especificação.
Authlete fornece uma API de introspecção em /auth/introspection
. A implementação da API não está em conformidade com a RFC 7662, mas do ponto de vista daqueles que desenvolvem pontos finais de recursos protegidos, nossa API é melhor que a RFC 7662 conforme listado abaixo.
Simplesmente dizendo, API de introspecção de Authlete faz introspecção + validação + construindo uma mensagem de erro cumprindo com rfc 6750. Você pode encontrar a especificação detalhada da API de introspecção da Authlete em Documento de APIs da Web Authlete.
O próximo passo depois de obter informações detalhadas sobre um token de acesso é validar o token de acesso. A validação envolve as seguintes etapas.
Como mencionado, você pode delegar validação para a API de introspecção da Authlete, se quiser. scopes
e subject
são parâmetros opcionais para a API de introspecção para especificar os escopos (= permissões) que o ponto final de recurso protegido requer e o assunto (= identificador exclusivo) de um usuário final que o ponto final de recurso protegido espera.
Se você utilizar scopes
parâmetro e subject
parâmetro de acordo com suas necessidades, você não precisa escrever código de validação separado.
Se você quiser cumprir RFC 6750, as respostas dos pontos finais de recursos protegidos devem conter o cabeçalho WWW-Authenticate sobre erros. O formato detalhado do valor do cabeçalho é descrito em “3. O campo de cabeçalho de resposta www-autenticado”. O formato não é tão simples como você pode imaginar. Abaixo está um exemplo com parâmetros mencionados em RFC 6750 (alguns são opcionais, porém).
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
scope="read write",
error="invalid_token",
error_description="The access token expired",
error_uri="http://example.com/error/123"
Você pode ser seduzido a ignorar essa especificação porque você pode duvidar que vale a pena fazer um esforço apenas para contar detalhes de erro para aplicativos de clientes. No entanto, você não precisa fazer um compromisso se você usar a API de introspecção da Authlete. É porque uma resposta da API contém responseContent
propriedade cujo valor pode ser usado como o valor de WWW-Authenticate
cabeçalho. Como resultado, você pode facilmente construir uma resposta de erro como abaixo.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Just embed responseContent here.
Se um erro for detectado na validação, um ponto final de recurso protegido deve retornar 400 Bad Request
, 401 Unauthorized
ou 403 Forbidden
(ou 500 Internal Server Error
), com base na causa do erro.
Por outro lado, quando um token de acesso é válido, um ponto final de recurso protegido pode gerar qualquer resposta que ele gosta. Em um caso típico, ele retornará o recurso solicitado no formato JSON com 200 OK
à aplicação do cliente.
Para facilitar o envio com base no resultado de validação, uma resposta da API de introspecção da Authlete contém action
propriedade. É uma sequência que representa o próximo action
você deve tomar. Valores de ação e seus significados são os seguintes.
Valor | Ou seja, |
---|---|
INTERNAL_SERVER_ERROR |
Ocorreu um erro em Authlete. 500 Erro do servidor interno deve ser devolvido ao aplicativo do cliente. |
BAD_REQUEST |
A solicitação do aplicativo cliente não contém um token de acesso. 400 Pedido de Ruim deve ser devolvido ao pedido do cliente. |
UNAUTHORIZED |
O token de acesso apresentado não existe ou expirou. 401 Não autorizado deve ser devolvido ao aplicativo do cliente. |
FORBIDDEN |
O token de acesso não cobre os escopos necessários ou o assunto associado ao token de acesso é diferente do esperado. 403 Proibido deve ser devolvido ao pedido do cliente. |
OK |
O token de acesso é válido. |
Abaixo estão as implementações de exemplo para despachar de acordo com o resultado de validação. Cada método/implementação de função (1) aceita um token de acesso [obrigatório], uma matriz de sequência de escopos [opcional] e um assunto [opcional], (2) chama a API de introspecção e (3) despacha o fluxo de acordo com o valor de action
propriedade na resposta da API de introspecção. Quando action
não é OK
, responseContent
propriedade na resposta da API de introspecção é usado como o valor de WWW-Authenticate
cabeçalho na resposta a um aplicativo cliente.
/\*\*
* Ligue para a API/auth/introspecção da Authlete.
* Uma resposta da API é devolvida quando o
* o token de acesso é válido. Caso contrário, um
* WebApplicationException é levantado.
\*/
introspecção estática privadaResponse doIntrospection(
Acesso de stringToken, string\[] escopos, sujeito string)
{
Ligue para a API/auth/introspecção da Authlete.
Resposta de resposta de introspecção = chamadaIntrospectionApi (accessToken, escopos, assunto);
O valor do cabeçalho 'WWW-Authenticate' no erro.
Conteúdo de string = response.getResponseContent();
status int;
Confira a próxima ação.
switch (response.getAction())
{
INTERNAL\_SERVER\_ERROR caso:
500 erros internos do servidor.
O pedido de API desta implementação estava errado
ou um erro ocorreu em Authlete.
status = 500;
quebrar;
case BAD_REQUEST:
// 400 Bad Request.
// The request from the client application does not
// contain an access token.
status = 400;
break;
case UNAUTHORIZED:
// 401 Unauthorized.
// The presented access token does not exist or has expired.
status = 401;
break;
case FORBIDDEN:
// 403 Forbidden.
// The access token does not cover the required scopes
// or the subject associated with the access token is
// different.
status = 403;
break;
case OK:
// The access token is valid (= exists and has not expired).
return response;
default:
// This never happens.
throw new WebApplicationException("Unknown action", 500);
}
Resposta res = Resposta
.status(status)
.header (HttpHeaders.WWW\_AUTHENTICATE, conteúdo)
.build();
lançar novo WebApplicationException(res);
}
\#--------------------------------------------------
# Ligue para a API/auth/introspecção da Authlete.
# Uma resposta da API é devolvida quando o
# o token de acesso é válido. Caso contrário, um WebException
# é levantada.
\#--------------------------------------------------
def do\_introspection (token, escopos, assunto)
# Ligue para a API/auth/introspecção da Authlete.
resposta = call\_introspection\_api (token, escopos, assunto)
# O valor do cabeçalho 'WWW-Authenticate' no erro.
conteúdo = resposta\["respostaContent"]
# "Ação" denota a próxima ação.
resposta de caso \["ação"]
quando "INTERNAL\_SERVER\_ERROR"
\# 500 Erro do servidor interno
\# O pedido de API desta implementação estava errado
\# ou um erro ocorreu em Authlete.
criar WebResponse.new(500).wwwAuthenticate(content).to\_exception
quando "BAD\_REQUEST"
\# 400 Pedido ruim
\# A solicitação do aplicativo do cliente não
\# contenha um token de acesso.
levantar o WebResponse.new(400).wwwAuthenticate(content).to\_exception
quando "NÃO AUTORIZADO"
\# 401 Não Autorizado
\# O token de acesso apresentado não existe ou expirou.
criar WebResponse.new(401).wwwAuthenticate(content).to\_exception
quando "PROIBIDO"
\# 403 Proibido
\# O token de acesso não cobre os escopos necessários
\# ou o assunto associado ao token de acesso é
\# diferente.
criar WebResponse.new(403).wwwAuthenticate(content).to\_exception
quando "OK"
\# O token de acesso é válido (= existe e não expirou).
resposta de retorno
mais
Isso nunca acontece.
levantar o WebResponse.new(500, "Ação desconhecida").plain.to\_exception
fim
fim
/\*\*
* Ligue para a API/auth/introspecção da Authlete.
* Uma resposta da API é devolvida quando o
* o token de acesso é válido. Caso contrário, um WebException
* é levantada.
\*/
do\_introspection de função ($token, $scopes, $subject)
{
Ligue para a API/auth/introspecção da Authlete.
$response = call\_introspection\_api($token, $scopes, $subject);
valor do cabeçalho 'WWW-Authenticate' no erro.
$content = $response\['responseContent'];
"Ação" denota a próxima ação.
switch ($response\['action'])
{
caso 'INTERNAL\_SERVER\_ERROR':
500 erros internos do servidor
O pedido de API desta implementação estava errado
ou um erro ocorreu em Authlete.
(novo WebResponse(500)->wwwAuthenticate($content)->finish();
retorno nulo; Não alcance aqui.
case 'BAD_REQUEST':
// 400 Bad Request
// The request from the client application does not
// contain an access token.
(new WebResponse(400))->wwwAuthenticate($content)->finish();
return null; // Not reach here.
case 'UNAUTHORIZED':
// 401 Unauthorized
// The presented access token does not exist or has expired.
(new WebResponse(401))->wwwAuthenticate($content)->finish();
return null; // Not reach here.
case 'FORBIDDEN':
// 403 Forbidden
// The access token does not cover the required scopes
// or the subject associated with the access token is
// different.
(new WebResponse(403))->wwwAuthenticate($content)->finish();
return null; // Not reach here.
case 'OK':
// The access token is valid (= exists and has not expired).
return $response;
default:
// This never happens.
(new WebResponse(500, "Unknown action"))->plain()->finish();
return null; // Not reach here.
}
}
Esta seção mostra implementações de exemplo para (1) extrair um token de acesso e (2) validar o token de acesso em um ponto final de recurso protegido (a chamada API web). Cada implementação requer dois escopos, read
e write
.
sequência final privada\[] SCOPES = {"ler", "escrever" };
@GET
olá resposta pública(
@HeaderParam (HttpHeaders.AUTHORIZATION) Autorização de string,
@QueryParam ("access\_token") Acesso à seqüênciatoken)
{
Extrair um token de acesso da solicitação.
accessToken = extratoAken (autorização, acessoToken);
// Introspect and validate the access token.
IntrospectionResponse response = doIntrospection(accessToken, SCOPES, null);
// The access token is valid. Write the main code after here.
......
}
obter '/Olá' do
# Extrair um token de acesso da solicitação.
token = extract\_access\_token(solicitação)
começar
\# Introspect e valide o token de acesso.
resposta = do\_introspection(token, \["ler", "escrever"], nil)
resgatar WebException => e
\# O token de acesso é inválido.
e.to\_response de retorno
fim
# O token de acesso é válido. Escreva o código principal depois daqui.
......
fim
<?php
require('../include/introspection-functions.php');
// Extract an access token from the request.
$token = extract_access_token();
// Introspect and validate the access token.
$response = do_introspection($token, ['read', 'write'], null);
// The access token is valid. Write the main code after here.
......
?>
No contexto da OAuth, âmbito é um termo técnico e representa uma permissão. Quando um aplicativo de cliente faz uma solicitação de autorização para um endpoint de autorização de um serviço, o aplicativo pode listar escopos que deseja usando scope
parâmetro. A seguir, uma solicitação de autorização de exemplo especificando dois escopos: read
e write
.
https://host/authorize?response_type=token&client_id=your-client-id&scope=read+write
Depois que o usuário final autoriza a solicitação de autorização, o servidor de autorização emite um token de acesso que está associado aos escopos. O aplicativo cliente usa o token de acesso quando acessa pontos finais de recursos protegidos do serviço. A seguir, uma solicitação de recurso protegido por exemplo que usa access_token parâmetro de consulta para passar por um access token
para um ponto final de recurso protegido.
https://host/profile?access_token=access-token
Um ponto final de recurso protegido extrai um token de acesso da solicitação de recurso protegido e valida o token de acesso. Em um caso típico, uma implementação do ponto final de recurso protegido verifica se o token de acesso apresentado está associado a escopos que o ponto final requer. A seguir, um código conceitual para extrair e validar um token de acesso.
// Extract an access token from the protected resource request.
access_token = extract_access_token_from_request(request);
// If the access token has expired.
if (access_token.has_expired) {
return unauthorized;
}
// Scopes associated with the access token.
scopes = access_token.scopes;
// If the access token covers "read" and "write".
if (scopes.contains("read") && scopes.contains("write")) {
return ok;
} else {
return forbidden;
}
Um nome de escopo é um caractere ASCII sensível a maiústos. A especificação exata é descrita em “RFC 6749, 3.3. Escopo de token de acesso”, como mostrado abaixo. %x20
(espaço), %x22
(marca de cotação dupla) e %x5C
(barra traseira) não é permitido usar.
scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
De acordo com a especificação, %x2C
(vírgula) pode ser usado para nomes de escopo, mas o Facebook usa vírgulas como delimitador para listar nomes de escopo. É uma violação contra a especificação.
Núcleo de conexão OpenID 1.0 definiu alguns escopos padrão. A tabela abaixo é a lista.
Os escopos precisam ser registrados com antecedência antes do uso. Como cadastrar os escopos depende de cada implementação do servidor de autorização.
Authlete fornece GUI para você editar a lista de escopos suportados pelo seu serviço. Login Console do proprietário de serviço, clique no botão “Editar” de um serviço cujos escopos deseja editar e clique na guia “Token”. Você pode ver “escopos suportados” na parte inferior como abaixo.
Depois de editar a lista, não se esqueça de pressionar “Atualizar” para aplicar alterações.
Authlete fornece uma API de introspecção (/api/auth/introspection
) para implementações de pontos finais de recursos protegidos. A API é melhor do que RFC 7662 porque ele não só introspecção, mas também validação e construção de uma mensagem de erro cumprindo RFC 6750.