glassfish的基本身份validation失败

首先,我为这篇长篇文章道歉。 这是我之前的问题( 在7u21更新后弹出身份validation所需的窗口 )关于此问题的延续 ,但我缩小了搜索范围。 简而言之,自Java 7u21以来,我的BASIC身份validation似乎已被破坏。

通过JNLP文件启动的小程序根本不起作用,并提供了身份validation弹出窗口。

设置

首先,我建立了一个带有usertable和grouptable的MySQL数据库

  • 表:身份validation

在此处输入图像描述

  • 表:组

在此处输入图像描述

接下来我在Glassfish中设置了一个jdbcRealm 。 请注意,数据库用户和数据库密码字段为空,因为我使用的是JNDI(请参见下文):

Glassfish境界设置:

在此处输入图像描述

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

          

完成此操作后,我将默认域更改为新创建的jdbcRealm,并检查Default principal to role mapping

在此处输入图像描述

测试

毕竟,为了测试,我在Netbeans中创建了一个简单的WebService,它从数据库中获取一些国家并配置web.xml进行BASIC身份validation:

    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  

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

接下来,我创建一个简单的JavaFX FXML项目来获取这些国家/地区。 我有一个class级(谁使用泽西岛)看起来像跟随。 这是由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); } 

在我的FXML Controller文件中,我将此方法链接到一个按钮:

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

这是关于我的项目的设置。 现在,当我在Netbeans中测试它或者我通过* .jar文件启动它时,一切都按预期工作,它给了我以下输出:

 Belgium1Ireland2United Kingdom3Poland4 

但是,一旦我通过* .jnlp文件启动applet,我就会收到这个恼人的弹出窗口抱怨凭据:

在此处输入图像描述

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 Belgium1Ireland2United Kingdom3Poland4 

在服务器端(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服务的完全相同的凭据,这怎么可能呢?
  • 在客户端,如果我在身份validation弹出窗口中按取消,如果用户未经过正确的身份validation,为什么还会从我的请求中收到XML数据?
  • 在服务器端,仍然正在进行身份validation过程。 这是否与Java编码的事实有关? 但同样,如果身份validation过程实际成功,为什么在客户端显示身份validation弹出窗口?

您遇到的症状的原因是Oracle 在Web Start中默认使用JDK7启用了HTTP响应的缓存 :

默认情况下启用缓存:默认情况下,现在启用在Web启动模式下运行的应用程序代码的网络内容缓存。 这允许应用程序改进性能并与applet执行模式保持一致。 为确保使用最新的内容副本,应用程序可以使用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 cacheControlRequestValues = new ArrayList(); cacheControlRequestValues.add( "no-cache" ); cacheControlRequestValues.add( "no-store" ); cr.getHeaders().put( HttpHeaders.CACHE_CONTROL, cacheControlRequestValues ); return getNext().handle( cr ); } } 

现在,如果上述规范是正确的,并且Web Start的实现将遵循HTTP / 1.1引用 ,其中指出

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

我们没问题 – 网络嗅探器certificate客户端正确设置了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 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; } } 

并在web.xml激活它

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

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

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

和voilà,没有更烦人的身份validation弹出窗口:)

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

在客户端,ClientFilter替换为ClientRequestFilter,并且filter已注册。 此外,filter接口不像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 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 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); } } }