Hoje o tópico será sobre Aplicações Web.
1. Introdução (Segurança)
Vamos falar hoje sobre um dos campos mais hostis e inóspitos que existe no ramo da programação: O ambiente Web. Um ambiente fértil para novas aplicações e que representa cada vez mais o futuro do desenvolvimento de softwares, mas ao mesmo tempo traz uma série de dificuldades.Adicionei um novo Gadget ao meu blog para demonstrar como a Web pode ser um ambiente terrível e perigoso. O Gadget mostra em tempo real o fluxo de ataques DDOS realizados ao redor do mundo. Lembrando a definição de ataque DDOS:
"A Distributed Denial of Service (DDoS) attack is an attempt to make an online service unavailable by overwhelming it with traffic from multiple sources. They target a wide variety of important resources, from banks to news websites, and present a major challenge to making sure people can publish and access important information".
Se você, leitor, se interessou pelo tema, aconselho que aprofunde um pouco mais sua visão com algum dos links da referência deste post. Sei que minha abordagem apreensiva quanto à segurança é um pouco inusitada, mas é ela que explica fatos como, por exemplo:
- A implementação de Applets em Java ser tão difícil e pouco estimulada devido aos perigos que uma máquina virtual (proveniente da execução do aplicativo) pode ocasionar ao seu utilizador.
- Não há possibilidade de se utilizar diretamente serviços Sockets (explicaremos neste post) e envio de Packets empregando Javascript, o que poderia ser um desastre de segurança.
- A enorme dificuldade de se hospedar arquivos online de forma gratuita, principalmente de aplicações que não sejam em HTML (um arquivo executável .jar, por exemplo), devido às aplicações maliciosas que viriam daí.
E estes são alguns poucos itens que enuncio aqui, dificuldades sérias para o nosso projeto Web que se originam fundamentalmente de problemas de segurança em rede.
2. Predefinição
A linguagem Java conta com uma boa API de Sockets pelo pacote java.net, que será o mais utilizado aqui neste post. Antes, vamos tomar algumas definições básicas:
Glossário
- Socket (soquete de rede) é o ponto final do fluxo de informação entre 2 aplicativos através de uma rede.
- Packet (pacote) é uma estrutura unitária de transmissão de dados em uma rede de comunicação.
- Protocolo (TCP - Transmission Control Protocol) é uma convenção que controla e possibilita uma conexão/ comunicação/ transferência de dados entre dois sistemas computacionais.
- IP (IP - Internet Protocol) pode ser definido como um protocolo de comunicação em conjunto com o TCP (TCP/IP).
- Endereço IP é uma identificação de um dispositivo em uma rede local ou pública. Cada endereço de domínio é convertido em um endereço IP pelo DNS (resolução de nomes).
- Porta, assim como o IP existe para identificar uma máquina, a porta é solução para identificar as diversas aplicações de uma máquina. É um número de 2 bytes (varia de 0 a 65535)
- DNS é um sistema de gerenciamento de nomes hierárquicos distribuídos para computadores, serviços ou qualquer recurso conectado à internet ou a uma rede privada.
- IPV4 é um cabeçalho que contém as informações a serem transmitidas segundo uma certa lógica. Seu endereçamento possui 32 bits.
- UDP é um protocolo simples da camada de transporte. Permite que a aplicação escreva um datagrama encapsulado num pacote IPV4 ou IPV6 e então envia ao destino, mas não garante a chegada ao destino ou não.
- Datagrama é uma unidade de transferência básica associada a uma rede de comutação de pacotes em que a entrega, hora de chegada e ordem não são garantidas.
- Domínio é um nome que serve para localizar um conjunto de computadores na internet.
- Servidor é uma aplicação central que espera a conexão de Clientes para a sua execução.
CIDR Bloco de Endereços | Descrição | Referência |
---|---|---|
0.0.0.0/8 | Rede corrente (só funciona como endereço de origem) | RFC 1700 |
10.0.0.0/8 | Rede Privada | RFC 1918 |
14.0.0.0/8 | Rede Pública | RFC 1700 |
39.0.0.0/8 | Reservado | RFC 1797 |
127.0.0.0/8 | Localhost | RFC 3330 |
128.0.0.0/16 | Reservado (IANA) | RFC 3330 |
169.254.0.0/16 | Zeroconf | RFC 3927 |
172.16.0.0/12 | Rede privada | RFC 1918 |
191.255.0.0/16 | Reservado (IANA) | RFC 3330 |
192.0.2.0/24 | Documentação | RFC 3330 |
192.88.99.0/24 | IPv6 para IPv4 | RFC 3068 |
192.168.0.0/16 | Rede Privada | RFC 1918 |
198.18.0.0/15 | Teste de benchmark de redes | RFC 2544 |
223.255.255.0/24 | Reservado | RFC 3330 |
224.0.0.0/4 | Multicasts (antiga rede Classe D) | RFC 3171 |
240.0.0.0/4 | Reservado (antiga rede Classe E) | RFC 1700 |
255.255.255.255 | Broadcast |
3. Sockets (Java)
Vamos esquentar um pouco com um pequeno exemplo de aplicação Cliente - Servidor em Java, em que cliente e servidor trocam uma simples mensagem entre si:
A aplicação Servidor disponibiliza uma determinada porta e espera a conexão do Cliente, para depois realizar a leitura dos dados enviados por ele, para depois enviar uma curta mensagem. Note que há a criação de um Socket de conexão:
ServerSocket servidor = new ServerSocket( porta );
Server.java
package sockets; import java.io.*; import java.net.*; import java.util.*; public class Server { static String Mensagem = "Hi", N; static String IPclient, IPserver, porta; static Socket cliente; public static void main(String[] args) throws UnknownHostException, IOException { // Campo para exibir IPserver e porta IPserver = InetAddress.getLocalHost().getHostAddress(); porta = "7777"; ServerSocket servidor = new ServerSocket(Integer.parseInt(porta)); // Detecção de nova requisição do cliente. while (cliente == null) { cliente = servidor.accept(); IPclient = cliente.getLocalAddress().getHostAddress(); } // Leitura do valor de N Scanner s = new Scanner(cliente.getInputStream()); N = s.nextLine(); System.out.println(N); // Escreve a mensagem codificada. PrintStream saida = new PrintStream(cliente.getOutputStream()); saida.println(Mensagem); while(!cliente.isClosed()) saida.close(); cliente.close(); } }
Para a aplicação Cliente, temos a conexão ao servidor e depois o subsequente envio envio de uma mensagem, para depois aguardar a mensagem enviada pelo cliente. Note que a conexão Socket é realizada no formato (IP, porta), conforme a sua definição:
Socket cliente = new Socket( IP, porta );
Client.java
package sockets; import java.io.*; import java.net.*; import java.util.*; public class Client { static String Mensagem, N = "10"; static String IPclient, IPserver, porta; static Socket cliente; public static void main(String[] args) throws UnknownHostException, IOException { // Campo para digitar IPserver & Porta & exibir IPcliente IPserver = "192.168.197.1"; porta = "7777"; IPclient = InetAddress.getLocalHost().getHostAddress(); // Conexão ao servidor cliente = new Socket(IPserver, Integer.parseInt(porta)); // Cliente envia o valor de "n" PrintStream saida = new PrintStream(cliente.getOutputStream()); saida.println(N); // Realiza a leitura da mensagem Scanner s = new Scanner(cliente.getInputStream()); Mensagem = s.nextLine(); System.out.println(Mensagem); saida.close(); cliente.close(); } }
Estes desafios podem ser resolvidos usando os conceitos que viemos desenvolvendo até agora no blog, de modo que deixaremos isso ao encargo do leitor.
4. WebSockets (Javascript)
Note que para usar WebSockets, será necessário criar um arquivo HTML estático como estrutura para o código dinâmico do Javascript. Fundamentalmente, Javascript não permite a utilização de Sockets diretamente como ocorre na linguagem Java, por motivos de segurança.
No entanto, WebSockets surge como uma alternativa moderna para contornar esse problema, usando funções encapsuladas de forma a restringir o uso de Sockets e a sua implementação.
Abaixo temos um exemplo de aplicação de WebSockets:
<!DOCTYPE HTML> <html> <head> <script type="text/javascript"> function WebSocketTest() { if ("WebSocket" in window) { alert("WebSocket is supported by your Browser!"); // Let us open a web socket var ws = new WebSocket("ws://localhost:9998/echo"); ws.onopen = function() { // Web Socket is connected, send data using send() ws.send("Message to send"); alert("Message is sent..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert("Message is received..."); }; ws.onclose = function() { // websocket is closed. alert("Connection is closed..."); }; } else { // The browser doesn't support WebSocket alert("WebSocket NOT supported by your Browser!"); } } </script> </head> <body> <div id="sse"> <a href="javascript:WebSocketTest()">Run WebSocket</a> </div> </body> </html>
Por outro lado, devemos empregar um Servidor, como por exemplo o Glassfish, para a criação de um terminal Servidor, com um endereço do tipo:
"ws://localhost:8080/Bizuca/server"
package Servidor; import java.io.IOException; import java.util.ArrayList; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/server") public class Servidor { ArrayList<Player> Players = new ArrayList<>(); public static int i = 0; public static String Msg = ""; @OnOpen public void onOpen(Session session) { Players.add(new Player("null",session.getId())); try { session.getBasicRemote().sendText("Conexão estabelecida. Nº"+(i++)); } catch (IOException ex) { } } @OnMessage public void onMessage(String message, Session session) { Msg = message; message+=": Sender : ("+session.getId()+")"; try { session.getBasicRemote().sendText(message); } catch (IOException ex) { } } @OnClose public void onClose(Session session){ for(Player P: Players) if (P.Id.equals(session.getId())) Players.remove(P); } }
É possível inclusive colocar essa aplicação online com uso de servidor Glassfish, empregando por exemplo o serviço Layershift (versão trial de 14 dias), com permissão ao uso de banco de dados online e execução da sua aplicação em um ambiente online.
Ambiente Layershift para Upload de projetos.
Porém sempre existem alternativas, como o emprego da API Sockets.io, que permite o uso de Sockets de maneira simplificada, ou da API jsocket, desenvolvida no MIT, que emprega arquivos Flash para burlar o sistema de segurança agressivo de Javascript.
5. Programando em baixo nível
Somente para ilustrar um pouco mais os conceitos abordados neste tópico, temos o seguinte exemplo de aplicação de Datagramas (a unidade básica de transferência de dados) em um chat, permitindo vislumbrar em baixo nível como é realizada a troca de informações na Web:
A classe Conexão realiza a conexão dos usuários e também se encarrega da interpretação e da troca de mensagens, convertendo Datagrams em mensagens e vice-e-versa:
package online; import java.io.IOException; import java.util.logging.*; import java.net.*; import java.util.*; public class Conexao extends Observable { private String ip, mensagem; private int porta; public void SetConexao(String ip, int porta) { this.ip = ip; this.porta = porta; new Thread(new Recebe()).start(); } public String getIP () { try { return ""+InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException ex) { Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex); } return ""; } public String getMensagem() { return mensagem; } public String getIp() { return ip; } public int getPorta() { return porta; } public void envia(String texto) { new Thread(new Envia(texto)).start(); } public void notifica(String mensagem) { this.mensagem = mensagem; setChanged(); notifyObservers(); } class Recebe implements Runnable { byte[] dadosReceber = new byte[255]; boolean erro = false; DatagramSocket socket = null; @Override public void run() { while (true) { try { socket = new DatagramSocket(getPorta()); } catch (SocketException ex) { Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex); } erro = false; while (!erro) { DatagramPacket pacoteRecebido = new DatagramPacket(dadosReceber, dadosReceber.length); try { socket.receive(pacoteRecebido); byte[] b = pacoteRecebido.getData(); String s = ""; for (int i = 0; i < b.length; i++) if (b[i] != 0) s += (char) b[i]; String nome = pacoteRecebido.getAddress().toString() + " disse:"; notifica(nome + s); } catch (Exception e) { try { Thread.sleep(100); } catch (InterruptedException ex) { Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex); } erro = true; continue; } } } } } class Envia implements Runnable { String texto; public Envia(String texto) { this.texto = texto; } @Override public void run() { byte[] dados = texto.getBytes(); try { DatagramSocket clientSocket = new DatagramSocket(); InetAddress addr = InetAddress.getByName(getIp()); DatagramPacket pacote = new DatagramPacket(dados, dados.length, addr, getPorta()); clientSocket.send(pacote); clientSocket.close(); } catch (SocketException ex) { Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex); } catch (UnknownHostException ex) { Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(Conexao.class.getName()).log(Level.SEVERE, null, ex); } } } }
Interface gráfica, responsável por capturar as mensagens e exibi-las, além de gerenciar as ferramentas usadas pelo usuário:
package online; import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.Observable; import java.util.Observer; public class GUI extends JFrame implements Observer { int xMouse, yMouse; private Conexao conex; private JTextField textoIP, textoPorta; private JTextArea chatjTextArea, mensagemjTextArea; private JButton enviarjButton, connect; private JScrollPane jScrollPane1, jScrollPane2; private JLabel credito, IP, porta, meuIP; public GUI (Conexao conexao) { conex = conexao; conex.addObserver(this); initComponents(); initEvents(); setBounds(250,150,600,550); escreve("Chat iniciado com " + conex.getIp() + ":" + conex.getPorta()); meuIP.setText("Meu IP: "+conex.getIP()); } private void initComponents() { setTitle("Xhat"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getContentPane().setBackground(new Color(0, 0, 0)); setLocationRelativeTo(null); setLayout(null); setVisible(true); IP = new JLabel("IP:"); IP.setForeground(Color.white); IP.setBounds(50,50,100,20); add(IP); porta = new JLabel("Porta:"); porta.setForeground(Color.white); porta.setBounds(200,50,100,20); add(porta); meuIP = new JLabel(); meuIP.setForeground(Color.white); meuIP.setBounds(200,20,150,20); add(meuIP); textoIP = new JTextField("192.168.0."); textoIP.setBounds(70,50,100,25); textoIP.setBackground(Color.black); textoIP.setForeground(Color.white); add(textoIP); textoPorta = new JTextField("5000"); textoPorta.setBounds(250,50,100,25); textoPorta.setBackground(Color.black); textoPorta.setForeground(Color.white); add(textoPorta); chatjTextArea = new JTextArea(); chatjTextArea.setEditable(false); chatjTextArea.setForeground(Color.white); chatjTextArea.setBackground(Color.black); jScrollPane1 = new JScrollPane(); jScrollPane1.setBounds(50,100,450,300); add(jScrollPane1); jScrollPane1.setViewportView(chatjTextArea); mensagemjTextArea = new JTextArea(); mensagemjTextArea.requestFocusInWindow(); mensagemjTextArea.setForeground(Color.white); mensagemjTextArea.setBackground(Color.black); jScrollPane2 = new JScrollPane(); jScrollPane2.setBounds(50,400,350,50); add(jScrollPane2); jScrollPane2.setViewportView(mensagemjTextArea); connect = new JButton("Conectar"); connect.setBackground(Color.black); connect.setForeground(Color.white); connect.setBounds(400,50,100,30); add(connect); enviarjButton = new JButton("Enviar"); enviarjButton.setBackground(Color.black); enviarjButton.setForeground(Color.white); enviarjButton.setBounds(400,400,100,50); add(enviarjButton); credito = new JLabel("By Tuyama"); credito.setForeground(Color.white); credito.setBounds(250,475,100,20); add(credito); pack(); } private void initEvents() { mensagemjTextArea.addKeyListener(new java.awt.event.KeyAdapter() { public void keyReleased(java.awt.event.KeyEvent evt) { if (evt.getKeyCode() == KeyEvent.VK_ENTER) envia(); } }); enviarjButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { envia(); } }); connect.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { conex.SetConexao(textoIP.getText(),Integer.parseInt(textoPorta.getText())); chatjTextArea.setText(""); escreve("Chat iniciado com " + conex.getIp() + ":" + conex.getPorta()); } }); addMouseMotionListener(new MouseMotionListener() { public void mouseDragged(MouseEvent e) { } public void mouseMoved(MouseEvent e) { xMouse = e.getX(); yMouse = e.getY(); } }); } private void envia(){ if (mensagemjTextArea.getText().equals("\n")) conex.envia("&"+xMouse+"@"+yMouse); if (!mensagemjTextArea.getText().isEmpty()) { conex.envia(mensagemjTextArea.getText()); escreve("Você disse: "+mensagemjTextArea.getText()); mensagemjTextArea.setText(""); } } private void escreve(String texto){ chatjTextArea.append(texto+"\n"); if (!chatjTextArea.getText().isEmpty() && !chatjTextArea.isFocusOwner()) chatjTextArea.setCaretPosition(chatjTextArea.getText().length() - 1); } @Override public void update(Observable o, Object arg) { String Msg = conex.getMensagem(); int MouseX, MouseY, S1, S2; if (!Msg.contains("&")&&!Msg.contains("@")) escreve(Msg); else { for (S1=0; Msg.charAt(S1)!='&'; S1++); for (S2=0; Msg.charAt(S2)!='@'; S2++); MouseX = Integer.parseInt(Msg.substring(S1+1, S2)); MouseY = Integer.parseInt(Msg.substring(S2+1)); credito.setBounds(MouseX,MouseY,100,20); } } }
Observe que a estrutura deste programa usa um conceito de Design Patterns (Observable & Observer) permitindo sincronizar o envio e recibo das mensagens, permitindo a troca fluente de mensagens entre as partes envolvidas.
6. Conclusão
A programação de Aplicações Web é complexa e promissora. Seria possível escrever cursos inteiros sobre suas aplicações e conceitos, aprofundar no tema de segurança e estudar mais a fundo a dinâmica de troca de informações.
Mas creio que o pequeno post de hoje deu uma pequena abordagem do tema ao nosso leitor, que agora pode se considerar iniciado no tema.
7. Referência:
(Ataque DDOS):
(Sockets):
(Sockets & Javascript):
(Glassfish - Web Sockets (Javascript))
(jSocket - Javascript)
(Socket.io - Alternativa)
Nenhum comentário:
Postar um comentário