java – 基本身份验证失败,使用glassfish

首先,我为这篇长篇文章道歉.这是我之前关于这个问题的问题(Authentication required window popping up after 7u21 update)的延续,但我缩小了搜索范围.简而言之,自Java 7u21以来,我的BASIC身份验证似乎已被破坏.

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

设置

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

>表:身份验证

>表:组

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

Glassfish境界设置:

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,并检查Default principal to role mapping:

测试

毕竟,为了测试,我在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中右键单击它并单击Test RESTful Web Service.一个新的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 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文件启动它时,一切都按预期工作,它给了我以下输出:

<?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>

但是,一旦我通过* .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
<?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>

在服务器端(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编码的事实有关?但同样,如果身份验证过程实际成功,为什么在客户端显示身份验证弹出窗口?

解决方法:

您遇到的症状的原因是Oracle有enabled caching of HTTP responses by default with JDK7 in Web Start

Caching enabled by default: Caching of network content for application
code running in Web Start mode is now enabled by default. This allows
application improved performance and consistency with applet execution
mode. To ensure the latest copy of content is used, the application
can use URLConnection.setUseCaches(false) or request header
Cache-Control values no-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,其中指出

The purpose of the no-store directive is to prevent the inadvertent
release or retention of sensitive information (for example, on backup
tapes). The no-store directive applies to the entire message, and MAY
be sent either in a response or in a request. If sent in a request, a
cache MUST NOT store any part of either this request or any response
to it.

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

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

上一篇:java – 泽西没有显示响应,以防它出错


下一篇:java – @GET可以为JAX-RS实现定义Consumes Content-Type吗?