Recentemente estive em um projeto e tive que desenvolver uma solução bem bacana com integração entre Oracle Webcenter e Siebel. O Siebel trabalha com tokens de segurança nas chamadas de serviços, o problema é que não há implementação de WS-Security que faça a autenticação e tokens de segurança de forma satisfatória e sem problemas. Além disso há outros detalhes de desempenho que a equipe da Siebel disse que é fundamental.
A equipe Siebel queria que fosse associada a sessão Web do usuário com a sessão de conexão com o Siebel para as chamadas de serviços. O problema dessa solução é justamente fazer essa associação de maneira correta (uma não tem nada a ver com a outra) e tratar solicitações paralelas. Isso é fundamental em ambientes Web. Outro problema é que haveriam muitas sessões Siebel ociosas, pois após o usuário realizar a requisição, ele fica analisando os dados e navegando na página, antes de realizar novas requisições. A solução que eu sugeri foi a implementação de um pool de conexões compartilhadas com o Siebel, da mesma maneira que utilizamos para Banco de Dados. Para atender o requisito solicitado pela equipe Siebel, definimos o número de conexões mínimas igual a 1 e o máximo igual ao número de usuários conectados.
Antes de entender o funcionamento do pool Siebel, vamos entender o funcionamento dos tokens no Siebel.
Requisição anônima sem sessão
Essa foi nossa primeira abordagem antes de criar o pool de conexões. No header da requisição SOAP não enviamos nenhuma informação de usuário, apenas definimos o SessionType como None. Para cada requisição ele abre uma sessão e fecha ao enviar a resposta. O problema dessa implementação é que embora seja simples, o tempo de criação de sessão sem o uso do token é bem elevado.
[sourcecode language=”xml”]
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<SessionType xmlns="http://siebel.com/webservices">None</SessionType>
</soap:Header>
<soap:Body>
<!– … –>
</soap:Body>
</soap:Envelope>
[/sourcecode]
Autorização Siebel, sem sessão
Semelhante à anterior, inclusive com os mesmo problemas, mas passando o usuário e senha para autorização.
[sourcecode language=”xml”]
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<UsernameToken xmlns="http://siebel.com/webservices">usuario</UsernameToken>
<PasswordText xmlns="http://siebel.com/webservices">senha</PasswordText>
<SessionType xmlns="http://siebel.com/webservices">None</SessionType>
</soap:Header>
<soap:Body>
<!– … –>
</soap:Body>
</soap:Envelope>
[/sourcecode]
Autorização Siebel com sessão stateless
Essa foi a solução adotada para o pool de conexões com o Siebel que explicarei mais adiante. O funcionamento é simples de entender, mas um pouco trabalhoso de se implementar já que é sempre necessário guardar o último token de cada conexão. A primeira requisição deve realizar a autorização passando o usuário, senha e o tipo de sessão Stateless, com isso uma sessão é aberta para esse usuário. Ao obter uma resposta da requisição, o Siebel retornará o token de segurança que deverá ser utilizado na próxima requisição, que retornará outro token de segurança que deverá ser utilizado na próxima requisição e assim por diante. Para finalizar a sessão Siebel é só passar o usuário e senha com o tipo de sessão None.
Requisição inicial
Para efetuar o log in e criar a sessão é necessário passar o seguinte header:
[sourcecode language=”xml”]
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<UsernameToken xmlns="http://siebel.com/webservices">usuario</UsernameToken>
<PasswordText xmlns="http://siebel.com/webservices">senha</PasswordText>
<SessionType xmlns="http://siebel.com/webservices">Stateless</SessionType>
</soap:Header>
<soap:Body>
<!– … –>
</soap:Body>
</soap:Envelope>
[/sourcecode]
Dessa forma ele deverá retornar uma resposta parecida com essa:
[sourcecode language=”xml”]
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<siebel-header:SessionToken xmlns:siebel-header="http://siebel.com/webservices">TokenCriptografadoComValorEstranhoDoResponse
</siebel-header:SessionToken>
</soap:Header>
<soap:Body>
<!– … –>
</soap:Body>
</soap:Envelope>
[/sourcecode]
Próximas Requisições
Para as próximas requisições e necessário recuperar o token da resposta e colocar no header da requisição da seguinte forma:
[sourcecode language=”xml”]
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<SessionType xmlns="http://siebel.com/webservices">Stateless</SessionType>
<SessionToken xmlns="http://siebel.com/webservices">TokenCriptografadoComValorEstranhoDoResponse</SessionToken>
</soap:Header>
<soap:Body>
<!– … –>
</soap:Body>
</soap:Envelope>
[/sourcecode]
E assim por diante. Existe a opção do tipo de sessão ser Statefull também para manter o estado da sessão. O funcionamento é o mesmo, só muda o SessionType para Statefull.
Pool de conexões
Como assinei contrato de confidencialidade não poderei disponibilizar o código fonte utilizado no projeto, mas explicarei os caminhos necessários para tudo funcionar adequadamente.
Para a inclusão do header nas requisições do Siebel, utilizei um SOAPHandler. O SOAPHandler funciona como uma espécie de “filtro” para todas as requisições que utilize ele. Dessa forma eu posso modificar o header, alterar a requisição e obter ou mesmo alterar os dados da resposta. Para realizar isso, é só criar uma classe que implemente a interface SOAPHandler como no exemplo e associar com os clientes de cada serviço (JAX-WS 2.0).
[sourcecode language=”java”]
public class SiebelHandler implements SOAPHandler<SOAPMessageContext> {
//…
}
[/sourcecode]
Dos métodos que a interface nos obriga a implementar utilizaremos dois: handleMessage e handleFault. Que é onde iremos modificar os headers e obter os tokens.
[sourcecode language=”java” highlight=”5,7,8,22,26,31,38,45,57,65,74″]
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty =
(Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
SiebelTokenHelper tokenHelper =
SiebelSimpleTokenManager.getInstance().getNextToken();
tokenUsed(tokenHelper);
SOAPMessage message = smc.getMessage();
try {
SOAPEnvelope envelope =
smc.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
if (header == null) {
header = envelope.addHeader();
}
// Token
if (tokenHelper.getTokenValue() == null) {
SOAPElement userNameToken =
header.addChildElement("UsernameToken", "",
"http://siebel.com/webservices");
userNameToken.addTextNode(tokenHelper.getUser());
SOAPElement password =
header.addChildElement("PasswordText", "",
"http://siebel.com/webservices");
password.addTextNode(tokenHelper.getPassword());
} else {
SOAPElement sessionToken =
header.addChildElement("SessionToken", "",
"http://siebel.com/webservices");
sessionToken.addTextNode(tokenHelper.getTokenValue());
}
SOAPElement sessionType =
header.addChildElement("SessionType", "",
"http://siebel.com/webservices");
if (isLogout(envelope)) {
sessionType.addTextNode("None");
SiebelSimpleTokenManager.getInstance().removeFromPool(tokenHelper);
tokenHelper = null;
} else {
sessionType.addTextNode("Stateless");
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Erro ao invocar servico", e);
}
} else {
SiebelTokenHelper tokenHelper = getUsedToken();
try {
SOAPMessage message = smc.getMessage();
if (tokenHelper != null) {
Node tokenCHild = message.getSOAPHeader().getFirstChild();
if (tokenCHild != null) {
tokenHelper.setTokenValue(tokenCHild.getTextContent());
}
}
} catch (Exception ex) {
ex.printStackTrace();
tokenHelper.setTokenValue(null);
} finally {
if (tokenHelper != null) {
SiebelSimpleTokenManager.getInstance().pushElemIntoPool(tokenHelper);
}
}
}
return outboundProperty;
}
[/sourcecode]
A primeira condição na linha 05 verifica se é requisição ou reposta. Se for requisição o valor será verdadeiro. Na linha 07 e 08 utilizo uma implementação de fila (utilizando LinkedList) para recuperar o elemento mais antigo (menos recente usado), removo ele da fila para que outras conexões Siebel possam ser utilizadas, ele será novamente inserido no final da fila (linha 74), após ter o valor do token atualizado (linha 65) na resposta (linha 57). Na linha 22 verifico se existe token anterior ou se é uma conexão nova. Se for uma conexão nova, atribuo o valor do usuário (linha 26), da senha (linha 31). Caso contrário, utilizo o token (linha 38) atributo anteriomente na resposta (linha 65). Se for uma requisição de logout (linha 45), atribuo o SessionType como None, caso contrário Stateless.
O coração da implementação é esse método, mas ainda não estamos tratando exceção e reestabelecendo a conexão caso a sessão expire. Para reestabelecer a conexão, basta colocar no catch do cliente uma nova tentativa se o erro tiver o código 10944642. No nosso handleFault basta remover da fila para não ser mais utilizado, caso contrário, se for outro tipo de erro, o token é retornado no fault e devemos atribuir para ser utilizado na próxima requisição. Quando a sessão expira o Siebel retorna a seguinte mensagem:
[sourcecode language=”xml”]
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>Error Code: 10944642 Error Message: Error: Inbound SOAP Message – Session Token is missing or invalid or has expired</faultstring>
<detail>
<siebelf:errorstack xmlns:siebelf="http://www.siebel.com/ws/fault">
<siebelf:error>
<siebelf:errorsymbol/>
<siebelf:errormsg>Error: Inbound SOAP Message – Session Token is missing or invalid or has expired</siebelf:errormsg>
</siebelf:error>
</siebelf:errorstack>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
[/sourcecode]
Por isso devemos tratar quando obtermos o código: 10944642, pois não é um erro de serviço, é somente sessão expirada. O controle de criação de novas conexões foi feito em uma classe que possui a fila de conexões e tokens e o controle do número máximo foi feito utilizando um Managed Bean para contabilização de login e logout e um Session Listener, para caso o usuário esqueça de clicar no botão Sair.
Para testar a Oracle disponibiliza um VM do Siebel no e-delivery. Com essa implementação, tivemos um ganho de performance absurdo, paralelizamos as requisições e otimizamos o uso de sessões e compartilhamento de tokens no Siebel.