Table of Contents
Este documento descreve como construir um servidor de autorização que suporte CIBA (Autenticação backchannel iniciada pelo cliente).
É essencial ter uma visão geral da CIBA com antecedência. Consulte os seguintes documentos, conforme necessário.
Também é necessário entender OAuth 2.0, OpenID Connect e Authlete. Consulte os seguintes documentos, conforme necessário.
Construiremos um servidor de autorização apoiado pela Authlete, como é mostrado no diagrama abaixo.
A implementação da amostra introduzida neste artigo é apenas para apreender como implementar o CIBA usando Authlete. Alguns exemplos não são otimizados intencionalmente para ajudá-lo a entender os fluxos de processamento. Por exemplo, a implementação da amostra neste artigo tem as seguintes restrições:
O servidor de autorização na implementação da amostra suporta apenas o fluxo CIBA, não o OAuth 2.0 ou o OpenID Connect.
O ponto final de autenticação do backchannel nesta implementação da amostra não suporta parâmetros opcionais, como login_hint_token
, id_token_hint
e acrs
.
O ponto final de autenticação do backchannel e o ponto final do token nesta implementação de amostra só suportam client_secret_basic
como o método de autenticação do cliente.
Consulte os seguintes códigos-fonte para implementações mais sofisticadas e práticas.
A “especificação” e “especificação” neste artigo referem-se a OpenID Connect Client Iniciou fluxo de autenticação do backchannel - Core 1.0 a menos que anotado.
O suporte ciba está agora disponível apenas para o plano Enterprise. Por favor entre em contato conosco se você estiver interesetado no teste de funções CIBA de Authlete.
No fluxo CIBA, ponto final de autenticação do backchannel e ponto final token desempenham papéis essenciais. Aqui, explicamos como implementar os pontos finais.
O diagrama abaixo explica o fluxo de processamento em cada ponto final ao implementar o CIBA com authlete. Por favor, note que o diagrama omite manipulações de erros.
O ponto final de autenticação do backchannel usa seguintes APIs Authlete.
/api/backchannel/authentication
API/api/backchannel/authentication/issue
API/api/backchannel/authentication/complete
APINa situação real, o ponto final também usa /api/backchannel/authentication/fail
API para manipulação de erros. Por favor, consulte Identificando um usuário final usando dica para detalhes.
O fluxo de processamento do ponto final do token é simples, como é o caso do OAuth 2.0. O servidor de autorização envia uma solicitação de token de um cliente para /api/auth/token
API e envia de volta uma resposta de token da Authlete API para o cliente.
O ponto final de autenticação do backchannel lida com várias solicitações e respostas. Para implementar o ponto final, você deve entender o básico do ponto final primeiro.
✔︎ Authentication Request
A solicitação enviada ao ponto final de autenticação do backchannel é chamada de “solicitação de autenticação”. A especificação requer que o método HTTP e Content-Type
ser POST
e application/x-www-form-urlencoded
respectivamente.
✔︎ Client Authentication
O ponto final de autenticação do backchannel deve autenticar um cliente. Como é explicado em este artigo, o CIBA permite vários métodos de autenticação do cliente; no entanto, a implementação da amostra aqui suporta apenas client_secret_basic
para simplificação. Observe que o método de autenticação do cliente no ponto final de autenticação do backchannel deve ser o mesmo que no ponto final do token.
Usamos o seguinte código como modelo para implementar o ponto final de autenticação do backchannel no servidor de autorização usando Java (JAX-RS).
@Path("/api/backchannel/authentication")
public class BackchannelAuthenticationEndpoint
{
/**
* Backchanel Authentication Endpoint
* The endpoint accepts a request with POST and application/x-www-form-urlencoded.
* Also, in this artichle, we explain the case of the client_secret_basic only.
*
* @param authorization
* The value of Authorization header in the backchannel authentication request.
*
* @param parameters
* Request parameters of a backchannel authentication request.
*/
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response post(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorization, MultivaluedMap<String, String> parameters)
{
try
{
// main process
return doProcess(authorization, parameters);
}
catch (WebApplicationException e)
{
// known errors
return e.getResponse();
}
catch (Throwable t)
{
// unknown errors
return ResponseUtil.internalServerError("Unknown error occurred.");
}
}
private Response doProcess(String authorization, MultivaluedMap<String, String> parameters)
{
// main process implementation
}
}
As seções a seguir adicionarão processos ao modelo e implementarão o ponto final.
A primeira coisa que o ponto final de autenticação do backchannel deve fazer é verificar a solicitação de autenticação. Os passos serão os seguintes:
authorization
parâmetro./backchannel/authentication
API com as informações extraídas e parameters
parâmetro, e delegar o processo de verificação para Authlete.action
valor do parâmetro em uma resposta de /backchannel/authentication
API.E os códigos para os processos são os seguintes.
private Response doProcess(String authorization, MultivaluedMap<String, String> parameters)
{
// Parse the Authorization header and extract client credentials.
BasicCredentials credentials = BasicCredentials.parse(authorization);
// Extract the client ID and the client secret from the client credentials.
String clientId = credentials == null ? null : credentials.getUserId();
String clientSecret = credentials == null ? null : credentials.getPassword();
// Call Authlete's /api/backchannel/authentication API to delegate the
// verification process of the request to Authlete.
BackchannelAuthenticationResponse response =
callBackchannelAuthenticationApi(parameters, clientId, clientSecret);
// The 'action' parameter in the response denotes the next action that
// this authorization server should take.
BackchannelAuthenticationResponse.Action action = response.getAction();
// The content of the response that should be returned to the client.
// The content varies depending on the 'action'.
String content = response.getResponseContent();
// Process according to the 'action'.
switch (action)
{
case INTERNAL_SERVER_ERROR:
// The API call was wrong or an error occurred on Authlete side.
// 500 Internal Server Error
return ResponseUtil.internalServerError(content);
case UNAUTHORIZED:
// The client authentication failed. For example, the client ID was wrong.
// 401 Unauthorized
return ResponseUtil.unauthorized(content, "Basic realm=\"backchannel/authentication\"");
case BAD_REQUEST:
// The request was wrong. For example, mandatory request parameters are not included.
// 400 Bad Request
return ResponseUtil.badRequest(content);
case USER_IDENTIFICATION:
// The request is valid. In this case, handleUserIdentification() handles
// the remaining steps.
return handleUserIdentification(response);
default:
// Other cases. This should not happen.
// 500 Internal Server Error
return ResponseUtil.internalServerError("Unknown action returned from /api/backchannel/authentication API.");
}
}
Se a solicitação não tem nenhum problema, o /backchannel/authentication
A API envia uma resposta com action=USER_IDENTIFICATION
. Neste caso de USER_IDENTIFICATION
, o servidor de autorização chamará o handleUserIdentification()
método para lidar com o pedido de tiantication. Outros valores no action
o parâmetro indica um erro, então o servidor de autorização gera uma resposta de erro e envia para o cliente.
Quando o /backchannel/authentication
A API retorna uma resposta com action=USER_IDENTIFICATION
, o servidor de autorização deve identificar o usuário final usando uma dica na resposta. A dica é qualquer um login_hint
, login_hint_token
ou id_token_hint
. Para simplificação, o servidor de autorização nesta amostra de impementação suporta login_hint
somente. O valor pode ser um assunto do usuário final, endereço de e-mail ou número de telefone na amostra a seguir.
Na identificação do usuário final usando com sucesso o login_hint
, o servidor de autorização fará os seguintes processos. Caso contrário, o servidor de autorização chama /backchannel/authentication/fail
API, gera uma resposta de erro e devolve ao cliente.
Os seguintes códigos incluem processos que levarão em conta a discussão acima.
private Response handleUserIdentification(BackchannelAuthenticationResponse baRes)
{
// Identify the end-user using the hint included in the request.
User user = identifyUserByHint(baRes);
}
private User identifyUserByHint(BackchannelAuthenticationResponse baRes)
{
// The type of the hint included in the request. The value is
// either LOGIN_HINT, LOGIN_HINT_TOKEN, or ID_TOKEN_HINT.
UserIdentificationHintType hintType = baRes.getHintType();
// The hint included in the request.
String hint = baRes.getHint();
// Find the end-user using the hint.
User user = getUserByHint(hintType, hint);
if (user != null)
{
// The end-user was found.
return user;
}
// No end-user was found with the hint. In this case, call Authlete's
// /api/backchannel/authentication/fail API in order to generate an
// appropriate response and throw it as an exception. Note that the
// exception will be caught by the post() method.
throw backchannelAuthenticationFail(baRes.getTicket(), Reason.UNKNOWN_USER_ID);
}
private User getUserByHint(UserIdentificationHintType hintType, String hint)
{
if (hintType != UserIdentificationHintType.LOGIN_HINT)
{
// This implementation supports login_hint only, so other hint
// types are ignored.
return null;
}
// Find the end-user using the login_hint. The implementation below
// assumes that either an end-user's subject, email address or phone
// number is used as the value of the login_hint parameter.
// First, look up the end-user assuming that the login_hint is an
// end-user's subject.
User user = UserDao.getBySubject(hint);
if (user != null)
{
// The end-user was found.
return user;
}
// Next, assume the login_hint is an end-user's email address.
user = UserDao.getByEmail(hint);
if (user != null)
{
// The end-user was found.
return user;
}
// Finally, assume the login_hint is an end-user's phone number.
return UserDao.getByPhoneNumber(hint);
}
private WebApplicationException backchannelAuthenticationFail(String ticket, BackchannelAuthenticationFailRequest.Reason reason)
{
// Call Authlete's /api/backchannel/authentication API to generate an
// appropriate response.
Response response = createBackchannelAuthenticationFailResponse(ticket, reason);
// Generate an exception that respresents the response.
return new WebApplicationException(response);
}
private Response createBackchannelAuthenticationFailResponse(String ticket, BackchannelAuthenticationFailRequest.Reason reason)
{
// Call Authlete's /api/backchannel/authentication/fail API.
BackchannelAuthenticationFailResponse response = callBackchannelAuthenticationFail(ticket, reason);
// The 'action' parameter in the response denotes the next action that
// this authorization server should take.
BackchannelAuthenticationFailResponse.Action action = response.getAction();
// The content of the response that should be returned to the client.
// The content varies depending on the 'action'.
String content = response.getResponseContent();
// Process according to the 'action'.
switch (action)
{
case INTERNAL_SERVER_ERROR:
// 500 Internal Server Error
return ResponseUtil.internalServerError(content);
case FORBIDDEN:
// 403 Forbidden
return ResponseUtil.forbidden(content);
case BAD_REQUEST:
// 400 Bad Request
return ResponseUtil.badRequest(content);
default:
// Other cases. This should not happen.
// 500 Internal Server Error
return ResponseUtil.internalServerError("Unknown action returned from /api/backchannel/authentication/fail.");
}
}
Além da identificação do usuário final, o servidor de autorização deve verificar parâmetros de solicitação no handleUserIdentification()
método, como está descrito nas seções a seguir.
login_hint_token
Ao usar login_hint_token
como dica, o servidor de autorização deve verificar sua expiração; no entanto, neste artigo, não implementaremos a verificação para a simplificação. Por favor, verifique o java-oauth-servidor e authlete-java-jaxrs para detalhes.
user_code
user_code
Na implementação da amostra acima, o servidor de autorização espera um assunto do usuário final, endereço de e-mail e número de telefone para identificar o usuário final. No entanto, uma terceira pessoa pode ser capaz de adivinhar esses valores.
O user_code
é um parâmetro para evitar que terceiros mal-intencionados enviem solicitações de autenticação de fraude. A especificação define o user_code
como segue:
user_code OPCIONAL. Um código secreto, como senha ou pino, conhecido apenas pelo usuário, mas verificável pela OP. O código é usado para autorizar o envio de uma solicitação de autenticação ao dispositivo de autenticação do usuário. Este parâmetro só deve estar presente se o parâmetro de registro do cliente backchannel_user_code_parameter indicar suporte para código do usuário.
Os clientes maliciosos não podem adivinhar o user_code
e, assim, enviar uma solicitação de autenticação válida. Em outras palavras, um servidor de autorização que verifica user_code
pode rejeitar os pedidos maliciosos.
user_code
Vamos verificar a verificação de um user_code
.
O servidor Authlete API fará o seguinte processo quando um servidor de autorização chamar o /backchannel/authentication
API.
if (service.isBackchannelUserCodeParameterSupported() && client.isBcUserCodeRequired())
{
if (isUserCodeContainedInRequestParameters() == false)
{
// returns an error response with action=BAD_REQUEST to the requesting authorization server
throw missingUserCodeError();
}
}
Quando os valores de ambos os backchannelUserCodeParameterSupported
metadados do serviço que corresponde ao servidor de autorização e o bcUserCodeRequired
metadados do cliente são verdadeiros, Authlete vai verificar se uma solicitação de autenticação contém user_code
. A API Authlete retorna uma resposta de erro com action=BAD_REQUEST
para o servidor de autorização se o servidor de autorização enviar uma solicitação de autenticação que não contenha user_code
. Neste caso, o servidor de autorização faz o processo para o caso de BAD_REQUEST
na implementação da amostra acima.
Neste artigo, vamos implementar um handleUserIdentification()
método, que lida com o caso para USER_IDENTIFICATION
. Durante USER_IDENTIFICATION
cenário, temos que implementar o método antecipando um dos casos listados abaixo.
✔º Caso 1
Um user_code
foi encontrado no pedido de autenticação. (= No caso do pseudo código acima, as condições de service.isBackchannelUserCodeParameterSupported() && client.isBcUserCodeRequired()
e isUserCodeContainedInRequestParameters()
foram satisfatificados.)
✔º Caso 2
A API Authlete não verifica o user_code
. (= No caso do pseudo código acima, a condição de service.isBackchannelUserCodeParameterSupported() && client.isBcUserCodeRequired()
não foi satisfatizado.)
No caso 1, a API Authlete garante que a solicitação de autenticação contém um user_code
. Assim, o servidor de autorização deve verificar se o valor de user_code
é válido. Podemos pular o cheque para o caso 2 porque uma solicitação de autenticação não tem que conter o user_code
Parâmetros.
Aqui está uma implementação amostral de handleUserIdentification()
.
private Response handleUserIdentification(BackchannelAuthenticationResponse baRes)
{
...
// Check the user code.
checkUserCode(baRes, user);
}
private void checkUserCode(BackchannelAuthenticationResponse baRes, User user)
{
// If a user code is not mandatory (= not Case 1)
//
// Note that isUserCodeRequired() method returns true when both the
// backchannelUserCodeParameterSupported metadata of the service (the
// authorization server) and the bcUserCodeRequired metadata of the
// client are true. In other cases, the method returns false.
if (baRes.isUserCodeRequired() == false)
{
// Nothing is checked here.
return;
}
// The value of the user_code in the request.
String userCodeInRequest = baRes.getUserCode();
// The valid user code.
String userCode = user.getCode();
// If they match.
if (userCodeInRequest.equals(userCode))
{
// The request includes the valid user code.
return;
}
// The user code included in the request is wrong. In this case,
// call Authlete's /api/backchannel/authentication/fail API in
// order to generate an appropriate response and then throw it
// as an exception. Note that the exception will be caught by
// the post() method later.
throw backchannelAuthenticationFail(baRes.getTicket(), Reason.INVALID_USER_CODE);
}
Quando um cliente inicia o fluxo CIBA, um usuário final será solicitado a autorizar em seu dispositivo de autenticação. Ao autorizar, como o usuário final confirma que o cliente que pede autorização através do dispositivo é o único que o usuário final está disposto a autorizar?
O binding_message
parâmetro é introduzido para resolver o problema acima. O diagrama abaixo é um caso de uso do binding_message
parâmetro.
Um usuário final pede a uma loja para pagar usando um serviço de pagamento apoiado pela CIBA.
A loja mostra uma mensagem vinculante, sRj89xCg, no terminal de PDV (= aplicativo do cliente).
O terminal de PDV envia uma solicitação de autenticação, incluindo a mensagem vinculante, ao servidor de autorização do serviço de pagamento.
O servidor de autorização começa a interagir com um dispositivo de autenticação do usuário final e envia a mensagem de vinculação para o dispositivo.
O dispositivo de autenticação pede ao usuário final que autorize o cliente, mostrando a mensagem de vinculação de sRj89xCg.
O usuário final confirma o cliente usando a mensagem de vinculação e autoriza a transação.
Então, como o servidor de autorização verifica a mensagem de vinculação? Abaixo está uma citação da especificação. A especificação não define os requisitos MUST para a mensagem de vinculação e apenas diz que o binding_message
valor DEVE ser relativamente curto e usar um conjunto limitado de caracteres de texto simples.
binding_message OPCIONAL. Um identificador ou mensagem legível humana destinado a ser exibido tanto no dispositivo de consumo quanto no dispositivo de autenticação para interligá-los para a transação por meio de uma sugestão visual para o usuário final. Esta mensagem de bloqueio permite ao usuário final garantir que a ação tomada no dispositivo de autenticação esteja relacionada à solicitação iniciada pelo dispositivo de consumo. O valor DEVE conter algo que permite ao usuário final discernir de forma confiável que a transação está relacionada entre o dispositivo de consumo e o dispositivo de autenticação, como um valor aleatório de entropia razoável (por exemplo, um código de aprovação transacional). Como os vários dispositivos envolvidos podem ter habilidades limitadas de exibição e a mensagem pretende inspeção visual pelo usuário final, o binding_message valor DEVE ser relativamente curto e usar um conjunto limitado de caracteres de texto simples. A invalid_binding_message definida na Seção 13 é utilizada no caso de ser necessário informar ao Cliente que o binding_message fornecido é inaceitável.
Aqui, nós adicionamos um handleUserIdentification()
método para verificar o comprimento da mensagem de vinculação.
/**
* The maximum number of characters in a binding message.
*/
private static String MAX_BINDING_MESSAGE_LENGTH = 100;
...
private Response handleUserIdentification(BackchannelAuthenticationResponse baRes)
{
...
// Check the binding message.
checkBindingMessage(baRes);
}
private void checkBindingMessage(BackchannelAuthenticationResponse baRes)
{
// The binding message included in the request.
String bindingMessage = baRes.getBindingMessage();
// If no binding message is available.
if (bindingMessage == null || bindingMessage.length() == 0)
{
// Nothing is checked here.
return;
}
// If the length of the binding message exceeeds the upper limit.
if (bindingMessage.length() > MAX_BINDING_MESSAGE_LENGTH)
{
// The request includes an invalid binding message (whose length
// exceeds the upper limit). In this case, call Authlete's
// /api/backchannel/authentication/fail API in order to generate
// an appropriate response and throw it as an exception. Note
// that the exception will be caught later by the post() method.
throw backchannelAuthenticationFail(baRes.getTicket(), Reason.INVALID_BINDING_MESSAGE);
}
}
###2.2.4 Emitindo um auth_req_id
Depois que o servidor de autorização terminou todas as verificações necessárias, ele emite um auth_req_id
Usando /backchannel/authentication/issue
API.
private Response handleUserIdentification(BackchannelAuthenticationResponse baRes)
{
...
// Issue an auth_req_id.
return issueAuthReqId(baRes);
}
private Response issueAuthReqId(BackchannelAuthenticationResponse baRes)
{
// Call Authlete's /api/backchannel/authentication/issue API.
// The API issues an 'auth_req_id'.
BackchannelAuthenticationIssueResponse baiRes = callBackchannelAuthenticationIssue(baRes.getTicket());
// The 'action' parameter in the response denotes the next action
// that this authorization server should take.
BackchannelAuthenticationIssueResponse.Action action = baiRes.getAction();
// The content of the response that should be returned to the client.
// The content varies depending on the 'action'.
String content = baiRes.getResponseContent();
// Process according to the 'action'.
switch (action)
{
case INTERNAL_SERVER_ERROR:
// The API call was wrong or an error occurred on Authlete side.
// 500 Internal Server Error
return ResponseUtil.internalServerError(content);
case INVALID_TICKET:
// The ticket included in the API call was invalid.
// 500 Internal Server Error
return ResponseUtil.internalServerError(content);
case OK:
// Start a background process.
startCommunicationWithAuthenticationDevice(user, baRes);
// 200 OK with an 'auth_req_id'.
return ResponseUtil.ok(content);
default:
// Other cases. This should not happen.
// 500 Internal Server Error
return ResponseUtil.internalServerError("Unknown action returned from /api/backchannel/authentication/issue API.");
}
}
Quando o servidor de autorização recebe uma resposta com action=OK
, o servidor inicia um processo de fundo chamando o startCommunicationWithAuthenticationDevice()
método, e envia um 200 OK
resposta ao cliente. Por favor, note que a resposta ao cliente contém o auth_req_id
emitido pela API Authlete.
###2.2.5. Processos de antecedentes
O servidor de autorização inicia o processo de fundo chamando de startCommunicationWithAuthenticationDevice()
método antes de enviar o auth_req_id
para o cliente. Nesse processo de segundo plano, o servidor de autorização fará as seguintes tarefas.
/backchannel/authentication/complete
API e diz ao Authlete o resultado da comunicação entre o servidor de autorização e o dispositivo de autenticação./backchannel/authentication/complete
API.Por favor, note que o conteúdo da resposta do /backchannel/authentication/complete
A API varia de acordo com o modo de entrega de tokens backchannel. Para ser concreto, em casos de sucesso, a API retorna action=OK
no modo POLL enquanto ele retorna action=NOTIFICATION
nos modos PING e PUSH. Quando o action
na resposta da API é NOTIFICATION
, o servidor de autorização deve enviar uma notificação para o ponto final da notificação do cliente.
Aqui está a implementação amostral dos processos de fundo.
private void startCommunicationWithAuthenticationDevice(User user, BackchannelAuthenticationResponse info)
{
// The ticket which is necessary to call Authlete's
// /api/backchannel/authentication/complete API after the communication
// with the authentication device is done.
final String ticket = info.getTicket();
// The name of the client.
final String clientName = info.getClientName();
// The scopes requested by the client.
final Scope[] scopes = info.getScopes();
// The claims requested by the client.
final String[] claimNames = info.getClaimNames();
// The binding message which will be displayed on the authentication device.
final String bindingMessage = info.getBindingMessage();
// Start a background process.
Executors.newSingleThreadExecutor().execute(new Runnable() {
try
{
// The main part of the background process.
doInBackground(ticket, user, clientName, scopes, claimNames, bindingMessage);
}
catch (WebApplicationException e)
{
// Log the error.
Logger.log(e);
}
catch (Throwable t)
{
// Log the error.
Logger.log(t);
}
});
}
private void doInBackground(String ticket, User user, String clientName, Scope[] scopes, String[] claimNames, String bindingMessage)
{
// A response from the authentication device.
MyAuthenticationDeviceResponse response;
try
{
// Communicate with the authentication device.
response = communicateWithMyAuthenticationDevice(user.getSubject(), buildMessage());
}
catch (Throwable t)
{
// An error occurred during communication with the authentication device.
// In this case, call Authlete's /api/backchannel/authentication/complete
// API with result=TRANSACTION_FAILED.
completeWithTransactionFailed(ticket, user);
return;
}
// The decision made by the end-user on the authentication device
// (= whether the end-user has authorized the request from the client).
MyAuthenticationDeviceResult result = response.getResult();
if (result == null)
{
// The result is empty. This should not happen. In this case,
// call Authlete's /api/backchannel/authentication/complete API
// with result=TRANSACTION_FAILED.
completeWithTransactionFailed(ticket, user);
return;
}
switch (result)
{
case allow:
// When the end-user has authorized the request. Call the
// Authlete API with result=AUTHORIZED.
completeWithAuthorized(ticket, user, claimNames, new Date());
return;
case deny:
// When the end-user has rejected the request. Call the
// Authlete's API with result=ACCESS_DENIED.
completeWithAccessDenied(ticket, user);
return;
case timeout:
// When the communication with the authentication device
// timed out. Call the Authlete's API with
// result=TRANSACTION_FAILED.
completeWithTransactionFailed(ticket, user);
return;
default:
// Unknown result. This should not happen. Call the Authlete's
// API with result=TRANSACTION_FAILED.
completeWithTransactionFailed(ticket, user);
return;
}
}
/**
* Call Authlete's /api/backchannel/authentication/complete API
* with result=AUTHORIZED.
*/
private void completeWithAuthorized(String ticket, User user, String[] claimNames, Date authTime)
{
complete(ticket, user, Result.AUTHORIZED, claimNames, authTime);
}
/**
* Call Authlete's /api/backchannel/authentication/complete API
* with result=ACCESS_DENIED.
*/
private void completeWithAccessDenied(String ticket, User user)
{
complete(ticket, user, Result.ACCESS_DENIED, null, null);
}
/**
* Call Authlete's /api/backchannel/authentication/complete API
* with result=TRANSACTION_FAILED.
*/
private void completeWithTransactionFailed(String ticket, User user)
{
complete(ticket, user, Result.TRANSACTION_FAILED, null, null);
}
/**
* Call Authlete's /api/backchannel/authentication/complete API to
* convey the result of the communication with the authentication
* device to Authlete.
*/
private void complete(String ticket, User user, Result result, String[] claimNames, Date authTime)
{
// The subject of the end-user.
String subject = user.getUserSubject();
// The time at which the end-user was authenticated.
// This is used only in the case of result=AUTHORIZED.
long userAuthenticatedAt = (result == Result.AUTHORIZED) ? authTime.getTime() / 1000L : 0;
// Claims of the end-user. This is used only in the case
// of result=AUTHORIZED.
Map<String, Object> claims = (result == Result.AUTHORIZED) ? collectClaims(user, claimNames) : null;
// Call Authlete's /api/backchannel/authentication/complete API
BackchannelAuthenticationCompleteResponse response =
callBackchannelAuthenticationComplete(ticket, subject, result, userAuthenticatedAt, claims);
// The 'action' in the response denotes the next action that
// this authorization server should take.
BackchannelAuthenticationCompleteResponse.Action action = response.getAction();
// Process according to the 'action'.
switch (action)
{
case SERVER_ERROR:
// The API call was wrong or an error occurred on Authlete side.
//
// Throw an exception.
throw new WebApplicationException( ResponseUtil.internalServerError(content) );
case NO_ACTION:
// Nothing to do. This happens when the backchannel token delivery
// mode of the client is the POLL mode.
return;
case NOTIFICATION:
// A notification must be sent to the client's notification endpoint.
// This happens when the backchannel token delivery mode of the
// client is either the PING mode or the PUSH mode.
//
// Notify the client.
handleNotification(response);
return;
default:
// Other cases. This should not happen.
//
// Throw an exception.
throw new WebApplicationException(
ResponseUtil.internalServerError("Unknown action returned from /api/backchannel/authentication/complete API.")
);
}
}
/**
* Send a notification to the client's notification endpoint.
*/
private void handleNotification(BackchannelAuthenticationCompleteResponse info)
{
// The URL of the client's notification endpoint.
URI clientNotificationEndpointUri = info.getClientNotificationEndpoint();
// The notification token which is used as a Bearer token.
String notificationToken = info.getClientNotificationToken();
// The content of the notification sent to the client in JSON format.
String notificationContent = info.getResponseContent();
// A response from the client's notification endpoint.
Response response;
try
{
// Send the notification to the client's notification endpoint.
response = WEB_CLIENT.target(clientNotificationEndpointUri).request()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + notificationToken)
.post(Entity.json(notificationContent));
}
catch (Throwable t)
{
// Failed to send the notification.
throw new WebApplicationException(
ResponseUtil.internalServerError("Failed to send the notification to the client", t)
);
}
// The HTTP status code of the response from the client's notification endpoint.
Status status = Status.fromStatusCode(response.getStatusInfo().getStatusCode());
// Process according to the HTTP status code.
//
// Note that the specification does not describe how the response from
// the client notification endpoint should be handled in the case where
// the authorization server sends an error notification in the PUSH mode.
// Therefore, this implementation handles the responses in the same way as
// in other cases even when an error notification is sent in the PUSH mode.
// When the status code is either '200 OK' or '204 No Content'.
if (status == Status.OK || status == Status.NO_CONTENT)
{
// Based on the specification excerpted as below, regard that
// the request has been processed successfully.
//
// CIBA Core spec, 10.2. Ping Callback and 10.3. Push Callback
// For valid requests, the Client Notification Endpoint SHOULD
// respond with an HTTP 204 No Content. The OP SHOULD also accept
// HTTP 200 OK and any body in the response SHOULD be ignored.
//
return;
}
// When the status code is '3xx'.
if (status.getFamily() == Status.Family.REDIRECTION)
{
// Based on the specification excerpted as below, ignore this case.
//
// CIBA Core spec, 10.2. Ping Callback, 10.3. Push Callback
// The Client MUST NOT return an HTTP 3xx code. The OP MUST
// NOT follow redirects.
//
return;
}
}
A implementação do ponto final do token usando Authlete é muito simples, como é mostrado em Ponto final do token. O servidor de autorização extrai o conteúdo de uma solicitação de token de um cliente e envia as informações extraídas para /auth/token
API de Authlete. Um ponto que deve ser observado é que o método de autenticação do cliente usado no ponto final do token deve ser o mesmo que tem sido usado no ponto final de autenticação do backchannel.
A implementação da amostra neste artigo foca apenas em client_secret_basic
, assim, a implementação a seguir implementa apenas o método de autenticação do cliente.
@Path("/api/token")
public class TokenEndpoint
{
...
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response post(
@HeaderParam(HttpHeaders.AUTHORIZATION) String authorization,
MultivaluedMap<String, String> parameters)
{
try
{
// The main process.
return doProcess(authorization, parameters);
}
catch (WebApplicationException e)
{
// Known error
return e.getResponse();
}
catch (Throwable t)
{
// Unknown error
return ResponseUtil.internalServerError("Unknown error occurred.");
}
}
private Response doProcess(String authorization, MultivaluedMap<String, String> parameters)
{
// Extract client credentials from the Authorization header.
BasicCredentials credentials = BasicCredentials.parse(authorization);
// Extract the client ID and the client secret from the client credentials.
String clientId = credentials == null ? null : credentials.getUserId();
String clientSecret = credentials == null ? null : credentials.getPassword();
// Call Authlete's /api/auth/token API.
TokenResponse response = callToken(parameters, clientId, clientSecret);
// The 'action' parameter in the response denotes the next action that
// this authorization server should take.
Action action = response.getAction();
// The content of the response that should be returned to the client.
// The content varies depending on the 'action'.
String content = response.getResponseContent();
// Process according to the 'action'.
switch (action)
{
case INVALID_CLIENT:
// Client authentication failed.
// 401 Unauthorized
return ResponseUtil.unauthorized(content, "Basic realm=\"token\"");
case INTERNAL_SERVER_ERROR:
// The API call was wrong or an error occurred on Authlete side.
// 500 Internal Server Error
return ResponseUtil.internalServerError(content);
case BAD_REQUEST:
// The token request was invalid.
// 400 Bad Request
return ResponseUtil.badRequest(content);
case OK:
// The token request was valid.
// 200 OK
return ResponseUtil.ok(content);
case PASSWORD:
// In the case of "Resource Owner Password Credentials" flow
// which is defined in OAuth 2.0 (RFC 6749). This does not
// happen in CIBA flows.
// 400 Bad Request
return ResponseUtil.badRequest("PASSWORD action returned from /api/auth/token API but this authorization server does not allow Resource Owner Password Credentials flow.");
default:
// Other cases. This should not happen.
// 500 Internal Server Error
return ResponseUtil.internalServerError("Unknown action returned from /api/auth/token API.");
}
}
...
}
Terminamos de implementar o servidor de autorização que suporta o fluxo CIBA. Agora, vamos testá-lo. A seguinte explicação usa java-oauth-servidor como servidor de autorização e Simulador CIBA como dispositivo de autenticação (AD) e dispositivo de consumo (CD).
Por favor, altere as configurações de um serviço da seguinte forma.
Guia | Valor | |
---|---|---|
CIBA Modos de entrega de tokens backchannel suportados | PING , POLL , PUSH |
|
CIBA Parâmetro de código do usuário backchannel suportado |
Por favor, altere as configurações de um cliente da seguinte forma.
Guia | Valor | |
---|---|---|
básica | do tipo cliente CONFIDENTIAL |
|
de autorização Método de autenticação do cliente | CLIENT_SECRET_BASIC |
|
CIBA Modo de entrega de tokens | (Escolha um modo que você deseja testar) | |
CIBA | de endpoint de notificação https://cibasim.authlete.com/api/notification | |
CIBA Código do usuário exigido |
Insira quaisquer valores em Namespace e Projeto e empurrar Criar botão em a página superior do CIBA Simulator.
Você será redirecionado para a página superior do projeto com a URL de https://cibasim.authlete.com/{namespace}/{project}
. Você pode configurar simuladores de AD e CD.
Para pedir ao usuário final que autorize a solicitação do cliente, java-oauth-servidor com a configuração padrão envia uma solicitação, que inclui um identificador de usuário como é mostrado abaixo, para o simulador /api/authenticate/sync
API.
{
...
"user" : "{the end-user identifier}",
...
}
A implementação amostral do servidor de autorização, java-oauth-servidor, usa o assunto de um usuário como um identificador. Depois do /api/authenticate/sync
A API recebe a solicitação do servidor de autorização, o usuário autoriza ou rejeita a solicitação do cliente no simulador de AD, e o resultado é enviado ao servidor de autorização.
Para iniciar o simulador AD, digite o identificador do usuário final no Identificação de usuário campo e empurrar o Lançar simulador de AD botão. java-oauth-servidor com a configuração padrão usa usuários manequim definidos em Entidade do Usuário.java. Neste artigo, vamos usar subject=1001
como usuário final. Por favor, entre 1001
no Identificação de usuário campo.
Você verá a página assim. Por favor, note que você deve manter esta página aberta durante o teste do fluxo CIBA.
Na página superior do projeto, configure o seguinte e pressione Salvar botão para armazenar a mudança.
Chave | Valor |
---|---|
URL de ponto final de autenticação BC | URL do ponto final de autenticação do backchannel do servidor de autorização |
URL do Token Endpoint | URL do ponto final do token do servidor de autorização |
Modo de entrega de tokens | (Escolha um modo de entrega que deseja testar) |
ID do cliente | Cscient ID do cliente |
Autenticação do cliente | Básico |
Segredo do Cliente | Cliente Secreto do cliente |
E lançar o simulador de CD pressionando o Lançar simulador de CD botão.
Por favor, consulte este artigo para o detalhe do java-oauth-servidor.
Baixe o código-fonte do java-oauth-servidor e configurá-lo em authlete.properties
como segue.
base_url = <base URL of Authlete API (e.g. https://api.authlete.com)>
service.api_key = <API Key of the service>
service.api_secret = <API Secret of the service>
E inicie o java-oauth-server usando o seguinte comando:
mvn jetty:run -Dauthlete.ad.workspace=<CIBA simulator's namespace>/<CIBA simulator's project>
Por favor, configure o modo de entrega nas seguintes configurações
Passo 1. Inicie o simulador de CD e os valores de entrada da seguinte forma. Observe que o valor da dica e do Código do Usuário são definidos em Entidade do Usuário.java neste caso.
Chave | Valor |
---|---|
Escopos | openid |
Valores ACR | (qualquer valor) |
Dica値 | 1001 |
Dica種類 | login_hint |
Mensagem de vinculação | (qualquer valor) |
Código do Usuário | 675325 |
Passo 2. Envie uma solicitação de autenticação do backchannel pressionando o Enviar botão.
Passo 3. O auth_req_id
será enviado ao simulador de CD após o servidor de autorização processar a solicitação.
Passo 4. Você verá a página de autorização no simulador de AD.
Passo 5. Imprensa Permitir botão e autorizar o cliente.
Passo 6. O servidor de autorização pressionará uma resposta que inclui tokens para o simulador de CD.
As etapas 1 a 3 são as mesmas do modo PUSH. O servidor de autorização envia auth_req_id
para o simulador de CD depois que ele processou a solicitação de autenticação do backchannel.
Passo 4. O simulador de CD aguardará uma notificatoin do servidor de autorização no ponto final da notificação.
Passo 5. Após autorizar o cliente no simulador de AD, o servidor de autorização envia uma notificação para o ponto final de notificação do cliente e recebe os tokens.
As etapas 1 a 3 são as mesmas do modo PUSH. O servidor de autorização envia auth_req_id
para o simulador de CD depois que ele processou a solicitação de autenticação do backchannel.
Passo 4. O simulador de CD começa a pesquisar para o ponto final do token. Por favor, note que o ponto final do token retorna authorization_pending
erros até que o cliente seja autorizado no simulador de AD.
Depois que o cliente é autorizado no simulador de AD, o ponto final do token envia uma resposta que inclui tokens.