Restringir acesso a Web Service / Desktop Application

21 respostas
W

Estou desenvolvendo um sistema para que os representantes de venda da empresa onde faço estágio possam fazer os pedidos.
A aplicação está sendo feita com Java Web Start, ou seja, uma aplicação que roda na máquina do usuário.
O sistema precisa se conectar a uma base de dados única, via internet, já que os representantes estão espalhados por várias partes do país.

Pedindo por ajuda aqui no fórum e pesquisando na internet, vi que seria uma má ideia disponibilizar o banco de dados na internet, que o correto seria utilizar um web service para servir como intermediário na comunicação do banco de dados com a aplicação. Já que o web service rodaria no mesmo “local” que o banco de dados, eu não precisaria disponibilizar o banco de dados na internet.

Como não tenho experiência com web service, resolvi seguir um tutorial na internet, para entender o funcionamento do web service.
A aplicação de teste que fiz, funcionou normalmente, mas analisando a situação, percebi que há um problema grave:
QUALQUER UM que saiba a URL do meu Web Service pode executar os “métodos” do web service e receber/enviar informações ao banco de dados.

Se eu criar um método no web service para fazer um SELECT Nome FROM CLIENTES, por exemplo, qualquer um que saiba a URL do web service pode criar uma aplicação-cliente para que esse método seja usado e TCHARAM!, tem acesso ao nome de todos os clientes.

Visto que o sistema só será utilizado por pessoas previamente cadastradas, e que portanto não há o interesse de o Web Service ser algo público, há como eu restringir o acesso ao web service apenas para os usuários desejados?

Estou confuso.

21 Respostas

L

Vc pode usar autenticação no Web Service, usando a mesma senha dos usuários dos usuários cadastrados.

Qual servidos está usando para prover os serviços?(JBoss, Glassfishm, Spring WS, CXF, etc)

K

Cara, sou iniciante então minha sugestão não é lá essas coisas, mas uma coisa que você poderia fazer era obrigar o cliente do webService a, quando fizesse uma requisição, passar um token de autenticação. Aí, dentro do método do webservice, vc validaria o token. Essa é uma idéia básica. Tem outras formas muito mais avançadas e seguras para se fazer isso mas entendo pouco. Uma boa dica (pra quem é iniciante no uso de webServices) é o livro da casa do código de SOA. Me ajudou muito aqui no trabalho. Tem a questão de segurança lá tambem mas infelizmente ainda não cheguei lá. Chegando pode ter certeza que eu te passo mais informações.
Abraços.

L

Foi o que perguntei, tem a especificação WS-Security com algumas possibilidades, sendo que a se encaixa melhor no contexto é a de autenticação usando usuário e senha.

Porém cada um ‘trata da forma como quer’, tipo o JBoss usa tais arquivos, Spring outros, etc.

Se não me engano tenho exemplo usando JBoss e Spring com autenticação via usuário e senha.

W

lsjunior:
Vc pode usar autenticação no Web Service, usando a mesma senha dos usuários dos usuários cadastrados.

Qual servidos está usando para prover os serviços?(JBoss, Glassfishm, Spring WS, CXF, etc)

Glass Fish.

L

Exatamente o que não mexo…

Bom vc vai precisar fazer a autenticação via UsernameToken, uma consulta no google usando ‘glassfish 3 ws-security usernametoken’ retornou essa página:
http://www.oio.de/public/xml/usernametoken-tutorial-webservice-security-artikel.htm
Da pra se basear nele(tem que pedir para traduzir ao clicar no link).

Acho que na documentação do JavaEE(tutorial) ou no site do Netbeans possa achar algo.

W

kbello:
Cara, sou iniciante então minha sugestão não é lá essas coisas, mas uma coisa que você poderia fazer era obrigar o cliente do webService a, quando fizesse uma requisição, passar um token de autenticação. Aí, dentro do método do webservice, vc validaria o token. Essa é uma idéia básica. Tem outras formas muito mais avançadas e seguras para se fazer isso mas entendo pouco. Uma boa dica (pra quem é iniciante no uso de webServices) é o livro da casa do código de SOA. Me ajudou muito aqui no trabalho. Tem a questão de segurança lá tambem mas infelizmente ainda não cheguei lá. Chegando pode ter certeza que eu te passo mais informações.
Abraços.

Esse “token” que você diz seria um parâmetro passado?

Exemplo:

Na minha aplicação, cliente do meu web service eu faria algo como:
service.getNomes(“123”);

E no web service:

public String getNomes(@WebParam(name = "chave") String chave) { if ("123".equals(chave)) { //código aqui } else { return null; } }

Foi algo assim que você quis dizer?

Se for isso, de fato, isso geraria uma maior segurança do que minha atual situação (na qual há 0 de segurança), mas pelo que eu li a respeito, é possível descompilar o programa. Se alguém descompilasse a minha aplicação, ele teria o valor passado como parâmetro.

W

lsjunior:
Exatamente o que não mexo…

Bom vc vai precisar fazer a autenticação via UsernameToken, uma consulta no google usando ‘glassfish 3 ws-security usernametoken’ retornou essa página:
http://www.oio.de/public/xml/usernametoken-tutorial-webservice-security-artikel.htm
Da pra se basear nele(tem que pedir para traduzir ao clicar no link).

Acho que na documentação do JavaEE(tutorial) ou no site do Netbeans possa achar algo.

Opa, muito obrigado, cara!
Vou dar uma olhada.

K

wellington_r:
kbello:
Cara, sou iniciante então minha sugestão não é lá essas coisas, mas uma coisa que você poderia fazer era obrigar o cliente do webService a, quando fizesse uma requisição, passar um token de autenticação. Aí, dentro do método do webservice, vc validaria o token. Essa é uma idéia básica. Tem outras formas muito mais avançadas e seguras para se fazer isso mas entendo pouco. Uma boa dica (pra quem é iniciante no uso de webServices) é o livro da casa do código de SOA. Me ajudou muito aqui no trabalho. Tem a questão de segurança lá tambem mas infelizmente ainda não cheguei lá. Chegando pode ter certeza que eu te passo mais informações.
Abraços.

Esse “token” que você diz seria um parâmetro passado?

Exemplo:

Na minha aplicação, cliente do meu web service eu faria algo como:
service.getNomes(“123”);

E no web service:

public String getNomes(@WebParam(name = "chave") String chave) { if ("123".equals(chave)) { //código aqui } else { return null; } }

Foi algo assim que você quis dizer?

Se for isso, de fato, isso geraria uma maior segurança do que minha atual situação (na qual há 0 de segurança), mas pelo que eu li a respeito, é possível descompilar o programa. Se alguém descompilasse a minha aplicação, ele teria o valor passado como parâmetro.

Sério isso? Não sabia disso não… então o jeito q eu falei é melhor deixar de lado…

L

A validação fica por conta do servidor do WS.

O que ocorre é que quando ativa a segurança, o cliente cria um elemento contendo os dados da segurança. Esses dados podem ser o tal usernametoken, criptografia, assinatura digital, etc. Do lado do servidor será verificado se essas credenciais são válidas. Dessa forma vc poderia, na chamada do cliente, aidicionar a informação da autenticação.

http://en.wikipedia.org/wiki/WS-Security

A usernametoken por si não garante tanta segurança, pois o XML seria enviado em texto plano. Colocando o Web Service rodando em HTTPS evitaria a captura da informação.

S

wellington_r:
Estou desenvolvendo um sistema para que os representantes de venda da empresa onde faço estágio possam fazer os pedidos.
A aplicação está sendo feita com Java Web Start, ou seja, uma aplicação que roda na máquina do usuário.
O sistema precisa se conectar a uma base de dados única, via internet, já que os representantes estão espalhados por várias partes do país.

Pedindo por ajuda aqui no fórum e pesquisando na internet, vi que seria uma má ideia disponibilizar o banco de dados na internet, que o correto seria utilizar um web service para servir como intermediário na comunicação do banco de dados com a aplicação. Já que o web service rodaria no mesmo “local” que o banco de dados, eu não precisaria disponibilizar o banco de dados na internet.

Como não tenho experiência com web service, resolvi seguir um tutorial na internet, para entender o funcionamento do web service.
A aplicação de teste que fiz, funcionou normalmente, mas analisando a situação, percebi que há um problema grave:
QUALQUER UM que saiba a URL do meu Web Service pode executar os “métodos” do web service e receber/enviar informações ao banco de dados.

Se eu criar um método no web service para fazer um SELECT Nome FROM CLIENTES, por exemplo, qualquer um que saiba a URL do web service pode criar uma aplicação-cliente para que esse método seja usado e TCHARAM!, tem acesso ao nome de todos os clientes.

Visto que o sistema só será utilizado por pessoas previamente cadastradas, e que portanto não há o interesse de o Web Service ser algo público, há como eu restringir o acesso ao web service apenas para os usuários desejados?

Estou confuso.

Depois de vários posts explicando como usar webservices, acho que no seu caso é melhor não usar webservices. Não os tradicionais, pelo menos.

O webservice nada mais é que uma aplicação web especial, mas no seu caso ela precisa ser ainda mais especial, porque é na realidade uma parte interna de um sistema e não um serviço publico.
A autenticação não é do usuário e sim da aplicação cliente como um todo.

Bastaria que vc encriptasse com um esquema de chave publica-privada. Assim o cliente tem a chave para encriptar, mas não tem como desincriptar, isso apenas o server sabe.
Como vc usa jws vc pode usar o jnlp para ser gerado dinamicamente por um jsp ou um servlet e gerar na hora uma chave publica-privada. A chave privada o servidor guarda e a publica ele passa no jnlp.
O cliente usa essa chave para encriptar as chamadas e o servidor para desencriptar. As chamadas em si podem ser simples classes no padrão command que são serilizadas, ou apenas os parametros e o nome do método num esquema de lista simples. O que interessa é que o servidor saiba interpretar isso do outro lado depois de desencriptar. Este processo não necessita de intervenção do usuário e funciona como um todo para o sistema que irá usar este mecanismo mesmo antes do usuário se logar.
Se quiser ainda melhor use HTTPS em vez de HTTP. Assim existe uma camada a mais e segurança na própria transmissão.

Agora um detalhes, seu serviço nunca jamais deve receber sql. Ele deve receber os parâmetros de uma função, é o servidor que vai interpretar o nome da função e chamar algum código que no fim poderá ou não usar um SQL. Isto tb é uma outra forma de dar mais segurança.

E

Outra coisa simples que você poderia fazer seria gerar um hash com o próprio usuário e senha do seu sistema e enviar como um token para o web service e fazer algo como:

public void metodoPublicadoWS(byte[] token, ...) { checkToken(token); ... //faz o que vc tiver que fazer }

Sendo que este checkToken() seria um método seu que validaria o token informado pelo usuário. Caso não valide, lança uma exception (que você irá tratar como quiser)…

Tenho aplicações com o contexto bem parecido com o seu aqui e uso esta forma para garantir a segurança. Uma vantagem é estar por dentro do processo de autenticação (pois foi criado por mim mesmo) e ter a liberdade de a qualquer momento mudar o algoritmo de criação do hash…

S

erico_kl:
Outra coisa simples que você poderia fazer seria gerar um hash com o próprio usuário e senha do seu sistema e enviar como um token para o web service e fazer algo como:

public void metodoPublicadoWS(byte[] token, ...) { checkToken(token); ... //faz o que vc tiver que fazer }

Sendo que este checkToken() seria um método seu que validaria o token informado pelo usuário. Caso não valide, lança uma exception (que você irá tratar como quiser)…

Tenho aplicações com o contexto bem parecido com o seu aqui e uso esta forma para garantir a segurança. Uma vantagem é estar por dentro do processo de autenticação (pois foi criado por mim mesmo) e ter a liberdade de a qualquer momento mudar o algoritmo de criação do hash…

E como o servidor sabe qual é o usuário ?

E

No primeiro acesso ao servidor o cliente envia o login e a senha, então o servidor verifica a existência do mesmo no banco. Se existe, gera o hash, guarda num Map e retorna para o cliente, que estará participando da “sessão” e enviará este token em cada requisição. Basicamente nas futuras requisições o servidor somente percorre o map esperando encontrar lá o token gerado que o cliente enviou. Se encontrar, realiza a ação, senão nada acontece e uma RemoteException é lançada para o cliente

S

Esse seu hash é nada mais que um sessionId. O servidor já vai mandar um sessioId do container, então não ha muita diferença (excepto que o servidor manda dentro de um cookie)

Pesquise por Session Hijacking

Com esse modelo um adversário pode pegar seu user e pass e mais tarde usá-los para simular que é vc e usar os mesmos serviços para outros fins. Estas informações deveria está criptografas de alguma forma na primeira comunicação. No minimo a primeira comunicação tem que ser HTTPS. Mesmo depois disso, o adversário pode usar o mesmo hash em outra sessão, o que tb não é bom.

Vc substituiu o envio de dois parametros (nome e senha) pelo envio de um. Mas não aumentou a segurança da aplicação e seria equivalente a enviar o user e senha a cada request.

E

Mas o usuário e senha não são enviados hardcoded… O algoritmo além de gerar o hash, cria um MD5 do token que é enviado (lembrando que o algoritmo do hash é um código próprio)

O cara só vai ter acesso ao web service se ele tiver um usuário/senha da aplicação…

S

Não entendi o que quer dizer com isso. eles são encriptados para envio ?


O cara só vai ter acesso ao web service se ele tiver um usuário/senha da aplicação…

Isso é simples em sistema web. Basta colocar algum sniffer na rede. Porque o pessoa manda os forms com http (plain text) fica fácil de saber. Apenas o HTTPS garante que ha encriptação.
É por isso, que o Google sempre pede sua senha por HTTPS (se ainda nunca reparou) e todos os sites deveriam fazer isso. Senão, não ha realmente segurança alguma. Qualquer pessoa pode pegar um par de usuário-senha. Tudo bem que não é trivial, mas convenhamos que do ponto de vista da segurança da aplicação é furada.

E

Na verdade é um byte[] md5 do hash do usuário e senha da aplicação


Isso é simples em sistema web. Basta colocar algum sniffer na rede. Porque o pessoa manda os forms com http (plain text) fica fácil de saber. Apenas o HTTPS garante que ha encriptação.
É por isso, que o Google sempre pede sua senha por HTTPS (se ainda nunca reparou) e todos os sites deveriam fazer isso. Senão, não ha realmente segurança alguma. Qualquer pessoa pode pegar um par de usuário-senha. Tudo bem que não é trivial, mas convenhamos que do ponto de vista da segurança da aplicação é furada.

Não é a forma mais segura que existe, mas dependendo do porte da aplicação e contexto do problema é bem viável… Apenas citei por ser uma solução simples que resolveria o problema de qualquer pessoa poder acessar o web service e consumir os métodos sem restrição nenhuma…

W

Tentei implementar o projeto do link que o lsjunior passou (http://www.oio.de/public/xml/usernametoken-tutorial-webservice-security-artikel.htm), para entender como funciona o UsernameToken.

Acho que fiz certo, mas não sei por que o programa está imprimindo o que não era esperado por mim.
Depois de fazer a entrada do usuário e senha, recebo:

[color=red]Fev 20, 2013 8:19:38 AM [com.sun.xml.ws.policy.jaxws.PolicyConfigParser] parse INFO: WSP5018: Loaded WSIT configuration from file: file:/C:/Users/Wellington/Downloads/usernametoken-tutorial-beispiele/UsernameTokenServiceSOClient/build/classes/META-INF/wsit-client.xml.[/color] ---[HTTP request - http://localhost:8080/UsernameTokenService/UsernameTokenServiceService]--- Content-type: text/xml;charset=utf-8 Soapaction: "http://server/UsernameTokenService/sayHelloRequest" Accept: text/xml, multipart/related http://localhost:8080/UsernameTokenService/UsernameTokenServiceServicehttp://server/UsernameTokenService/sayHelloRequest
http://www.w3.org/2005/08/addressing/anonymous
(...) +4RSjqP2NBeSuTwB+YTnlRwSswU=bVNjqtFFp/ft+Ot1rCe1Hcn9Bx4pWPkYuWQwuzoDawjpLV2G+t2Iu+jMcqgrFP1u/bdx2AtNUX4WD/RN0U0+66yelG/r3BLDowPpKgluarAQBwhYPw1h4wt5X2xCcq+Xzq0mwKkrA5bHh8Dl/+2bMDpH3StPqDiBGxH4DSDiLMI=
-------------------- Result = Hello Test

(A primeira parte da saída é em vermelho, mesmo)

O único resultado deveria ser "Result = Hello Test" (indica que a autenticação foi feita com sucesso).
Não consigo descobrir de onde vem o restante da saída.

Minha classe Main:

public class Main {

    public static void main(String[] args) {
        try {
            System.out.println("Digite o nome do usuário: ");
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            UserPasswordInfo.setUsername(reader.readLine());
            System.out.println("Digite a senha: ");
            UserPasswordInfo.setPassword(reader.readLine());
            reader.close();

            System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
            server.UsernameTokenServiceService service = new server.UsernameTokenServiceService();
            server.UsernameTokenService port = service.getUsernameTokenServicePort();
            BindingProvider prov = (BindingProvider) port;
            List<Handler> handlerChain = prov.getBinding().getHandlerChain();
            handlerChain.add(new UsernameTokenHandler());
            prov.getBinding().setHandlerChain(handlerChain);
            prov.getRequestContext().put(prov.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:8080/UsernameTokenService/UsernameTokenServiceService");
            java.lang.String name = "Test";
            java.lang.String result = port.sayHello(name);
            System.out.println("Result = " + result);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
W

Consegui resolver o problema.

Segui o seguinte tutorial:
http://docs.oracle.com/cd/E17802_01/webservices/webservices/reference/tutorials/wsit/doc/WSIT_Security9.html#wp162458

As configurações do web service, no servidor:

Serviço seguro: habilitado
Localização de keystore: keystore.jks, no diretório do Glassfish.
Demais configurações: padrão ou como especificado no tutorial.

As configurações do cliente do web service na aplicação-cliente:
Credenciais de autenticação: dinâmico
Handler de callback de nome de usuário: security.PWCallback (pacote security, classe PWCallback)
Handler de callback de senha: security.PWCallback
Localização de truststore: cacerts.jks (o arquivo deve ser colocado em PastadoProjeto>build>classes>META-INF, manualmente)
Demais configurações: padrão

Código da classe PWCallback:

package security;

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;


public class PWCallback implements CallbackHandler {

    //thread specific username, initialized to null; 
    private static ThreadLocal username = new ThreadLocal() {
        @Override
        protected synchronized Object initialValue() {
            return null;
        }
    };
    //thread specific password, initialized to null; 
    private static ThreadLocal password = new ThreadLocal() {
        @Override
        protected synchronized Object initialValue() {
            return null;
        }
    };
    
    public static void setThreadIdentity(String user, String passw) {
        password.set(user);
        username.set(passw);
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof NameCallback) {
                NameCallback nc = (NameCallback) callbacks[i];
                String user = (String) username.get();
                nc.setName(user);
                username.set(null);
                
            } else if (callbacks[i] instanceof PasswordCallback) {
                PasswordCallback pc = (PasswordCallback) callbacks[i];
                String passCode = (String) password.get();
                pc.setPassword(passCode.toCharArray());
                //also, after each use revert to using default 
                password.set(null);
            }
        }
    }
    
}
Código da classe Main:
package client;

import java.util.Scanner;
import security.PWCallback;
import security.Pref;

public class Main {

    public static void main(String[] args) {
        try {
            Scanner scan = new Scanner(System.in);
            String id = scan.nextLine();
            
            PWCallback.setThreadIdentity("123", "usuario");

            server.CalculatorWS_Service service = new server.CalculatorWS_Service();
            server.CalculatorWS port = service.getCalculatorWSPort();

            java.lang.String resultado = port.hello(id);
            System.out.println("Result = " + resultado);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

O único problema que ocorreu, foi que como se pode ver na linha

PWCallback.setThreadIdentity("123", "usuario");

da classe Main, eu tive que inverter os argumentos, para que o código funcionasse. O esperado era setThreadIdentity(String user, String passw), mas quando passei os argumentos na ordem certa, estava dando exception, pois a senha era tida como o nome de usuário. Alguém sabe por que isso ocorre?

---

Além disso, o único "empecilho" é que implementando o web service dessa maneira, ao criar um usuário novo para o sistema, terei que criar um usuário com mesmo login e senha no glassfish. Mas isso não é um problema tão grande, já que a empresa tem apenas 15 representantes de venda, e eles raramente mudam.

PS: para implementar a classe PWCallBack e entender como utilizá-la utilizei o seguinte link:
http://netbeans-org.1045718.n5.nabble.com/How-to-do-dynamic-authentication-with-default-callback-handler-td3274173.html

A

Sabe que, no livro que eu escreví, tem uma explicação de mais ou menos esse mesmo assunto? O empecilho é que os códigos do livro são mais baseados em JBoss 7, mas deve te dar uma luz do que fazer no server-side.

Caso interesse, o código está no github: https://github.com/alesaudate/soa (cap. 6).

[]'s

W

Opa, valeu, cara!

Vou dar uma olhada.

Criado 14 de fevereiro de 2013
Ultima resposta 22 de fev. de 2013
Respostas 21
Participantes 6