2013-06-24 29 views
3

首先,我很抱歉这个长篇文章。这是我之前的问题(Authentication required window popping up after 7u21 update)关于此问题的延续,但我缩小了搜索范围。总之,自从Java 7u21以来,我的BASIC身份验证似乎中断了。基本身份验证失败玻璃鱼

通过JNLP文件启动的小应用程序不能稳定运行,并提供身份验证弹出窗口。

的SETUP

首先我已经成立了一个MySQL数据库与用户表和grouptable将的。

  • 表:认证

enter image description here

  • 表:组

enter image description here

接下来,我已经建立了jdbcReal在Glassfish中的m。注意,数据库用户和数据库密码字段是空的,因为我使用JNDI(进一步参见下文):

Glassfish的境界设置:

enter image description here

JDNI配置如图所示的( domain.xml文件):

<jdbc-connection-pool connection-validation-method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" wrap-jdbc-objects="false" res-type="javax.sql.DataSource" name="mysql_mit_rohhPool"> 
    <property name="URL" value="jdbc:mysql://localhost:3306/mit?zeroDateTimeBehavior=convertToNull"></property> 
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property> 
    <property name="Password" value="****"></property> 
    <property name="portNumber" value="3306"></property> 
    <property name="databaseName" value="mit"></property> 
    <property name="User" value="****"></property> 
    <property name="serverName" value="localhost"></property> 
</jdbc-connection-pool> 
<jdbc-resource pool-name="mysql_mit_rohhPool" jndi-name="jdbc/DB_MIT"></jdbc-resource> 

完成此操作后,我将默认领域更改为新创建的jdbcRealm和检查默认主体到角色的映射

enter image description here

测试

毕竟,对于测试我在NetBeans中创建一个简单的WebService谁从数据库中获取一些国家并配置了web.xml进行BASIC验证:

<?xml version="1.0" encoding="UTF-8"?> 
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> 
<servlet> 
    <servlet-name>ServletAdaptor</servlet-name> 
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> 
    <init-param> 
     <description>Multiple packages, separated by semicolon(;), can be specified in param-value</description> 
     <param-name>com.sun.jersey.config.property.packages</param-name> 
     <param-value>service</param-value> 
    </init-param> 
    <init-param> 
     <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> 
     <param-value>true</param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup> 
</servlet> 
<servlet-mapping> 
    <servlet-name>ServletAdaptor</servlet-name> 
    <url-pattern>/webresources/*</url-pattern> 
</servlet-mapping> 
<session-config> 
    <session-timeout> 
     30 
    </session-timeout> 
</session-config> 
<security-constraint> 
    <display-name>Basic Protection</display-name> 
    <web-resource-collection> 
     <web-resource-name>REST</web-resource-name> 
     <description/> 
     <url-pattern>/webresources/*</url-pattern> 
    </web-resource-collection> 
    <auth-constraint> 
     <description/> 
     <role-name>dummy</role-name> 
    </auth-constraint> 
</security-constraint> 
<login-config> 
    <auth-method>BASIC</auth-method> 
    <realm-name>jdbcRealm</realm-name> 
</login-config> 
<security-role> 
    <description>Dummy</description> 
    <role-name>dummy</role-name> 
</security-role> 

为了测试web服务,我右点击它在NetBeans和点击测试RESTful Web服务。一个新的Internet Explorer窗口打开并显示一个登录屏幕,我输入虚拟用户的凭证,一切正常。

接下来,我创建了一个简单的获取国家/地区的JavaFX FXML项目。我有一个类(谁使用泽西岛),看起来像下面。这是由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> T findAll_XML(Class<T> responseType) throws UniformInterfaceException { 
    WebResource resource = webResource; 
    return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_XML).get(responseType); 
} 

在我FXML控制器文件,我有这样的方法连接到一个按钮:

@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(); 
} 

这是关于我的项目的设置。只要我通过*启动小程序

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><countriess><countries><country>Belgium</country><id>1</id></countries><countries><country>Ireland</country><id>2</id></countries><countries><country>United Kingdom</country><id>3</id></countries><countries><country>Poland</country><id>4</id></countries></countriess> 

然而,:现在,当我测试这里面的Netbeans或我推出这个通过* .jar文件,一切都会按计划,它给了我下面的输出。 JNLP文件我得到这个恼人的弹出抱怨凭据:

enter image description here

Java控制台记录本:

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 
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><countriess><countries><country>Belgium</country><id>1</id></countries><countries><country>Ireland</country><id>2</id></countries><countries><country>United Kingdom</country><id>3</id></countries><countries><country>Poland</country><id>4</id></countries></countriess> 

在SE rver侧(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") 

有,我真的不明白几件事情:

  • 在客户端,我看到这意味着未授权401响应代码。如果我使用完全相同的凭据来测试我的web服务,这怎么可能?
  • 在客户端,如果我在身份验证弹出窗口上按取消,为什么仍然收到来自我的请求的XML数据(如果用户没有正确验证)?
  • 在服务器端,仍在进行身份验证过程。这是否与这是用Java编码的事实有关?但是,如果认证过程真的成功了,为什么在客户端显示认证弹出窗口?

回答

4

的理由让你遇到的症状是,甲骨文有enabled caching of HTTP responses by default with JDK7 in Web Start

缓存默认启用:缓存的网络内容,以便在Web Start的模式下运行的应用程序 代码现在默认启用。这允许 应用程序改进了与小应用程序执行 模式的性能和一致性。为确保使用最新的内容副本,应用程序 可以使用URLConnection.setUseCaches(false)或请求标头 Cache-Controlno-cache/no-store

所以我所做的是,创造了Jersey客户端之后设置这个头:

Client client = Client.create(); 
client.addFilter(new HTTPBasicAuthFilter(userId, password)); 
client.addFilter(new ClientFilter() { 

    @Override 
    public ClientResponse handle(ClientRequest cr) 
     throws ClientHandlerException { 
      List<Object> cacheControlRequestValues = new ArrayList<Object>(); 
      cacheControlRequestValues.add("no-cache"); 
      cacheControlRequestValues.add("no-store"); 
      cr.getHeaders().put(HttpHeaders.CACHE_CONTROL, cacheControlRequestValues); 
      return getNext().handle(cr); 
    } 
} 

现在,如果上面的说明是正确的,并且Web Start的实施将遵循HTTP/1.1 reference,其中规定

无存储指令的目的是为了防止无意释放 或保留的敏感信息(例如,上备份 磁带) 。无存储指令适用于整个消息,并且可以在响应中或在请求中发送。如果在请求中发送,则高速缓存不得存储此请求的任何部分或对其的任何响应 。

我们会很好 - 网络嗅探器证明客户端正确设置了Cache-Control头。另外泽西的ContainerResponseFilter向我展示了请求上的标题设置正确。另一方面,响应没有Cache-Control头部集合。根据规范应该不重要,但实际上Web Start不断缓存响应!

所以我写了一个ContainerResponseFilter那份从请求到响应的Cache-Control头:

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<Object> responseCacheControlValues = new ArrayList<Object>(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; 
    } 

} 

并激活它在web.xml

<init-param> 
    <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name> 
    <param-value>my.package.CacheControlCopyFilter</param-value> 
</init-param> 

然后,你必须删除你的Java客户端缓存:

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

andvoilà,no more恼人的身份验证弹出式窗口:)

+0

非常感谢分享,它像一个魅力! – Perneel

2

我已经将上面的代码转换为使用Jersey 2.0 API。

在客户端,ClientFilter被替换为ClientRequestFilter,并且过滤器被注册。此外,过滤器界面不像1.0 API那样链接。

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

在服务器端,接口名称ContainerResponseFilter是相同的,但方法签名有点不同。不需要web.xml,因为您可以在类的开始处指定@Provider注释(对于我来说,这是解决方案工作的一个棘手部分)。

@Provider 
public class CacheControlCopyFilter implements ContainerResponseFilter { 

    @Override 
    public void filter(
      ContainerRequestContext requestContext, 
      ContainerResponseContext responseContext) throws IOException { 

     if (requestContext.getHeaderString(HttpHeaders.CACHE_CONTROL) != null) { 
     List<Object> 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); 
     } 
    } 
}