autenticação básica falha com glassfish

Primeiro de tudo, minhas desculpas por este longo post. Esta é uma continuação da minha pergunta anterior ( janela de autenticação requerida aparecendo após a atualização 7u21 ) relacionada a esse problema, mas reduzi a pesquisa. Em suma, parece que minha autenticação BASIC está quebrada desde o Java 7u21.

Os applets iniciados por meio dos arquivos JNLP não estão funcionando de forma estável e fornecem janelas de pop-up de autenticação.

A CONFIGURAÇÃO

Primeiro de tudo eu configurei um database MySQL com uma tabela usável e de grupo.

  • Tabela: autenticação

insira a descrição da imagem aqui

  • Tabela: grupos

insira a descrição da imagem aqui

Em seguida eu configurei um jdbcRealm no Glassfish. Observe que os campos de usuário do database e senha do database estão vazios porque eu uso um JNDI (veja mais abaixo):

Configurações do reino Glassfish:

insira a descrição da imagem aqui

Configuração JDNI (conforme mostrado no arquivo domain.xml):

          

Feito isso, alterei o domínio padrão para o jdbcRealm recém-criado e verifiquei o principal padrão no mapeamento de function :

insira a descrição da imagem aqui

TESTANDO

Depois de tudo isso, para testar, criei um WebService simples no Netbeans que busca alguns países do database e configurou o web.xml para autenticação BASIC:

    ServletAdaptor com.sun.jersey.spi.container.servlet.ServletContainer  Multiple packages, separated by semicolon(;), can be specified in param-value com.sun.jersey.config.property.packages service   com.sun.jersey.api.json.POJOMappingFeature true  1   ServletAdaptor /webresources/*    30    Basic Protection  REST  /webresources/*    dummy    BASIC jdbcRealm   Dummy dummy  

Para testar o webservice, cliquei nele com o botão direito do mouse no NetBeans e cliquei em Testar serviço da Web RESTful . Uma nova janela do Internet Explorer é aberta e mostra uma canvas de login, eu insiro as credenciais para o usuário fictício e tudo funciona.

Em seguida, crio um projeto JavaFX FXML simples que busca os países. Eu tenho uma class (que usa Jersey) que parece seguir. Este é o código gerado pelo NetBeans 7.3:

 private WebResource webResource; private Client client; private static final String BASE_URI = "http://localhost:8080/myWS/webresources"; public CountriesClient() { com.sun.jersey.api.client.config.ClientConfig config = new com.sun.jersey.api.client.config.DefaultClientConfig(); client = Client.create(config); webResource = client.resource(BASE_URI).path("entities.countries"); } public void close() { client.destroy(); } public void setUsernamePassword(String username, String password) { client.addFilter(new com.sun.jersey.api.client.filter.HTTPBasicAuthFilter(username, password)); } public  T findAll_XML(Class responseType) throws UniformInterfaceException { WebResource resource = webResource; return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_XML).get(responseType); } 

No meu arquivo FXML Controller, tenho esse método vinculado a um botão:

 @FXML private void handleButtonAction(ActionEvent event) { System.out.println("You clicked me!"); CountriesClient c = new CountriesClient(); c.setUsernamePassword("dummy", "****"); String r = c.findAll_XML(String.class); System.out.println(r); c.close(); } 

Isso é sobre a configuração do meu projeto. Agora, quando eu testo isso dentro do NetBeans ou eu lanço isso através do arquivo * .jar, tudo funciona como pretendido e ele me dá a seguinte saída:

 Belgium1Ireland2United Kingdom3Poland4 

No entanto, assim que inicio o applet através de um arquivo * .jnlp, recebo este popup irritante reclamando sobre credenciais:

insira a descrição da imagem aqui

O console java registra isso:

 network: Cache entry found [url: http://localhost:8080/myWS/webresources/entities.countries, version: null] prevalidated=false/0 cache: Adding MemoryCache entry: http://localhost:8080/myWS/webresources/entities.countries cache: Resource http://localhost:8080/myWS/webresources/entities.countries has expired. cache: Resource http://localhost:8080/myWS/webresources/entities.countries has cache control: no-cache. network: Connecting http://localhost:8080/myWS/webresources/entities.countries with proxy=DIRECT network: Connecting socket://localhost:8080 with proxy=DIRECT network: Firewall authentication: site=localhost/127.0.0.1:8080, protocol=http, prompt=jdbcRealm, scheme=basic network: ResponseCode for http://localhost:8080/myWS/webresources/entities.countries : 401 network: Encoding for http://localhost:8080/myWS/webresources/entities.countries : null network: Connecting http://localhost:8080/myWS/webresources/entities.countries with proxy=DIRECT basic: JNLP2ClassLoader.findClass: com.sun.jersey.core.header.InBoundHeaders: try again .. basic: JNLP2ClassLoader.findClass: com.sun.jersey.core.util.StringKeyStringValueIgnoreCaseMultivaluedMap: try again .. network: Downloading resource: http://localhost:8080/myWS/webresources/entities.countries Content-Length: 322 Content-Encoding: null network: Wrote URL http://localhost:8080/myWS/webresources/entities.countries to File C:\Users\stbrunee\AppData\LocalLow\Sun\Java\Deployment\cache\6.0\6\4b456206-236d2196-temp cache: MemoryCache replacing http://localhost:8080/myWS/webresources/entities.countries (refcnt=0). Was: URL: http://localhost:8080/myWS/webresources/entities.countries | C:\Users\stbrunee\AppData\LocalLow\Sun\Java\Deployment\cache\6.0\6\4b456206-15cb0b99.idx Now: URL: http://localhost:8080/myWS/webresources/entities.countries | C:\Users\stbrunee\AppData\LocalLow\Sun\Java\Deployment\cache\6.0\6\4b456206-236d2196.idx Belgium1Ireland2United Kingdom3Poland4 

No lado do servidor (log glassfish)

 FINE: [Web-Security] Policy Context ID was: myWS/myWS FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/webresources/entities.countries" "GET") FINE: [Web-Security] hasUserDataPermission isGranted: true FINE: [Web-Security] Policy Context ID was: myWS/myWS FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS FINE: [Web-Security] Checking Web Permission with Principals : null FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET") FINEST: JACC Policy Provider: PolicyWrapper.implies, context (myWS/myWS)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")) FINE: [Web-Security] hasResource isGranted: false FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET") FINE: [Web-Security] Policy Context ID was: myWS/myWS FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/webresources/entities.countries" "HEAD") FINE: [Web-Security] hasUserDataPermission isGranted: true FINE: [Web-Security] Policy Context ID was: myWS/myWS FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS FINE: [Web-Security] Checking Web Permission with Principals : null FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "HEAD") FINEST: JACC Policy Provider: PolicyWrapper.implies, context (myWS/myWS)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "HEAD")) FINE: [Web-Security] hasResource isGranted: false FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "HEAD") //NOW I PRESS CANCEL AT THE POPUP WINDOW CLIENT SIDE FINE: [Web-Security] Setting Policy Context ID: old = null ctxID = myWS/myWS FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/webresources/entities.countries" "GET") FINE: [Web-Security] hasUserDataPermission isGranted: true FINE: [Web-Security] Policy Context ID was: myWS/myWS FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS FINE: [Web-Security] Checking Web Permission with Principals : null FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET") FINEST: JACC Policy Provider: PolicyWrapper.implies, context (myWS/myWS)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")) FINE: [Web-Security] hasResource isGranted: false FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET") FINEST: Processing login with credentials of type: class com.sun.enterprise.security.auth.login.common.PasswordCredential FINE: Logging in user [dummy] into realm: jdbcRealm using JAAS module: jdbcRealm FINE: Login module initialized: class com.sun.enterprise.security.auth.login.JDBCLoginModule FINEST: JDBC login succeeded for: dummy groups:[dummy] FINE: JAAS login complete. FINE: JAAS authentication committed. FINE: Password login succeeded for : dummy FINE: Set security context as user: dummy FINE: [Web-Security] Policy Context ID was: myWS/myWS FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS FINE: [Web-Security] Checking Web Permission with Principals : dummy, dummy FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET") FINE: [Web-Security] hasResource isGranted: true FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET") 

Existem algumas coisas que eu realmente não entendo:

  • No lado do cliente, vejo um código de resposta 401, que significa Não autorizado. Como isso pode acontecer se eu usar exatamente as mesmas credenciais que eu uso para testar meu serviço web?
  • No lado do cliente, se eu pressionar Cancelar nas janelas pop-up de autenticação, por que ainda recebo os dados XML da minha solicitação se o usuário não estiver devidamente autenticado?
  • No lado do servidor, ainda está ocorrendo um processo de autenticação. Isso tem a ver com o fato de que isso é codificado em Java? Mas, novamente, se o processo de autenticação realmente der certo, por que o popup de autenticação é exibido no lado do cliente?

O motivo dos sintomas encontrados é que o Oracle ativou o armazenamento em cache de respostas HTTP por padrão com o JDK7 no Web Start :

Cache ativado por padrão: o armazenamento em cache do conteúdo da rede para o código do aplicativo em execução no modo Web Start agora está ativado por padrão. Isso permite que o aplicativo melhore o desempenho e a consistência com o modo de execução do applet. Para garantir que a cópia mais recente do conteúdo seja usada, o aplicativo pode usar URLConnection.setUseCaches(false) ou solicitar valores de Cache-Control header no-cache/no-store .

Então, o que eu fiz foi definir esse header depois de criar o cliente Jersey:

 Client client = Client.create(); client.addFilter( new HTTPBasicAuthFilter( userId, password ) ); client.addFilter( new ClientFilter() { @Override public ClientResponse handle( ClientRequest cr ) throws ClientHandlerException { List cacheControlRequestValues = new ArrayList(); cacheControlRequestValues.add( "no-cache" ); cacheControlRequestValues.add( "no-store" ); cr.getHeaders().put( HttpHeaders.CACHE_CONTROL, cacheControlRequestValues ); return getNext().handle( cr ); } } 

Agora, se a especificação acima estivesse correta, e a implementação do Web Start seguiria a referência HTTP / 1.1 , que afirma

O objective da diretiva no-store é impedir a liberação inadvertida ou a retenção de informações confidenciais (por exemplo, em fitas de backup). A diretiva no-store se aplica a toda a mensagem e pode ser enviada em uma resposta ou em uma solicitação. Se enviado em uma solicitação, um cache NÃO DEVE armazenar nenhuma parte dessa solicitação ou de qualquer resposta a ela.

estaríamos bem – um sniffer de rede provou que o cliente estava configurando o header Cache-Control corretamente. Além disso, um ContainerResponseFilter de Jersey estava me mostrando que o header da solicitação estava definido corretamente. A resposta, por outro lado, não tinha nenhum conjunto de headers de controle de cache. Não deve importar de acordo com a especificação, mas na realidade o Web Start manteve o cache das respostas!

Então escrevi um ContainerResponseFilter que copia o header Cache-Control da solicitação para a resposta:

 import com.sun.jersey.spi.container.ContainerRequest; import com.sun.jersey.spi.container.ContainerResponse; import com.sun.jersey.spi.container.ContainerResponseFilter; import javax.ws.rs.core.HttpHeaders; import java.util.ArrayList; import java.util.List; public class CacheControlCopyFilter implements ContainerResponseFilter @Override public ContainerResponse filter( ContainerRequest containerRequest, ContainerResponse containerResponse ) { if ( containerRequest.getRequestHeader( HttpHeaders.CACHE_CONTROL ) != null ) { List responseCacheControlValues = new ArrayList( containerRequest.getRequestHeader( HttpHeaders.CACHE_CONTROL ).size() ); for ( String value : containerRequest.getRequestHeader( HttpHeaders.CACHE_CONTROL ) ) { responseCacheControlValues.add( value ); } containerResponse.getHttpHeaders().put( HttpHeaders.CACHE_CONTROL, responseCacheControlValues ); } return containerResponse; } } 

e o ativou no web.xml

  com.sun.jersey.spi.container.ContainerResponseFilters my.package.CacheControlCopyFilter  

Então você tem que apagar seu cache de cliente Java:

 "javaws -viewer" -> General -> Settings... -> Delete Files... -> Select all three check boxes -> OK 

e voilà, não mais autenticação irritante pop-ups 🙂

Eu converti o código acima para usar o Jersey 2.0 API.

No lado do cliente, o ClientFilter é substituído pelo ClientRequestFilter e os filtros são registrados. Além disso, a interface de filtro não é uma cadeia como na API 1.0.

  Client client = ClientBuilder.newClient(clientConfig); client.register(new HttpBasicAuthFilter(username, password)); client.register(new ClientRequestFilter() { @Override public void filter(ClientRequestContext crc) throws IOException { List cacheControlRequestValues = new ArrayList<>(); cacheControlRequestValues.add("no-cache"); cacheControlRequestValues.add("no-store"); crc.getHeaders().put(HttpHeaders.CACHE_CONTROL, cacheControlRequestValues); } }); 

No lado do servidor, o nome da interface, ContainerResponseFilter, é o mesmo, mas a assinatura do método é um pouco diferente. Não há necessidade de web.xml, já que você pode especificar a anotação @Provider no início da class (o que para mim foi a parte complicada para fazer a solução funcionar).

 @Provider public class CacheControlCopyFilter implements ContainerResponseFilter { @Override public void filter( ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { if (requestContext.getHeaderString(HttpHeaders.CACHE_CONTROL) != null) { List responseCacheControlValues = new ArrayList<>( requestContext.getHeaders().get(HttpHeaders.CACHE_CONTROL).size()); for (String value : requestContext.getHeaders().get(HttpHeaders.CACHE_CONTROL)) { responseCacheControlValues.add(value); } responseContext.getHeaders().put(HttpHeaders.CACHE_CONTROL, responseCacheControlValues); } } } 
    Intereting Posts