Os Servlets são tipicamente acessados através da internet, e assim sendo devemos se preocupar bastante com os requisitos de segurança. Através da utilização de anotações ou do arquivo web.xml podemos especificar o modelo de segurança para Servlets, incluindo papeis, controle de acesso e requisitos de autenticação. As recentes versões do Servlet, em especial a versão 3.1, trás mais algumas novidades como o gerenciamento de múltiplas requisições que permite aos desenvolvedores manipular entre outras coisas múltiplos arquivos. Outra funcionalidade é o empacotamento de recursos que oferece uma maior produtividade e facilidade para usar recursos de terceiros localizados em arquivos jar. Além disso, temos também o mapeamento de erros que permite um resultado mais compreensível ao usuário quando algum problema ocorre devido algum erro do HTTP ou exceção lançada por algum Servlet.

Nas próximas seções veremos mais sobre cada uma das funcionalidades mencionadas acima.

Segurança

A anotação @ServletSecurity é utilizada para especificar restrições de segurança nas classes de implementação do Servlet para todos métodos disponíveis ou para métodos “doXXX” específicos.

O container forçará que as mensagens correspondentes a “doXXX” possam ser invocadas pelos usuários nos papeis (roles) especificados. Segue na Listagem 1 um código de implementação utilizando a anotação @ServletSecurity.

Listagem 1. Exemplo de código utilizando a anotação @ServletSecurity.


  @WebServlet("/testeServlet")
  @ServletSecurity(value=@HttpConstraint(rolesAllowed = {"NORMAL"}),
           httpMethodConstraints={
                     @HttpMethodConstraint(value="GET", rolesAllowed="USUARIO"),
                     @HttpMethodConstraint(value="POST", rolesAllowed={"ADMINISTRADOR", "GERENTE"})
           }
  )
  public class TesteServlet extends javax.servlet.http.HttpServlet {
           //. . .
  }

No exemplo de código acima a anotação “@HttpMethodConstraint” é utilizada para especificar que o método doGet pode ser invocado pelos usuário no papel (role) USUARIO, e o método doPost pode ser invocado pelos usuários nos papéis ADMINISTRADOR e GERENTE. O @HttpConstraint especifica que todos os outros métodos podem ser invocados pelos usuários no papel NORMAL. Os papeis são mapeados para entidades de segurança ou grupos no container.

As restrições de segurança podem ser também especificadas usando o elemento no web.xml. Dentro dele, um elemento é usado para especificar restrições nas operações HTTP e recursos web, o elemento é usado para especificar os papéis permitidos para acessar o recurso e indica como o dado entre o cliente e o servidor deveria ser protegido pelo sub-elemento . Segue na Listagem 2 um exemplo de como poderíamos utilizar os elementos citados no arquivo web.xml.

Listagem 2. Exemplo de configurações de segurança utilizando o arquivo web.xml.


  <security-constraint>
           <web-resource-collection>
                     <url-pattern>/testeServlet/*</url-pattern>
                     <http-method>GET</http-method>
           </web-resource-collection>
   
           <auth-constraint>
                     <role-name>GERENTE</role-name>
           </auth-constraint>
   
           <user-data-constraint>
                    <transport-guarantee>INTEGRITY</transport-guarantee>
           </user-data-constraint>
  </security-constraint>

Este deployment descriptor exige que apenas o método GET na URL /testeServlet/* esteja protegida. Este método pode apenas ser acessado por um usuário no papel "GERENTE" com uma exigência de integridade de conteúdo.

Todos os outros métodos HTTP que não sejam GET estão desprotegidos.

Se métodos HTTP não são enumerados dentro de uma restrição de segurança, as proteções definidas pela restrição se aplicam ao conjunto completo de métodos HTTP. Segue na Listagem 3 um exemplo de como poderíamos proteger todo conjunto de métodos HTTP.

Listagem 3. Exemplo de como proteger todos os métodos HTTP.


  <security-constraint>
           <web-resource-collection>
                     <url-pattern>/testeServlet/*</url-pattern>
           </web-resource-collection>
           . . .
  </security-constraint>

Neste código todos os métodos HTTP na URL /testeServlet/* são protegidos.

O elemento pode ser usado para especificar a lista de métodos HTTP não protegidos pela restrição. Segue na Listagem 4 um exemplo de como podemos utilizar o elemento .

Listagem 4. Exemplo de utilização do elemento .


  <security-constraint>
           <web-resource-collection>
                     <url-pattern>/account/*</url-pattern>
                     <http-method-omission>GET</http-method-omission>
           </web-resource-collection>
   
           . . .
  </security-constraint>

No código acima tem-se que apenas o método GET do HTTP não é protegido e todos os outros métodos do protocolo HTTP são protegidos.

O elemento , um novo elemento adicionado na versão 3.1 do Servlet, pode ser usado para negar um pedido de método HTTP para um método HTTP não coberto. A requisição negada é retornada com um código de erro 403 (SC_FORBIDDEN). Segue na Listagem 5 um exemplo de utilização do elemento.

Listagem 5. Exemplo de utilização do elemento .


  <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
           http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
           version="3.1">
   
           <deny-uncovered-http-methods/>
                     
           <web-resource-collection>
                     <url-pattern>/testeServlet/*</url-pattern>
                     <http-method>GET</http-method>
           </web-resource-collection>
   
           . . .
   </web-app>

No código acima o elemento garante que o GET do HTTP é chamado com as credenciais de segurança necessárias, e todos os métodos HTTP são negados com um código de erro 403.

As anotações @RolesAllowed, @DenyAll, @PermitAll e @TransportProtected fornecem um conjunto alternativo de anotações para especificar papéis de segurança em um recurso especial ou um método do recurso. Segue na Listagem 6 um exemplo.

Listagem 6. Exemplo utilizando a anotação @RolesAllowed.


  @RolesAllowed("ADMINISTRADOR")
  protected void doGet(HttpServletRequest request, HttpServletResponse response) {
           //. . .
  }

Se uma anotação é especificada tanto na classe quando num método, aquele especificado no método sobrescreve o especificado na classe.

A última versão do Servlet 3.1 introduz dois novos papéis pré-definidos, são eles:

  • * mapeia para qualquer papel definido.
  • ** mapeia para qualquer usuário autenticado independente do papel.

Isso permite que possamos especificar as restrições de segurança em um nível mais elevado do que um papel particular, caso seja necessário.

É importante salientar que no máximo, um dos @RolesAllowed, @DenyAll, ou @PermitAll pode ser especificado em um destino.

A anotação @TransportProtected pode ser definida em combinação com as anotações @PermitAll ou @RolesAllowed.

Os Servlets podem ser configurados para HTTP Basic, HTTP Digest, Cliente HTTPS e autenticação baseada em formulário.

Segue na Listagem 7 um código de exemplo

Listagem 7. Exemplo de autenticação baseada em formulário.


  <form method="POST" action="j_security_check">
           <input type="text" name="j_username">
           <input type="password" name="j_password" autocomplete="off">
           <input type="button" value="submit">
  </form>

Este código mostra como a autenticação baseada em formulário pode ser feita. Esta forma de autenticação é bastante semelhante à forma de autenticação realizada com o Spring Security. O formulário de login deve conter campos para a entrada dos dados de username e senha. Esses campos devem ser nomeados como “j_username” e “j_password” respectivamente. O “action” do formulário é sempre “j_security_check”.

O Servlet 3.1 requer autocomplete="off" no campo de senha do formulário, o que aumenta ainda mais a segurança dos formulários baseados em Servlet.

O HttpServletRequest também provê segurança programática com login, logout, e o método authenticate. O método login valida o username e a senha fornecidos no Password Validation Realm, que é específico do container, configurado para o ServletContext. Isto garante que os métodos getUserPrincipal, getRemoteUser, e getAuthType retornam valores válidos.

O método de login pode ser usado como um substituto para o login baseado em formulário. Por sua vez o método authenticate usa o mecanismo de login do Container configurado para o ServletContext autenticar o usuário que está fazendo a solicitação.

Resource Packaging

Podemos acessar os recursos de um arquivo “.war” usando os métodos ServletContext.getResource e ServletContext.getResourceAsStream. O caminho do recurso é especificado como uma String com um "/".

Este caminho é resolvido em relação à raiz do contexto ou em relação ao diretório META-INF/resources dos arquivos JAR empacotados no diretório WEB-INF/lib. Podemos imaginar a seguinte estrutura de código abaixo:


  minhaApp.war
           WEB-INF
                     lib
                              minhaBiblioteca.jar

Na estrutura acima temos um arquivo “minhaApp.war” com um diretório “WEB-INF” e dentro desse diretório um outro diretório “lib” que possui um jar “minhaBiblioteca.jar”. Por sua vez no arquivo “minhaBiblioteca.jar” temos a seguinte estrutura:


  minhaBiblioteca.jar
           MinhaClasse1Exemplo.class
           MinhaClasse2Exemplo.class
           css
                     principal.css
           imagens
                     cabecalho.png
                     rodape.png

Normalmente, se as folhas de estilo (CSS) e diretórios de imagens precisam ser acessadas no servlet, precisamos extraí-los manualmente na raiz da aplicação web. O Servlet na versão 3 permitem que a biblioteca empacote os recursos no diretório META-INF/resources. Dessa forma, teríamos a estrutura abaixo:


  minhaBiblioteca.jar
           MinhaClasse1Exemplo.class
           MinhaClasse2Exemplo.class
           META-INF
                     resources
                              css
                                        principal.css
                              imagens
                                        cabecalho.png
                                        rodape.png

Neste caso, os recursos não precisam ser extraídos na raiz da aplicação. Ao invés disso podem ser acessados diretamente. Isto permite que recursos de terceiros no META-INF/resources possam ser acessados diretamente ao invés de extraídos manualmente. Este tipo de coisa facilita imensamente a vida dos programadores dando maior produtividade e facilidade.

A aplicação sempre busca os recursos na raiz antes de verificar os JARs empacotados no diretório WEB-INF/lib.

Error Mapping

Podemos mapear para um recurso um código de erro do HTTP ou uma exceção lançada por um Servlet. Este recurso da aplicação pode então personalizar a aparência do conteúdo quando um Servlet gera um erro.

Essas páginas são definidas através do elemento , conforme mostrado no exemplo da Listagem 8.

Listagem 8. Exemplo de definição do elemento error-page no deployment descriptor.


  <error-page>
           <error-code>404</error-code>
           <location>/error-404.jsp</location>
  </error-page>

Adicionando o fragmento de código acima no arquivo “web.xml” teremos como resultado a exibição da página “/error-404.jsp” para o cliente sempre que ele tentar acessar um recursos inexistente no servidor.

Podemos implementar este mapeamento também para outros erros do HTTP apenas adicionando outros elementos .

O HTTP agrupa o status (retorno) da seguinte maneira:

  • Do 200 até 299: Requisições foram todas bem sucedidas
  • Do 300 até 399: Informações
  • Do 400 até 499: Erro de solicitação (request)
  • Do 500 até 599: Erro do servidor e Programação

Entre os códigos de erros mais comuns temos:

  • 200 : A URL foi encontrada e transmissão a foi concluída com sucesso.
  • 400 : A Solicitação é incompreensível, o protocolo é inesistente ou o tipo é incompatível.
  • 404 : A URL solicitada não foi encontrada.
  • 405 : O servidor não suporta o método solicitado.
  • 500 : O Erro é desconhecido do servidor.
  • 503 : A Capacidade máxima do servidor foi alcançada.

O elemento é usado para mapear uma exceção lançada por um Servlet para um recurso na aplicação web. Segue na Listagem 9 um exemplo de código.

Listagem 9. Exemplo de mapeamento de um exceção lançada num Servlet para um recurso.


  <error-page>
           <exception-type>com.exemplo.MeuServletExemplo</exception-type>
           <location>/error.jsp</location>
  </error-page>

Caso o Servlet definido (com.exemplo.MeuServletExemplo) lance uma exceção teremos como resultado a exibição da página /error.jsp ao usuário.

Podemos implementar o mapeamento para outras exceções adicionando outros elementos .

É importante salientar que a declaração deve ser única para cada nome de classe e código HTTP.

Gerenciando Múltiplas Requisições

A anotação @MultipartConfig pode ser especificada em um Servlet, indicando que o Servlet espera uma solicitação do tipo “multipart/form-data”. Os métodos “HttpServletRequest.getParts()” e “HttpServletRequest.getPart” então tornam as várias partes da solicitação disponível. Segue na Listagem 10 um exemplo de código.

Listagem 10. Exemplo de como gerenciar múltiplas requisições com @MultipartConfig.


  @WebServlet(urlPatterns = {"/CarregaArquivoServlet"})
  @MultipartConfig(location="/tmp")
  public class FileUploadServlet extends HttpServlet {
           
           @Override
           protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                     for (Part parte : request.getParts()) {
                              parte.write("meuArquivo");
                     }
           }
  }

No código acima temos a especificação da anotação @MultipartConfig na classe, indicando que o método “doPost()” receberá uma solicitação do tipo “multipart/form-data”. O atributo location é utilizado para especificar a localização do diretório onde os arquivos são armazenados. O método “getParts” fornece uma coleção de partes para este solicitação “multipart”. Por fim, “part.write” é utilizado para escrever esta parte carregada diretamente para o disco do servidor.

O Servlet 3.1 adicionou um novo método “Part.getSubmittedFileName” para obter o nome do arquivo especificado pelo cliente.

O Servlet criado acima pode ser invocado por uma página JSP. O código da Listagem 11 mostra um exemplo de como isso poderia ser feito.

Listagem 11. Exemplo de código de uma JSP invocando o Servlet.


  <form action="CarregaArquivoServlet"
           enctype="multipart/form-data"
           method="POST">
           
           <input type="file" name="meuArquivo"><br>
           <input type="Submit" value="Carregar Arquivo"><br>
  </form>

Neste código o formulário é enviado para CarregaArquivoServlet com a codificação “multipart/formdata”.

Bibliografia

[1]Josh Juneau. Java EE 7 Recipes: A Problem-Solution Approach. Apress, 2013.

[2]Josh Juneau. Introducing Java EE 7: A Look at What's New. Apress, 2013.

[3]Arun Gupta. Java EE 7 Essentials. O'Reilly, 2013.